Skip to content

Commit de9c53c

Browse files
committed
slog: both AtomicLevel and Level implement Leveler
Introduce the Leveler interface (only method: Level() Level) so that both AtomicLevel and Level can be used interchangeably. HandlerOptions.Level is now a Leveler. The default for HandlerOptions.Level is InfoLevel, meaning that Debug logs are disabled by default. This is also now the behavior of the default Handler, which is used if slog.{Log,Info,Debug,...} are called before installing a handler. It used to write everything to the default log.Logger; now it ignores any level below Info. Change-Id: I0538201cffacd101901179a01f262ae6a5d96797 Reviewed-on: https://go-review.googlesource.com/c/exp/+/431055 Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent df6207c commit de9c53c

File tree

5 files changed

+82
-34
lines changed

5 files changed

+82
-34
lines changed

slog/handler.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ type defaultHandler struct {
4545
attrs []Attr
4646
}
4747

48-
func (*defaultHandler) Enabled(Level) bool { return true }
48+
func (*defaultHandler) Enabled(l Level) bool {
49+
return l >= InfoLevel
50+
}
4951

5052
// Collect the level, attributes and message in a string and
5153
// write it with the default log.Logger.
@@ -75,9 +77,9 @@ type HandlerOptions struct {
7577
// "file:line".
7678
AddSource bool
7779

78-
// Ignore records with levels above Level.Level.
79-
// If nil, accept all levels.
80-
Level *AtomicLevel
80+
// Ignore records with levels below Level.Level().
81+
// The default is InfoLevel.
82+
Level Leveler
8183

8284
// If set, ReplaceAttr is called on each attribute of the message,
8385
// and the returned value is used instead of the original. If the returned
@@ -98,10 +100,14 @@ type commonHandler struct {
98100
w io.Writer
99101
}
100102

101-
// Enabled reports whether l is less than or equal to the
102-
// maximum level.
103+
// Enabled reports whether l is greater than or equal to the
104+
// minimum level.
103105
func (h *commonHandler) Enabled(l Level) bool {
104-
return l <= h.opts.Level.Level()
106+
minLevel := InfoLevel
107+
if h.opts.Level != nil {
108+
minLevel = h.opts.Level.Level()
109+
}
110+
return l >= minLevel
105111
}
106112

107113
func (h *commonHandler) with(as []Attr) *commonHandler {

slog/handler_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,32 @@ func upperCaseKey(a Attr) Attr {
122122
return a.WithKey(strings.ToUpper(a.Key()))
123123
}
124124

125+
func TestHandlerEnabled(t *testing.T) {
126+
atomicLevel := func(l Level) *AtomicLevel {
127+
var al AtomicLevel
128+
al.Set(l)
129+
return &al
130+
}
131+
132+
for _, test := range []struct {
133+
leveler Leveler
134+
want bool
135+
}{
136+
{nil, true},
137+
{WarnLevel, false},
138+
{&AtomicLevel{}, true}, // defaults to Info
139+
{atomicLevel(WarnLevel), false},
140+
{DebugLevel, true},
141+
{atomicLevel(DebugLevel), true},
142+
} {
143+
h := &commonHandler{opts: HandlerOptions{Level: test.leveler}}
144+
got := h.Enabled(InfoLevel)
145+
if got != test.want {
146+
t.Errorf("%v: got %t, want %t", test.leveler, got, test.want)
147+
}
148+
}
149+
}
150+
125151
const rfc3339Millis = "2006-01-02T15:04:05.000Z07:00"
126152

127153
func TestAppendTimeRFC3339(t *testing.T) {

slog/level.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package slog
66

77
import (
88
"fmt"
9-
"math"
109
"strconv"
1110
"sync/atomic"
1211
)
@@ -84,30 +83,37 @@ func (l Level) MarshalJSON() ([]byte, error) {
8483
return strconv.AppendQuote(nil, l.String()), nil
8584
}
8685

87-
// An AtomicLevel is Level that can be read and written safely by multiple
86+
// Level returns the receiver.
87+
// It implements Leveler.
88+
func (l Level) Level() Level { return l }
89+
90+
// An AtomicLevel is a Level that can be read and written safely by multiple
8891
// goroutines.
89-
// Use NewAtomicLevel to create one.
92+
// The default value of AtomicLevel is InfoLevel.
9093
type AtomicLevel struct {
9194
val atomic.Int64
9295
}
9396

94-
// NewAtomicLevel creates an AtomicLevel initialized to the given Level.
95-
func NewAtomicLevel(l Level) *AtomicLevel {
96-
var r AtomicLevel
97-
r.Set(l)
98-
return &r
97+
// Level returns r's level.
98+
func (a *AtomicLevel) Level() Level {
99+
return Level(int(a.val.Load()))
99100
}
100101

101-
// Level returns r's level.
102-
// If r is nil, it returns the maximum level.
103-
func (r *AtomicLevel) Level() Level {
104-
if r == nil {
105-
return Level(math.MaxInt)
106-
}
107-
return Level(int(r.val.Load()))
102+
// Set sets the receiver's level to l.
103+
func (a *AtomicLevel) Set(l Level) {
104+
a.val.Store(int64(l))
108105
}
109106

110-
// Set sets r's level to l.
111-
func (r *AtomicLevel) Set(l Level) {
112-
r.val.Store(int64(l))
107+
func (a *AtomicLevel) String() string {
108+
return fmt.Sprintf("AtomicLevel(%s)", a.Level())
109+
}
110+
111+
// A Leveler provides a Level value.
112+
//
113+
// As Level itself implements Leveler, clients typically supply
114+
// a Level value wherever a Leveler is needed, such as in HandlerOptions.
115+
// Clients who need to vary the level dynamically can provide a more complex
116+
// Leveler implementation such as *AtomicLevel.
117+
type Leveler interface {
118+
Level() Level
113119
}

slog/level_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package slog
66

77
import (
8-
"math"
98
"testing"
109
)
1110

@@ -33,16 +32,16 @@ func TestLevelString(t *testing.T) {
3332
}
3433

3534
func TestAtomicLevel(t *testing.T) {
36-
var r *AtomicLevel
37-
if got, want := r.Level(), Level(math.MaxInt); got != want {
35+
var al AtomicLevel
36+
if got, want := al.Level(), InfoLevel; got != want {
3837
t.Errorf("got %v, want %v", got, want)
3938
}
40-
r = NewAtomicLevel(WarnLevel)
41-
if got, want := r.Level(), WarnLevel; got != want {
39+
al.Set(WarnLevel)
40+
if got, want := al.Level(), WarnLevel; got != want {
4241
t.Errorf("got %v, want %v", got, want)
4342
}
44-
r.Set(InfoLevel)
45-
if got, want := r.Level(), InfoLevel; got != want {
43+
al.Set(InfoLevel)
44+
if got, want := al.Level(), InfoLevel; got != want {
4645
t.Errorf("got %v, want %v", got, want)
4746
}
4847

slog/logger_test.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@ func TestLogTextHandler(t *testing.T) {
2525

2626
check := func(want string) {
2727
t.Helper()
28-
want = "time=" + timeRE + " " + want
28+
if want != "" {
29+
want = "time=" + timeRE + " " + want
30+
}
2931
checkLogOutput(t, buf.String(), want)
3032
buf.Reset()
3133
}
3234

3335
l.Info("msg", "a", 1, "b", 2)
3436
check(`level=INFO msg=msg a=1 b=2`)
3537

38+
// By default, debug messages are not printed.
3639
l.Debug("bg", Int("a", 1), "b", 2)
37-
check(`level=DEBUG msg=bg a=1 b=2`)
40+
check("")
3841

3942
l.Warn("w", Duration("dur", 3*time.Second))
4043
check(`level=WARN msg=w dur=3s`)
@@ -58,6 +61,14 @@ func TestConnections(t *testing.T) {
5861
Info("msg", "a", 1)
5962
checkLogOutput(t, logbuf.String(),
6063
`\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} logger_test.go:\d\d: INFO a=1 msg`)
64+
logbuf.Reset()
65+
Warn("msg", "b", 2)
66+
checkLogOutput(t, logbuf.String(),
67+
`\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} logger_test.go:\d\d: WARN b=2 msg`)
68+
// Levels below Info are not printed.
69+
logbuf.Reset()
70+
Debug("msg", "c", 3)
71+
checkLogOutput(t, logbuf.String(), "")
6172

6273
// Once slog.SetDefault is called, the direction is reversed: the default
6374
// log.Logger's output goes through the handler.

0 commit comments

Comments
 (0)