Skip to content

Commit 5c2e9b4

Browse files
committed
slog: Levels
This is the first commit for the new experimental package `slog`, providing structured, leveled logging. golang.org/x/exp/slog implements the draft proposal currently under discussion at golang/go. This commit defines Level and the related type AtomicLevel. Change-Id: I2825a926dab14ea66a62313ca2b6cd5c1ca04683 Reviewed-on: https://go-review.googlesource.com/c/exp/+/426020 Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 334a238 commit 5c2e9b4

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

slog/level.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package slog
6+
7+
import (
8+
"fmt"
9+
"math"
10+
"sync/atomic"
11+
)
12+
13+
// A Level is the importance or severity of a log event.
14+
// The higher the level, the less important or severe the event.
15+
type Level int
16+
17+
// The level numbers below don't really matter too much. Any system can map them
18+
// to another numbering scheme if it wishes. We picked them to satisfy two
19+
// constraints.
20+
//
21+
// First, we wanted to make it easy to work with verbosities instead of levels.
22+
// Since higher verbosities are less important, higher levels are as well.
23+
//
24+
// Second, we wanted some room between levels to accommodate schemes with named
25+
// levels between ours. For example, Google Cloud Logging defines a Notice level
26+
// between Info and Warn. Since there are only a few of these intermediate
27+
// levels, the gap between the numbers need not be large. We selected a gap of
28+
// 10, because the majority of humans have 10 fingers.
29+
//
30+
// The missing gap between Info and Debug has to do with verbosities again. It
31+
// is natural to think of verbosity 0 as Info, and then verbosity 1 is the
32+
// lowest level one would call Debug. The simple formula
33+
// level = InfoLevel + verbosity
34+
// then works well to map verbosities to levels. That is,
35+
//
36+
// Level(InfoLevel+0).String() == "INFO"
37+
// Level(InfoLevel+1).String() == "DEBUG"
38+
// Level(InfoLevel+2).String() == "DEBUG+1"
39+
//
40+
// and so on.
41+
42+
// Names for common levels.
43+
const (
44+
ErrorLevel Level = 10
45+
WarnLevel Level = 20
46+
InfoLevel Level = 30
47+
DebugLevel Level = 31
48+
)
49+
50+
// String returns a name for the level.
51+
// If the level has a name, then that name
52+
// in uppercase is returned.
53+
// If the level is between named values, then
54+
// an integer is appended to the uppercased name.
55+
// Examples:
56+
//
57+
// WarnLevel.String() => "WARN"
58+
// (WarnLevel-2).String() => "WARN-2"
59+
func (l Level) String() string {
60+
str := func(base string, val Level) string {
61+
if val == 0 {
62+
return base
63+
}
64+
return fmt.Sprintf("%s%+d", base, val)
65+
}
66+
67+
switch {
68+
case l <= 0:
69+
return fmt.Sprintf("!BADLEVEL(%d)", l)
70+
case l <= ErrorLevel:
71+
return str("ERROR", l-ErrorLevel)
72+
case l <= WarnLevel:
73+
return str("WARN", l-WarnLevel)
74+
case l <= InfoLevel:
75+
return str("INFO", l-InfoLevel)
76+
default:
77+
return str("DEBUG", l-DebugLevel)
78+
}
79+
}
80+
81+
// An AtomicLevel is Level that can be read and written safely by multiple
82+
// goroutines.
83+
// Use NewAtomicLevel to create one.
84+
type AtomicLevel struct {
85+
val atomic.Int64
86+
}
87+
88+
// NewAtomicLevel creates an AtomicLevel initialized to the given Level.
89+
func NewAtomicLevel(l Level) *AtomicLevel {
90+
var r AtomicLevel
91+
r.Set(l)
92+
return &r
93+
}
94+
95+
// Level returns r's level.
96+
// If r is nil, it returns the maximum level.
97+
func (r *AtomicLevel) Level() Level {
98+
if r == nil {
99+
return Level(math.MaxInt)
100+
}
101+
return Level(int(r.val.Load()))
102+
}
103+
104+
// Set sets r's level to l.
105+
func (r *AtomicLevel) Set(l Level) {
106+
r.val.Store(int64(l))
107+
}

slog/level_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package slog
6+
7+
import (
8+
"math"
9+
"testing"
10+
)
11+
12+
func TestLevelString(t *testing.T) {
13+
for _, test := range []struct {
14+
in Level
15+
want string
16+
}{
17+
{0, "!BADLEVEL(0)"},
18+
{ErrorLevel, "ERROR"},
19+
{ErrorLevel - 2, "ERROR-2"},
20+
{WarnLevel, "WARN"},
21+
{WarnLevel - 1, "WARN-1"},
22+
{InfoLevel, "INFO"},
23+
{InfoLevel - 3, "INFO-3"},
24+
{DebugLevel, "DEBUG"},
25+
{InfoLevel + 2, "DEBUG+1"},
26+
{-1, "!BADLEVEL(-1)"},
27+
} {
28+
got := test.in.String()
29+
if got != test.want {
30+
t.Errorf("%d: got %s, want %s", test.in, got, test.want)
31+
}
32+
}
33+
}
34+
35+
func TestAtomicLevel(t *testing.T) {
36+
var r *AtomicLevel
37+
if got, want := r.Level(), Level(math.MaxInt); got != want {
38+
t.Errorf("got %v, want %v", got, want)
39+
}
40+
r = NewAtomicLevel(WarnLevel)
41+
if got, want := r.Level(), WarnLevel; got != want {
42+
t.Errorf("got %v, want %v", got, want)
43+
}
44+
r.Set(InfoLevel)
45+
if got, want := r.Level(), InfoLevel; got != want {
46+
t.Errorf("got %v, want %v", got, want)
47+
}
48+
49+
}

0 commit comments

Comments
 (0)