Skip to content

Commit 1fffd07

Browse files
pohlythockin
authored andcommittedDec 1, 2023
move slogr into main package
This is necessary to give the context handling functions access to the slogr functionality. Functions get renamed to match names in the main package where needed. The slogr package is now deprecated, but continues to be supported as part of the logr v1 API by keeping all exported items as wrappers or aliases for the main package.
1 parent 41d36ee commit 1fffd07

File tree

7 files changed

+177
-136
lines changed

7 files changed

+177
-136
lines changed
 

‎README.md

+14-14
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ 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](#slog-interoperability) provided by the [`slogr`](slogr)
98-
package.
97+
alternative API, with [interoperability](#slog-interoperability) provided by
98+
some conversion functions.
9999

100100
### Inspiration
101101

@@ -145,24 +145,24 @@ There are implementations for the following logging libraries:
145145
## slog interoperability
146146

147147
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`.
148+
and using the `slog.Logger` API with a `logr.LogSink`. `FromSlogHandler` and
149+
`ToSlogHandler` convert between a `logr.Logger` and a `slog.Handler`.
150150
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.
151+
slog API.
152152

153-
## Using a `logr.Sink` as backend for slog
153+
### Using a `logr.LogSink` as backend for slog
154154

155155
Ideally, a logr sink implementation should support both logr and slog by
156-
implementing both the normal logr interface(s) and `slogr.SlogSink`. Because
156+
implementing both the normal logr interface(s) and `SlogSink`. Because
157157
of a conflict in the parameters of the common `Enabled` method, it is [not
158158
possible to implement both slog.Handler and logr.Sink in the same
159159
type](https://github.com/golang/go/issues/59110).
160160

161161
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
162+
without the need to convert parameters. `FromSlogHandler` and `ToSlogHandler` can
163163
convert back and forth without adding additional wrappers, with one exception:
164164
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
165+
`ToSlogHandler` has to use a wrapper which adjusts the verbosity for future
166166
log calls.
167167

168168
Such an implementation should also support values that implement specific
@@ -187,13 +187,13 @@ Not supporting slog has several drawbacks:
187187
These drawbacks are severe enough that applications using a mixture of slog and
188188
logr should switch to a different backend.
189189

190-
## Using a `slog.Handler` as backend for logr
190+
### Using a `slog.Handler` as backend for logr
191191

192192
Using a plain `slog.Handler` without support for logr works better than the
193193
other direction:
194194
- All logr verbosity levels can be mapped 1:1 to their corresponding slog level
195195
by negating them.
196-
- Stack unwinding is done by the `slogr.SlogSink` and the resulting program
196+
- Stack unwinding is done by the `SlogSink` and the resulting program
197197
counter is passed to the `slog.Handler`.
198198
- Names added via `Logger.WithName` are gathered and recorded in an additional
199199
attribute with `logger` as key and the names separated by slash as value.
@@ -205,7 +205,7 @@ ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility
205205
with logr implementations without slog support is not important, then
206206
`slog.Valuer` is sufficient.
207207

208-
## Context support for slog
208+
### Context support for slog
209209

210210
Storing a logger in a `context.Context` is not supported by
211211
slog. `logr.NewContext` and `logr.FromContext` can be used with slog like this
@@ -214,13 +214,13 @@ to fill this gap:
214214
func HandlerFromContext(ctx context.Context) slog.Handler {
215215
logger, err := logr.FromContext(ctx)
216216
if err == nil {
217-
return slogr.NewSlogHandler(logger)
217+
return ToSlogHandler(logger)
218218
}
219219
return slog.Default().Handler()
220220
}
221221

222222
func ContextWithHandler(ctx context.Context, handler slog.Handler) context.Context {
223-
return logr.NewContext(ctx, slogr.NewLogr(handler))
223+
return logr.NewContext(ctx, FromSlogHandler(handler))
224224
}
225225

226226
The downside is that storing and retrieving a `slog.Handler` needs more

‎slogr/example/main.go renamed to ‎examples/slog/main.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828

2929
"github.com/go-logr/logr"
3030
"github.com/go-logr/logr/funcr"
31-
"github.com/go-logr/logr/slogr"
3231
)
3332

3433
type e struct {
@@ -62,7 +61,7 @@ func main() {
6261
Level: slog.Level(-1),
6362
}
6463
handler := slog.NewJSONHandler(os.Stderr, &opts)
65-
logrLogger := slogr.NewLogr(handler)
64+
logrLogger := logr.FromSlogHandler(handler)
6665
logrExample(logrLogger)
6766

6867
logrLogger = funcr.NewJSON(
@@ -72,7 +71,7 @@ func main() {
7271
LogTimestamp: true,
7372
Verbosity: 1,
7473
})
75-
slogLogger := slog.New(slogr.NewSlogHandler(logrLogger))
74+
slogLogger := slog.New(logr.ToSlogHandler(logrLogger))
7675
slogExample(slogLogger)
7776
}
7877

@@ -91,7 +90,7 @@ func logrExample(log logr.Logger) {
9190

9291
func slogExample(log *slog.Logger) {
9392
// There's no guarantee that this logs the right source code location.
94-
// It works for Go 1.21.0 by compensating in slogr.NewSlogHandler
93+
// It works for Go 1.21.0 by compensating in logr.ToSlogHandler
9594
// for the additional callers, but those might change.
9695
log = log.With("saved", "value")
9796
log.Info("1) hello", "val1", 1, "val2", map[string]int{"k": 1})

‎slogr/sloghandler.go renamed to ‎sloghandler.go

+16-17
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,16 @@ See the License for the specific language governing permissions and
1717
limitations under the License.
1818
*/
1919

20-
package slogr
20+
package logr
2121

2222
import (
2323
"context"
2424
"log/slog"
25-
26-
"github.com/go-logr/logr"
2725
)
2826

2927
type slogHandler struct {
3028
// May be nil, in which case all logs get discarded.
31-
sink logr.LogSink
29+
sink LogSink
3230
// Non-nil if sink is non-nil and implements SlogSink.
3331
slogSink SlogSink
3432

@@ -90,15 +88,15 @@ func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error {
9088
// are called by Handle, code in slog gets skipped.
9189
//
9290
// This offset currently (Go 1.21.0) works for calls through
93-
// slog.New(NewSlogHandler(...)). There's no guarantee that the call
91+
// slog.New(ToSlogHandler(...)). There's no guarantee that the call
9492
// chain won't change. Wrapping the handler will also break unwinding. It's
9593
// still better than not adjusting at all....
9694
//
97-
// This cannot be done when constructing the handler because NewLogr needs
95+
// This cannot be done when constructing the handler because FromSlogHandler needs
9896
// access to the original sink without this adjustment. A second copy would
9997
// work, but then WithAttrs would have to be called for both of them.
100-
func (l *slogHandler) sinkWithCallDepth() logr.LogSink {
101-
if sink, ok := l.sink.(logr.CallDepthLogSink); ok {
98+
func (l *slogHandler) sinkWithCallDepth() LogSink {
99+
if sink, ok := l.sink.(CallDepthLogSink); ok {
102100
return sink.WithCallDepth(2)
103101
}
104102
return l.sink
@@ -148,21 +146,22 @@ func (l *slogHandler) addGroupPrefix(name string) string {
148146

149147
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
150148
// It ensures that the result is >= 0. This is necessary because the result is
151-
// passed to a logr.LogSink and that API did not historically document whether
149+
// passed to a LogSink and that API did not historically document whether
152150
// levels could be negative or what that meant.
153151
//
154152
// Some example usage:
155-
// logrV0 := getMyLogger()
156-
// logrV2 := logrV0.V(2)
157-
// slogV2 := slog.New(slogr.NewSlogHandler(logrV2))
158-
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
159-
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
160-
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
153+
//
154+
// logrV0 := getMyLogger()
155+
// logrV2 := logrV0.V(2)
156+
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
157+
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
158+
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
159+
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
161160
func (l *slogHandler) levelFromSlog(level slog.Level) int {
162161
result := -level
163-
result += l.levelBias // in case the original logr.Logger had a V level
162+
result += l.levelBias // in case the original Logger had a V level
164163
if result < 0 {
165-
result = 0 // because logr.LogSink doesn't expect negative V levels
164+
result = 0 // because LogSink doesn't expect negative V levels
166165
}
167166
return int(result)
168167
}

‎slogr.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
/*
5+
Copyright 2023 The logr Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package logr
21+
22+
import (
23+
"context"
24+
"log/slog"
25+
)
26+
27+
// FromSlogHandler returns a Logger which writes to the slog.Handler.
28+
//
29+
// The logr verbosity level is mapped to slog levels such that V(0) becomes
30+
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
31+
func FromSlogHandler(handler slog.Handler) Logger {
32+
if handler, ok := handler.(*slogHandler); ok {
33+
if handler.sink == nil {
34+
return Discard()
35+
}
36+
return New(handler.sink).V(int(handler.levelBias))
37+
}
38+
return New(&slogSink{handler: handler})
39+
}
40+
41+
// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
42+
//
43+
// The returned logger writes all records with level >= slog.LevelError as
44+
// error log entries with LogSink.Error, regardless of the verbosity level of
45+
// the Logger:
46+
//
47+
// logger := <some Logger with 0 as verbosity level>
48+
// slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
49+
//
50+
// The level of all other records gets reduced by the verbosity
51+
// level of the Logger and the result is negated. If it happens
52+
// to be negative, then it gets replaced by zero because a LogSink
53+
// is not expected to handled negative levels:
54+
//
55+
// slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
56+
// slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
57+
// slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
58+
// slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
59+
func ToSlogHandler(logger Logger) slog.Handler {
60+
if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 {
61+
return sink.handler
62+
}
63+
64+
handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())}
65+
if slogSink, ok := handler.sink.(SlogSink); ok {
66+
handler.slogSink = slogSink
67+
}
68+
return handler
69+
}
70+
71+
// SlogSink is an optional interface that a LogSink can implement to support
72+
// logging through the slog.Logger or slog.Handler APIs better. It then should
73+
// also support special slog values like slog.Group. When used as a
74+
// slog.Handler, the advantages are:
75+
//
76+
// - stack unwinding gets avoided in favor of logging the pre-recorded PC,
77+
// as intended by slog
78+
// - proper grouping of key/value pairs via WithGroup
79+
// - verbosity levels > slog.LevelInfo can be recorded
80+
// - less overhead
81+
//
82+
// Both APIs (Logger and slog.Logger/Handler) then are supported equally
83+
// well. Developers can pick whatever API suits them better and/or mix
84+
// packages which use either API in the same binary with a common logging
85+
// implementation.
86+
//
87+
// This interface is necessary because the type implementing the LogSink
88+
// interface cannot also implement the slog.Handler interface due to the
89+
// different prototype of the common Enabled method.
90+
//
91+
// An implementation could support both interfaces in two different types, but then
92+
// additional interfaces would be needed to convert between those types in FromSlogHandler
93+
// and ToSlogHandler.
94+
type SlogSink interface {
95+
LogSink
96+
97+
Handle(ctx context.Context, record slog.Record) error
98+
WithAttrs(attrs []slog.Attr) SlogSink
99+
WithGroup(name string) SlogSink
100+
}

‎slogr/slogr.go

+11-65
Original file line numberDiff line numberDiff line change
@@ -23,86 +23,32 @@ limitations under the License.
2323
//
2424
// See the README in the top-level [./logr] package for a discussion of
2525
// interoperability.
26+
//
27+
// Deprecated: use the main logr package instead.
2628
package slogr
2729

2830
import (
29-
"context"
3031
"log/slog"
3132

3233
"github.com/go-logr/logr"
3334
)
3435

3536
// NewLogr returns a logr.Logger which writes to the slog.Handler.
3637
//
37-
// The logr verbosity level is mapped to slog levels such that V(0) becomes
38-
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
38+
// Deprecated: use [logr.FromSlogHandler] instead.
3939
func NewLogr(handler slog.Handler) logr.Logger {
40-
if handler, ok := handler.(*slogHandler); ok {
41-
if handler.sink == nil {
42-
return logr.Discard()
43-
}
44-
return logr.New(handler.sink).V(int(handler.levelBias))
45-
}
46-
return logr.New(&slogSink{handler: handler})
40+
return logr.FromSlogHandler(handler)
4741
}
4842

49-
// NewSlogHandler returns a slog.Handler which writes to the same sink as the logr.Logger.
50-
//
51-
// The returned logger writes all records with level >= slog.LevelError as
52-
// error log entries with LogSink.Error, regardless of the verbosity level of
53-
// the logr.Logger:
54-
//
55-
// logger := <some logr.Logger with 0 as verbosity level>
56-
// slog.New(NewSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
43+
// ToSlogHandler returns a slog.Handler which writes to the same sink as the logr.Logger.
5744
//
58-
// The level of all other records gets reduced by the verbosity
59-
// level of the logr.Logger and the result is negated. If it happens
60-
// to be negative, then it gets replaced by zero because a LogSink
61-
// is not expected to handled negative levels:
62-
//
63-
// slog.New(NewSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
64-
// slog.New(NewSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
65-
// slog.New(NewSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
66-
// slog.New(NewSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
67-
func NewSlogHandler(logger logr.Logger) slog.Handler {
68-
if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 {
69-
return sink.handler
70-
}
71-
72-
handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())}
73-
if slogSink, ok := handler.sink.(SlogSink); ok {
74-
handler.slogSink = slogSink
75-
}
76-
return handler
45+
// Deprecated: use [logr.ToSlogHandler] instead.
46+
func ToSlogHandler(logger logr.Logger) slog.Handler {
47+
return logr.ToSlogHandler(logger)
7748
}
7849

7950
// SlogSink is an optional interface that a LogSink can implement to support
80-
// logging through the slog.Logger or slog.Handler APIs better. It then should
81-
// also support special slog values like slog.Group. When used as a
82-
// slog.Handler, the advantages are:
51+
// logging through the slog.Logger or slog.Handler APIs better.
8352
//
84-
// - stack unwinding gets avoided in favor of logging the pre-recorded PC,
85-
// as intended by slog
86-
// - proper grouping of key/value pairs via WithGroup
87-
// - verbosity levels > slog.LevelInfo can be recorded
88-
// - less overhead
89-
//
90-
// Both APIs (logr.Logger and slog.Logger/Handler) then are supported equally
91-
// well. Developers can pick whatever API suits them better and/or mix
92-
// packages which use either API in the same binary with a common logging
93-
// implementation.
94-
//
95-
// This interface is necessary because the type implementing the LogSink
96-
// interface cannot also implement the slog.Handler interface due to the
97-
// different prototype of the common Enabled method.
98-
//
99-
// An implementation could support both interfaces in two different types, but then
100-
// additional interfaces would be needed to convert between those types in NewLogr
101-
// and NewSlogHandler.
102-
type SlogSink interface {
103-
logr.LogSink
104-
105-
Handle(ctx context.Context, record slog.Record) error
106-
WithAttrs(attrs []slog.Attr) SlogSink
107-
WithGroup(name string) SlogSink
108-
}
53+
// Deprecated: use [logr.SlogSink] instead.
54+
type SlogSink = logr.SlogSink

0 commit comments

Comments
 (0)
Please sign in to comment.