Skip to content

fix: support slog.Group in the zap bridge#183

Merged
rvagg merged 4 commits into
masterfrom
rvagg/slog-group-bridge
May 11, 2026
Merged

fix: support slog.Group in the zap bridge#183
rvagg merged 4 commits into
masterfrom
rvagg/slog-group-bridge

Conversation

@rvagg
Copy link
Copy Markdown
Member

@rvagg rvagg commented May 6, 2026

slog.Group attrs falls through to ReflectType in slogAttrToZapField and is rendered []slog.Attr via reflection as [{"Key":"0","Value":{}},...]. This fix render Groups via a zapcore.ObjectMarshaler adapter.

slog.Group attrs falls through to ReflectType in slogAttrToZapField and
is rendered []slog.Attr via reflection as [{"Key":"0","Value":{}},...].
This fix render Groups via a zapcore.ObjectMarshaler adapter.
@rvagg rvagg requested review from gammazero and lidel May 6, 2026 00:22
@welcome

This comment was marked as off-topic.

lidel added 3 commits May 6, 2026 17:56
slog inlines a Group with an empty key into its parent, but the
record-level path emitted an empty-key field instead. Mirror the
nested encoder via zap.Inline and broaden test coverage.

- slog_bridge.go: inline path for empty-key group at record level
- slog_bridge.go: cross-ref comment between paired switches
- slog_bridge_test.go: cover time/duration/float/uint inside group
- slog_bridge_test.go: cover top-level empty-key inlining
slog handlers must call Value.Resolve() at the leaf to honor the
LogValuer contract. Without it, an attr whose Value is a LogValuer
arrives with Kind() == KindLogValuer and falls through every switch
case to ReflectType, handing zap the raw wrapper struct instead of
the value LogValue() would produce.

Concretely, for a User type whose LogValue() returns a Group of
{id, email}, the bridge would emit the reflected wrapper rather
than {"user":{"id":"u123","email":"***@example.com"}}. The output
is usually unhelpful and sometimes empty, depending on which fields
the wrapper happens to export. It also hides the new Group path,
since a LogValuer returning a slog.Group never reaches the
KindGroup case without resolution; calling value.Group() on an
unresolved LogValuer would also panic for the same reason.

Resolve() is idempotent and effectively free for non-LogValuer
values (a single Kind check), and is called on paths gated by
slog's Enabled() check, so filtered logs do not pay the cost.
This matches log/slog's own commonHandler resolution points.

- slog_bridge.go: resolve in both attr conversion paths
- slog_bridge.go: resolve in slogGroup.MarshalLogObject so the
  empty-key inline check sees LogValuer-of-Group
- slog_bridge_test.go: cover LogValuer returning a Group
Copy link
Copy Markdown
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, tested with kubo and no regressions (afaik, for what we use go-log for).

note: pushed three follow-ups (see below), feel free to tag a release or lmk if you want me to make one

fix: resolve slog LogValuer in zap bridge

slog.Handler must call Value.Resolve() at the leaf to honor LogValuer. Without it, a LogValuer attr falls through every switch case to ReflectType and zap renders the wrapper struct instead of what LogValue() returns. This also blocks the new Group path: a LogValuer returning a Group never reaches KindGroup, and value.Group() on an unresolved value panics. Matches log/slog's own commonHandler resolution points.

input v2.9.1 this branch
slog.Any("user", LogValuer→Group) "user":{} "user":{"id":"u123","email":"alice@example.com"}

fix: inline top-level empty-key slog.Group

slog inlines a Group with an empty key into its parent. The nested encoder did this; the record-level path did not. Fixed via zap.Inline(slogGroup(g)).

input v2.9.1 this branch
slog.Group("", ...) at top level "":[{"Key":"inlined","Value":{}},...] "inlined":"yes","count":7

chore: flag WithGroup as a known no-op

A TODO for humans and LLMs. Inline slog.Group(...) attrs nest correctly via the new path, but attrs added after Handler.WithGroup(name) are not prefixed with the group name. Full support needs deferred attr conversion plus a group-frame stack walked at Handle time. No caller in kubo/boxo needs it today; ship as-is, implement when someone hits it.

@rvagg rvagg merged commit b9ccca1 into master May 11, 2026
9 checks passed
@rvagg rvagg deleted the rvagg/slog-group-bridge branch May 11, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants