@@ -94,7 +94,7 @@ logr design but also left out some parts and changed others:
94
94
95
95
The high-level slog API is explicitly meant to be one of many different APIs
96
96
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 )
98
98
package.
99
99
100
100
### Inspiration
@@ -142,6 +142,91 @@ There are implementations for the following logging libraries:
142
142
- ** github.com/go-kit/log** : [ gokitlogr] ( https://github.com/tonglil/gokitlogr ) (also compatible with github.com/go-kit/kit/log since v0.12.0)
143
143
- ** bytes.Buffer** (writing to a buffer): [ bufrlogr] ( https://github.com/tonglil/buflogr ) (useful for ensuring values were logged, like during testing)
144
144
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
+
145
230
## FAQ
146
231
147
232
### Conceptual
0 commit comments