Skip to content

Commit df6207c

Browse files
committed
slog: rethink levels
We want the default level to be Info, so make it zero. Then rejigger the level numbers to match OpenTelemetry: - Reverse the direction, so larger numbers are more severe. - Reduce the gaps between named levels from 10 to 4. There is still a nice mapping to verbosities: negation. By making the zero level valid, we lose a level value that means "no level." We could reinstate it by picking an extreme value, like math.MinInt. Or we could just omit it; perhaps it's not needed. Change-Id: I0f06418b7de79fde0c172e5c9d88c707fac74601 Reviewed-on: https://go-review.googlesource.com/c/exp/+/435896 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 4a82f89 commit df6207c

File tree

5 files changed

+53
-61
lines changed

5 files changed

+53
-61
lines changed

slog/handler.go

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ type Handler interface {
3232
// Handle handles the Record.
3333
// Handle methods that produce output should observe the following rules:
3434
// - If r.Time() is the zero time, ignore the time.
35-
// - If r.Level() is Level(0), ignore the level.
3635
// - If an Attr's key is the empty string, ignore the Attr.
3736
Handle(r Record) error
3837

@@ -53,10 +52,8 @@ func (*defaultHandler) Enabled(Level) bool { return true }
5352
// Let the log.Logger handle time and file/line.
5453
func (h *defaultHandler) Handle(r Record) error {
5554
var b strings.Builder
56-
if r.Level() > 0 {
57-
b.WriteString(r.Level().String())
58-
b.WriteByte(' ')
59-
}
55+
b.WriteString(r.Level().String())
56+
b.WriteByte(' ')
6057
r.Attrs(func(a Attr) {
6158
fmt.Fprint(&b, a) // Attr.Format will print key=value
6259
b.WriteByte(' ')
@@ -144,15 +141,13 @@ func (h *commonHandler) handle(r Record) error {
144141
}
145142
}
146143
// level
147-
if r.Level() != 0 {
148-
key := "level"
149-
val := r.Level()
150-
if rep == nil {
151-
state.appendKey(key)
152-
state.appendString(val.String())
153-
} else {
154-
state.appendAttr(Any(key, val))
155-
}
144+
key := "level"
145+
val := r.Level()
146+
if rep == nil {
147+
state.appendKey(key)
148+
state.appendString(val.String())
149+
} else {
150+
state.appendAttr(Any(key, val))
156151
}
157152
// source
158153
if h.opts.AddSource {
@@ -173,14 +168,13 @@ func (h *commonHandler) handle(r Record) error {
173168
}
174169
}
175170
}
176-
// message
177-
key := "msg"
178-
val := r.Message()
171+
key = "msg"
172+
msg := r.Message()
179173
if rep == nil {
180174
state.appendKey(key)
181-
state.appendString(val)
175+
state.appendString(msg)
182176
} else {
183-
state.appendAttr(String(key, val))
177+
state.appendAttr(String(key, msg))
184178
}
185179
// preformatted Attrs
186180
if len(h.preformattedAttrs) > 0 {

slog/level.go

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,36 @@ import (
1616
type Level int
1717

1818
// The level numbers below don't really matter too much. Any system can map them
19-
// to another numbering scheme if it wishes. We picked them to satisfy two
19+
// to another numbering scheme if it wishes. We picked them to satisfy three
2020
// constraints.
2121
//
22-
// First, we wanted to make it easy to work with verbosities instead of levels.
23-
// Since higher verbosities are less important, higher levels are as well.
22+
// First, we wanted the default level to be Info, Since Levels are ints, Info is
23+
// the default value for int, zero.
2424
//
25-
// Second, we wanted some room between levels to accommodate schemes with named
25+
// Second, we wanted to make it easy to work with verbosities instead of levels.
26+
// Verbosities start at 0 corresponding to Info, and larger values are less severe
27+
// Negating a verbosity converts it into a Level.
28+
//
29+
// Third, we wanted some room between levels to accommodate schemes with named
2630
// levels between ours. For example, Google Cloud Logging defines a Notice level
2731
// between Info and Warn. Since there are only a few of these intermediate
28-
// levels, the gap between the numbers need not be large. We selected a gap of
29-
// 10, because the majority of humans have 10 fingers.
30-
//
31-
// The missing gap between Info and Debug has to do with verbosities again. It
32-
// is natural to think of verbosity 0 as Info, and then verbosity 1 is the
33-
// lowest level one would call Debug. The simple formula
34-
// level = InfoLevel + verbosity
35-
// then works well to map verbosities to levels. That is,
32+
// levels, the gap between the numbers need not be large. Our gap of 4 matches
33+
// OpenTelemetry's mapping. Subtracting 9 from an OpenTelemetry level in the
34+
// DEBUG, INFO, WARN and ERROR ranges converts it to the corresponding slog
35+
// Level range. OpenTelemetry also has the names TRACE and FATAL, which slog
36+
// does not. But those OpenTelemetry levels can still be represented as slog
37+
// Levels by using the appropriate integers.
3638
//
37-
// Level(InfoLevel+0).String() == "INFO"
38-
// Level(InfoLevel+1).String() == "DEBUG"
39-
// Level(InfoLevel+2).String() == "DEBUG+1"
39+
// The lack of a gap between Debug and Info doesn't follow the pattern.
40+
// It makes sense, though, that the first negative number is the start
41+
// of the Debug range.
4042
//
41-
// and so on.
42-
4343
// Names for common levels.
4444
const (
45-
ErrorLevel Level = 10
46-
WarnLevel Level = 20
47-
InfoLevel Level = 30
48-
DebugLevel Level = 31
45+
DebugLevel Level = -1
46+
InfoLevel Level = 0
47+
WarnLevel Level = 4
48+
ErrorLevel Level = 8
4949
)
5050

5151
// String returns a name for the level.
@@ -66,16 +66,14 @@ func (l Level) String() string {
6666
}
6767

6868
switch {
69-
case l <= 0:
70-
return fmt.Sprintf("!BADLEVEL(%d)", l)
71-
case l <= ErrorLevel:
72-
return str("ERROR", l-ErrorLevel)
73-
case l <= WarnLevel:
69+
case l <= DebugLevel:
70+
return str("DEBUG", l-DebugLevel)
71+
case l < WarnLevel:
72+
return str("INFO", l)
73+
case l < ErrorLevel:
7474
return str("WARN", l-WarnLevel)
75-
case l <= InfoLevel:
76-
return str("INFO", l-InfoLevel)
7775
default:
78-
return str("DEBUG", l-DebugLevel)
76+
return str("ERROR", l-ErrorLevel)
7977
}
8078
}
8179

slog/level_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ func TestLevelString(t *testing.T) {
1414
in Level
1515
want string
1616
}{
17-
{0, "!BADLEVEL(0)"},
17+
{0, "INFO"},
1818
{ErrorLevel, "ERROR"},
19-
{ErrorLevel - 2, "ERROR-2"},
19+
{ErrorLevel + 2, "ERROR+2"},
20+
{ErrorLevel - 2, "WARN+2"},
2021
{WarnLevel, "WARN"},
21-
{WarnLevel - 1, "WARN-1"},
22+
{WarnLevel - 1, "INFO+3"},
2223
{InfoLevel, "INFO"},
23-
{InfoLevel - 3, "INFO-3"},
24+
{InfoLevel - 3, "DEBUG-2"},
2425
{DebugLevel, "DEBUG"},
25-
{InfoLevel + 2, "DEBUG+1"},
26-
{-1, "!BADLEVEL(-1)"},
26+
{DebugLevel - 2, "DEBUG-2"},
2727
} {
2828
got := test.in.String()
2929
if got != test.want {

slog/logger_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ func TestLogTextHandler(t *testing.T) {
4242
l.Error("bad", io.EOF, "a", 1)
4343
check(`level=ERROR msg=bad a=1 err=EOF`)
4444

45-
l.Log(WarnLevel-1, "w", Int("a", 1), String("b", "two"))
46-
check(`level=WARN-1 msg=w a=1 b=two`)
45+
l.Log(WarnLevel+1, "w", Int("a", 1), String("b", "two"))
46+
check(`level=WARN\+1 msg=w a=1 b=two`)
4747

48-
l.LogAttrs(InfoLevel-1, "a b c", Int("a", 1), String("b", "two"))
49-
check(`level=INFO-1 msg="a b c" a=1 b=two`)
48+
l.LogAttrs(InfoLevel+1, "a b c", Int("a", 1), String("b", "two"))
49+
check(`level=INFO\+1 msg="a b c" a=1 b=two`)
5050
}
5151

5252
func TestConnections(t *testing.T) {

slog/text_handler_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,14 @@ func TestTextHandlerPreformatted(t *testing.T) {
162162
var buf bytes.Buffer
163163
var h Handler = NewTextHandler(&buf)
164164
h = h.With([]Attr{Duration("dur", time.Minute), Bool("b", true)})
165-
// Also test omitting time and level.
166-
r := NewRecord(time.Time{}, 0, "m", 0)
165+
// Also test omitting time.
166+
r := NewRecord(time.Time{}, 0 /* 0 Level is INFO */, "m", 0)
167167
r.AddAttrs(Int("a", 1))
168168
if err := h.Handle(r); err != nil {
169169
t.Fatal(err)
170170
}
171171
got := strings.TrimSuffix(buf.String(), "\n")
172-
want := `msg=m dur=1m0s b=true a=1`
172+
want := `level=INFO msg=m dur=1m0s b=true a=1`
173173
if got != want {
174174
t.Errorf("got %s, want %s", got, want)
175175
}

0 commit comments

Comments
 (0)