Skip to content

v2.0.8 — Restore Info(...any) backward compatibility, add InfoF for zero-alloc typed

Latest

Choose a tag to compare

@semihalev semihalev released this 25 Apr 19:15

Walks back the v2.0.7 API break on the global helpers. v2.0.6 untyped call sites compile and run again with no source changes. v2.0.7 typed call sites continue to work too. The zero-alloc typed path moves to new *F entrypoints that opt in by name.

What changed

zlog.Debug / Info / Warn / Error / Fatal once again accept ...any and route correctly:

zlog.Info("user logged in", "username", "alice", "user_id", 12345)              // 0 allocs (KV path)
zlog.Info("user logged in", zlog.String("username", "alice"))                    // 3 allocs (Field via ...any)

zlog.DebugF / InfoF / WarnF / ErrorF / FatalF are new typed-only entrypoints that take ...Field directly:

zlog.InfoF("user logged in", zlog.String("username", "alice"), zlog.Int("user_id", 12345))  // 0 allocs

zlog.InfoKV / DebugKV / WarnKV / ErrorKV / FatalKV are kept as explicit aliases for the untyped path. Anyone who already migrated to them in v2.0.7 keeps working.

Why the rollback

v2.0.7's switch to Info(msg, fields ...Field) was strictly a perf decision — it shaved 3 allocs from the typed-via-global path by avoiding Field-into-...any boxing. But it was the wrong call for a non-major release: every existing v2.0.6 caller of the untyped style (zlog.Info("msg", "k", v)) became a compile error.

The structurally-correct design is two names — one for each variadic shape — added additively. v2.0.8 does that:

Style API Allocations
Untyped key/value pairs zlog.Info("msg", "k", v) 0
Typed Fields (compat) zlog.Info("msg", String(...)) 3 (Field→any boxing at callsite)
Typed Fields (zero-alloc) zlog.InfoF("msg", String(...)) 0

The 3-alloc cost on Info(...any) with typed Fields is structural: passing a 56-byte Field through a ...any parameter heap-boxes each argument at the Go callsite, before the function runs. No compiler trick on the callee side eliminates that. The InfoF entrypoint sidesteps it by taking ...Field directly.

Compatibility matrix

Source style v2.0.6 v2.0.7 v2.0.8
zlog.Info("msg", "k", v) (untyped) 0 allocs compile error 0 allocs
zlog.Info("msg", String(...)) (typed) silent drop bug 0 allocs 3 allocs
zlog.InfoF("msg", String(...)) n/a n/a 0 allocs
zlog.InfoKV("msg", "k", v) n/a 0 allocs 0 allocs
Default().Info("msg", String(...)) 0 allocs 0 allocs 0 allocs

Migration

  • Coming from v2.0.6: bump the dependency, no code changes. (You also pick up every v2.0.7 fix: the silent-drop bug, the Bytes(empty) panic, the Logger.log 65535 truncation, the proper MPMC ring buffer.)
  • Coming from v2.0.7: bump the dependency, no code changes. Typed zlog.Info(...) calls now cost 3 allocs each — rename to zlog.InfoF(...) to keep zero-alloc behavior. The compiler doesn't catch this; it's a perf migration, not a correctness one.

Bench (Apple M5)

zlog.Info("msg", "k", v)              32 ns/op    0 allocs   (KV)
zlog.InfoF("msg", String(...))        34 ns/op    0 allocs   (typed, no boxing)
zlog.Info("msg", String(...))         87 ns/op    3 allocs   (typed via ...any)
Default().Info("msg", String(...))    34 ns/op    0 allocs   (typed, instance)

Other changes

Carries forward all v2.0.7 fixes:

  • Bytes(nil) and Bytes([]byte{}) no longer panic.
  • Logger.log clamps long messages to 65535 to match the uint16 header.
  • Logger.Fatal and StructuredLogger.Fatal always exit, even when filtered.
  • LogfmtWriter.Write is zero-alloc on long records (one-pass size estimate).
  • LogfmtWriter caches the formatted RFC3339 timestamp by Unix second.
  • AsyncWriter uses an LMAX Disruptor MPMC ring buffer (per-slot sequence numbers) — 212 ns/op end-to-end, race-clean.

Tests

Coverage for all three global-helper paths (KV via Info, typed via Info, typed via InfoF) plus the Fatal-when-filtered subprocess test. Full race-detector run is green; long-record alloc test gated //go:build !race because the race detector triggers GC frequently enough to clear sync.Pool's localcache for the 1 MiB-class buffers (a -race property, not a production bug).