Skip to content

Commit 8adefbe

Browse files
pohlythockin
authored andcommitted
docs: interoperability with slog
This assumes that context helper functions (#213) for slog will not get merged. If that is the consensus, then this documentation should be the last missing piece for slog support in logr.
1 parent ebabbb9 commit 8adefbe

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

README.md

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ logr design but also left out some parts and changed others:
9494

9595
The high-level slog API is explicitly meant to be one of many different APIs
9696
that can be layered on top of a shared `slog.Handler`. logr is one such
97-
alternative API, with interoperability provided by the [`slogr`](slogr)
97+
alternative API, with [interoperability](#slog-interoperability) provided by the [`slogr`](slogr)
9898
package.
9999

100100
### Inspiration
@@ -142,6 +142,91 @@ There are implementations for the following logging libraries:
142142
- **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0)
143143
- **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing)
144144

145+
## slog interoperability
146+
147+
Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler`
148+
and using the `slog.Logger` API with a `logr.LogSink`. [slogr](./slogr) provides `NewLogr` and
149+
`NewSlogHandler` API calls to convert between a `logr.Logger` and a `slog.Handler`.
150+
As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level
151+
slog API. `slogr` itself leaves that to the caller.
152+
153+
## Using a `logr.Sink` as backend for slog
154+
155+
Ideally, a logr sink implementation should support both logr and slog by
156+
implementing both the normal logr interface(s) and `slogr.SlogSink`. Because
157+
of a conflict in the parameters of the common `Enabled` method, it is [not
158+
possible to implement both slog.Handler and logr.Sink in the same
159+
type](https://github.com/golang/go/issues/59110).
160+
161+
If both are supported, log calls can go from the high-level APIs to the backend
162+
without the need to convert parameters. `NewLogr` and `NewSlogHandler` can
163+
convert back and forth without adding additional wrappers, with one exception:
164+
when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then
165+
`NewSlogHandler` has to use a wrapper which adjusts the verbosity for future
166+
log calls.
167+
168+
Such an implementation should also support values that implement specific
169+
interfaces from both packages for logging (`logr.Marshaler`, `slog.LogValuer`,
170+
`slog.GroupValue`). logr does not convert those.
171+
172+
Not supporting slog has several drawbacks:
173+
- Recording source code locations works correctly if the handler gets called
174+
through `slog.Logger`, but may be wrong in other cases. That's because a
175+
`logr.Sink` does its own stack unwinding instead of using the program counter
176+
provided by the high-level API.
177+
- slog levels <= 0 can be mapped to logr levels by negating the level without a
178+
loss of information. But all slog levels > 0 (e.g. `slog.LevelWarning` as
179+
used by `slog.Logger.Warn`) must be mapped to 0 before calling the sink
180+
because logr does not support "more important than info" levels.
181+
- The slog group concept is supported by prefixing each key in a key/value
182+
pair with the group names, separated by a dot. For structured output like
183+
JSON it would be better to group the key/value pairs inside an object.
184+
- Special slog values and interfaces don't work as expected.
185+
- The overhead is likely to be higher.
186+
187+
These drawbacks are severe enough that applications using a mixture of slog and
188+
logr should switch to a different backend.
189+
190+
## Using a `slog.Handler` as backend for logr
191+
192+
Using a plain `slog.Handler` without support for logr works better than the
193+
other direction:
194+
- All logr verbosity levels can be mapped 1:1 to their corresponding slog level
195+
by negating them.
196+
- Stack unwinding is done by the `slogr.SlogSink` and the resulting program
197+
counter is passed to the `slog.Handler`.
198+
- Names added via `Logger.WithName` are gathered and recorded in an additional
199+
attribute with `logger` as key and the names separated by slash as value.
200+
- `Logger.Error` is turned into a log record with `slog.LevelError` as level
201+
and an additional attribute with `err` as key, if an error was provided.
202+
203+
The main drawback is that `logr.Marshaler` will not be supported. Types should
204+
ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility
205+
with logr implementations without slog support is not important, then
206+
`slog.Valuer` is sufficient.
207+
208+
## Context support for slog
209+
210+
Storing a logger in a `context.Context` is not supported by
211+
slog. `logr.NewContext` and `logr.FromContext` can be used with slog like this
212+
to fill this gap:
213+
214+
func HandlerFromContext(ctx context.Context) slog.Handler {
215+
logger, err := logr.FromContext(ctx)
216+
if err == nil {
217+
return slogr.NewSlogHandler(logger)
218+
}
219+
return slog.Default().Handler()
220+
}
221+
222+
func ContextWithHandler(ctx context.Context, handler slog.Handler) context.Context {
223+
return logr.NewContext(ctx, slogr.NewLogr(handler))
224+
}
225+
226+
The downside is that storing and retrieving a `slog.Handler` needs more
227+
allocations compared to using a `logr.Logger`. Therefore the recommendation is
228+
to use the `logr.Logger` API in code which uses contextual logging.
229+
145230
## FAQ
146231

147232
### Conceptual

slogr/slogr.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ limitations under the License.
2121
// API and of a logr.LogSink through the slog.Handler and thus slog.Logger
2222
// APIs.
2323
//
24-
// Both approaches are currently experimental and need further work.
24+
// See the README in the top-level [./logr] package for a discussion of
25+
// interoperability.
2526
package slogr
2627

2728
import (

0 commit comments

Comments
 (0)