Codebase list golang-github-go-kit-kit / fb98706
log/experimental_level: mv to level Peter Bourgon 7 years ago
10 changed file(s) with 561 addition(s) and 561 deletion(s). Raw diff Collapse all Expand all
+0
-72
log/experimental_level/benchmark_test.go less more
0 package level_test
1
2 import (
3 "io/ioutil"
4 "testing"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/log/experimental_level"
8 )
9
10 func Benchmark(b *testing.B) {
11 contexts := []struct {
12 name string
13 context func(log.Logger) log.Logger
14 }{
15 {"NoContext", func(l log.Logger) log.Logger {
16 return l
17 }},
18 {"TimeContext", func(l log.Logger) log.Logger {
19 return log.NewContext(l).With("time", log.DefaultTimestampUTC)
20 }},
21 {"CallerContext", func(l log.Logger) log.Logger {
22 return log.NewContext(l).With("caller", log.DefaultCaller)
23 }},
24 {"TimeCallerReqIDContext", func(l log.Logger) log.Logger {
25 return log.NewContext(l).With("time", log.DefaultTimestampUTC, "caller", log.DefaultCaller, "reqID", 29)
26 }},
27 }
28
29 loggers := []struct {
30 name string
31 logger log.Logger
32 }{
33 {"Nop", log.NewNopLogger()},
34 {"Logfmt", log.NewLogfmtLogger(ioutil.Discard)},
35 {"JSON", log.NewJSONLogger(ioutil.Discard)},
36 }
37
38 filters := []struct {
39 name string
40 filter func(log.Logger) log.Logger
41 }{
42 {"Baseline", func(l log.Logger) log.Logger {
43 return l
44 }},
45 {"DisallowedLevel", func(l log.Logger) log.Logger {
46 return level.NewFilter(l, level.AllowInfo())
47 }},
48 {"AllowedLevel", func(l log.Logger) log.Logger {
49 return level.NewFilter(l, level.AllowAll())
50 }},
51 }
52
53 for _, c := range contexts {
54 b.Run(c.name, func(b *testing.B) {
55 for _, f := range filters {
56 b.Run(f.name, func(b *testing.B) {
57 for _, l := range loggers {
58 b.Run(l.name, func(b *testing.B) {
59 logger := c.context(f.filter(l.logger))
60 b.ResetTimer()
61 b.ReportAllocs()
62 for i := 0; i < b.N; i++ {
63 level.Debug(logger).Log("foo", "bar")
64 }
65 })
66 }
67 })
68 }
69 })
70 }
71 }
+0
-24
log/experimental_level/doc.go less more
0 // Package level is an EXPERIMENTAL levelled logging package. The API will
1 // definitely have breaking changes and may be deleted altogether. Be warned!
2 //
3 // To use the level package, create a logger as per normal in your func main,
4 // and wrap it with level.NewFilter.
5 //
6 // var logger log.Logger
7 // logger = log.NewLogfmtLogger(os.Stderr)
8 // logger = level.NewFilter(logger, level.AllowInfoAndAbove()) // <--
9 // logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
10 //
11 // Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
12 // helper methods to emit leveled log events.
13 //
14 // logger.Log("foo", "bar") // as normal, no level
15 // level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get())
16 // if value > 100 {
17 // level.Error(logger).Log("value", value)
18 // }
19 //
20 // NewFilter allows precise control over what happens when a log event is
21 // emitted without a level key, or if a squelched level is used. Check the
22 // Option functions for details.
23 package level
+0
-25
log/experimental_level/example_test.go less more
0 package level_test
1
2 import (
3 "errors"
4 "os"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/log/experimental_level"
8 )
9
10 func Example_basic() {
11 // setup logger with level filter
12 logger := log.NewLogfmtLogger(os.Stdout)
13 logger = level.NewFilter(logger, level.AllowInfo())
14 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
15
16 // use level helpers to log at different levels
17 level.Error(logger).Log("err", errors.New("bad data"))
18 level.Info(logger).Log("event", "data saved")
19 level.Debug(logger).Log("next item", 17) // filtered
20
21 // Output:
22 // level=error caller=example_test.go:18 err="bad data"
23 // level=info caller=example_test.go:19 event="data saved"
24 }
+0
-205
log/experimental_level/level.go less more
0 package level
1
2 import "github.com/go-kit/kit/log"
3
4 // Error returns a logger that includes a Key/ErrorValue pair.
5 func Error(logger log.Logger) log.Logger {
6 return log.NewContext(logger).WithPrefix(Key(), ErrorValue())
7 }
8
9 // Warn returns a logger that includes a Key/WarnValue pair.
10 func Warn(logger log.Logger) log.Logger {
11 return log.NewContext(logger).WithPrefix(Key(), WarnValue())
12 }
13
14 // Info returns a logger that includes a Key/InfoValue pair.
15 func Info(logger log.Logger) log.Logger {
16 return log.NewContext(logger).WithPrefix(Key(), InfoValue())
17 }
18
19 // Debug returns a logger that includes a Key/DebugValue pair.
20 func Debug(logger log.Logger) log.Logger {
21 return log.NewContext(logger).WithPrefix(Key(), DebugValue())
22 }
23
24 // NewFilter wraps next and implements level filtering. See the commentary on
25 // the Option functions for a detailed description of how to configure levels.
26 // If no options are provided, all leveled log events created with Debug,
27 // Info, Warn or Error helper methods are squelched and non-leveled log
28 // events are passed to next unmodified.
29 func NewFilter(next log.Logger, options ...Option) log.Logger {
30 l := &logger{
31 next: next,
32 }
33 for _, option := range options {
34 option(l)
35 }
36 return l
37 }
38
39 type logger struct {
40 next log.Logger
41 allowed level
42 squelchNoLevel bool
43 errNotAllowed error
44 errNoLevel error
45 }
46
47 func (l *logger) Log(keyvals ...interface{}) error {
48 var hasLevel, levelAllowed bool
49 for i := 1; i < len(keyvals); i += 2 {
50 if v, ok := keyvals[i].(*levelValue); ok {
51 hasLevel = true
52 levelAllowed = l.allowed&v.level != 0
53 break
54 }
55 }
56 if !hasLevel && l.squelchNoLevel {
57 return l.errNoLevel
58 }
59 if hasLevel && !levelAllowed {
60 return l.errNotAllowed
61 }
62 return l.next.Log(keyvals...)
63 }
64
65 // Option sets a parameter for the leveled logger.
66 type Option func(*logger)
67
68 // AllowAll is an alias for AllowDebug.
69 func AllowAll() Option {
70 return AllowDebug()
71 }
72
73 // AllowDebug allows error, warn, info and debug level log events to pass.
74 func AllowDebug() Option {
75 return allowed(levelError | levelWarn | levelInfo | levelDebug)
76 }
77
78 // AllowInfo allows error, warn and info level log events to pass.
79 func AllowInfo() Option {
80 return allowed(levelError | levelWarn | levelInfo)
81 }
82
83 // AllowWarn allows error and warn level log events to pass.
84 func AllowWarn() Option {
85 return allowed(levelError | levelWarn)
86 }
87
88 // AllowError allows only error level log events to pass.
89 func AllowError() Option {
90 return allowed(levelError)
91 }
92
93 // AllowNone allows no leveled log events to pass.
94 func AllowNone() Option {
95 return allowed(0)
96 }
97
98 func allowed(allowed level) Option {
99 return func(l *logger) { l.allowed = allowed }
100 }
101
102 // ErrNotAllowed sets the error to return from Log when it squelches a log
103 // event disallowed by the configured Allow[Level] option. By default,
104 // ErrNotAllowed is nil; in this case the log event is squelched with no
105 // error.
106 func ErrNotAllowed(err error) Option {
107 return func(l *logger) { l.errNotAllowed = err }
108 }
109
110 // SquelchNoLevel instructs Log to squelch log events with no level, so that
111 // they don't proceed through to the wrapped logger. If SquelchNoLevel is set
112 // to true and a log event is squelched in this way, the error value
113 // configured with ErrNoLevel is returned to the caller.
114 func SquelchNoLevel(squelch bool) Option {
115 return func(l *logger) { l.squelchNoLevel = squelch }
116 }
117
118 // ErrNoLevel sets the error to return from Log when it squelches a log event
119 // with no level. By default, ErrNoLevel is nil; in this case the log event is
120 // squelched with no error.
121 func ErrNoLevel(err error) Option {
122 return func(l *logger) { l.errNoLevel = err }
123 }
124
125 // NewInjector wraps next and returns a logger that adds a Key/level pair to
126 // the beginning of log events that don't already contain a level. In effect,
127 // this gives a default level to logs without a level.
128 func NewInjector(next log.Logger, level Value) log.Logger {
129 return &injector{
130 next: next,
131 level: level,
132 }
133 }
134
135 type injector struct {
136 next log.Logger
137 level interface{}
138 }
139
140 func (l *injector) Log(keyvals ...interface{}) error {
141 for i := 1; i < len(keyvals); i += 2 {
142 if _, ok := keyvals[i].(*levelValue); ok {
143 return l.next.Log(keyvals...)
144 }
145 }
146 kvs := make([]interface{}, len(keyvals)+2)
147 kvs[0], kvs[1] = key, l.level
148 copy(kvs[2:], keyvals)
149 return l.next.Log(kvs...)
150 }
151
152 // Value is the interface that each of the canonical level values implement.
153 // It contains unexported methods that prevent types from other packages from
154 // implementing it and guaranteeing that NewFilter can distinguish the levels
155 // defined in this package from all other values.
156 type Value interface {
157 String() string
158 levelVal()
159 }
160
161 // Key returns the unique key added to log events by the loggers in this
162 // package.
163 func Key() interface{} { return key }
164
165 // ErrorValue returns the unique value added to log events by Error.
166 func ErrorValue() Value { return errorValue }
167
168 // WarnValue returns the unique value added to log events by Warn.
169 func WarnValue() Value { return warnValue }
170
171 // InfoValue returns the unique value added to log events by Info.
172 func InfoValue() Value { return infoValue }
173
174 // DebugValue returns the unique value added to log events by Warn.
175 func DebugValue() Value { return debugValue }
176
177 var (
178 // key is of type interfae{} so that it allocates once during package
179 // initialization and avoids allocating every type the value is added to a
180 // []interface{} later.
181 key interface{} = "level"
182
183 errorValue = &levelValue{level: levelError, name: "error"}
184 warnValue = &levelValue{level: levelWarn, name: "warn"}
185 infoValue = &levelValue{level: levelInfo, name: "info"}
186 debugValue = &levelValue{level: levelDebug, name: "debug"}
187 )
188
189 type level byte
190
191 const (
192 levelDebug level = 1 << iota
193 levelInfo
194 levelWarn
195 levelError
196 )
197
198 type levelValue struct {
199 name string
200 level
201 }
202
203 func (v *levelValue) String() string { return v.name }
204 func (v *levelValue) levelVal() {}
+0
-235
log/experimental_level/level_test.go less more
0 package level_test
1
2 import (
3 "bytes"
4 "errors"
5 "io"
6 "strings"
7 "testing"
8
9 "github.com/go-kit/kit/log"
10 "github.com/go-kit/kit/log/experimental_level"
11 )
12
13 func TestVariousLevels(t *testing.T) {
14 testCases := []struct {
15 name string
16 allowed level.Option
17 want string
18 }{
19 {
20 "AllowAll",
21 level.AllowAll(),
22 strings.Join([]string{
23 `{"level":"debug","this is":"debug log"}`,
24 `{"level":"info","this is":"info log"}`,
25 `{"level":"warn","this is":"warn log"}`,
26 `{"level":"error","this is":"error log"}`,
27 }, "\n"),
28 },
29 {
30 "AllowDebug",
31 level.AllowDebug(),
32 strings.Join([]string{
33 `{"level":"debug","this is":"debug log"}`,
34 `{"level":"info","this is":"info log"}`,
35 `{"level":"warn","this is":"warn log"}`,
36 `{"level":"error","this is":"error log"}`,
37 }, "\n"),
38 },
39 {
40 "AllowDebug",
41 level.AllowInfo(),
42 strings.Join([]string{
43 `{"level":"info","this is":"info log"}`,
44 `{"level":"warn","this is":"warn log"}`,
45 `{"level":"error","this is":"error log"}`,
46 }, "\n"),
47 },
48 {
49 "AllowWarn",
50 level.AllowWarn(),
51 strings.Join([]string{
52 `{"level":"warn","this is":"warn log"}`,
53 `{"level":"error","this is":"error log"}`,
54 }, "\n"),
55 },
56 {
57 "AllowError",
58 level.AllowError(),
59 strings.Join([]string{
60 `{"level":"error","this is":"error log"}`,
61 }, "\n"),
62 },
63 {
64 "AllowNone",
65 level.AllowNone(),
66 ``,
67 },
68 }
69
70 for _, tc := range testCases {
71 t.Run(tc.name, func(t *testing.T) {
72 var buf bytes.Buffer
73 logger := level.NewFilter(log.NewJSONLogger(&buf), tc.allowed)
74
75 level.Debug(logger).Log("this is", "debug log")
76 level.Info(logger).Log("this is", "info log")
77 level.Warn(logger).Log("this is", "warn log")
78 level.Error(logger).Log("this is", "error log")
79
80 if want, have := tc.want, strings.TrimSpace(buf.String()); want != have {
81 t.Errorf("\nwant:\n%s\nhave:\n%s", want, have)
82 }
83 })
84 }
85 }
86
87 func TestErrNotAllowed(t *testing.T) {
88 myError := errors.New("squelched!")
89 opts := []level.Option{
90 level.AllowWarn(),
91 level.ErrNotAllowed(myError),
92 }
93 logger := level.NewFilter(log.NewNopLogger(), opts...)
94
95 if want, have := myError, level.Info(logger).Log("foo", "bar"); want != have {
96 t.Errorf("want %#+v, have %#+v", want, have)
97 }
98
99 if want, have := error(nil), level.Warn(logger).Log("foo", "bar"); want != have {
100 t.Errorf("want %#+v, have %#+v", want, have)
101 }
102 }
103
104 func TestErrNoLevel(t *testing.T) {
105 myError := errors.New("no level specified")
106
107 var buf bytes.Buffer
108 opts := []level.Option{
109 level.SquelchNoLevel(true),
110 level.ErrNoLevel(myError),
111 }
112 logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
113
114 if want, have := myError, logger.Log("foo", "bar"); want != have {
115 t.Errorf("want %v, have %v", want, have)
116 }
117 if want, have := ``, strings.TrimSpace(buf.String()); want != have {
118 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
119 }
120 }
121
122 func TestAllowNoLevel(t *testing.T) {
123 var buf bytes.Buffer
124 opts := []level.Option{
125 level.SquelchNoLevel(false),
126 level.ErrNoLevel(errors.New("I should never be returned!")),
127 }
128 logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
129
130 if want, have := error(nil), logger.Log("foo", "bar"); want != have {
131 t.Errorf("want %v, have %v", want, have)
132 }
133 if want, have := `{"foo":"bar"}`, strings.TrimSpace(buf.String()); want != have {
134 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
135 }
136 }
137
138 func TestLevelContext(t *testing.T) {
139 var buf bytes.Buffer
140
141 // Wrapping the level logger with a context allows users to use
142 // log.DefaultCaller as per normal.
143 var logger log.Logger
144 logger = log.NewLogfmtLogger(&buf)
145 logger = level.NewFilter(logger, level.AllowAll())
146 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
147
148 level.Info(logger).Log("foo", "bar")
149 if want, have := `level=info caller=level_test.go:149 foo=bar`, strings.TrimSpace(buf.String()); want != have {
150 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
151 }
152 }
153
154 func TestContextLevel(t *testing.T) {
155 var buf bytes.Buffer
156
157 // Wrapping a context with the level logger still works, but requires users
158 // to specify a higher callstack depth value.
159 var logger log.Logger
160 logger = log.NewLogfmtLogger(&buf)
161 logger = log.NewContext(logger).With("caller", log.Caller(5))
162 logger = level.NewFilter(logger, level.AllowAll())
163
164 level.Info(logger).Log("foo", "bar")
165 if want, have := `caller=level_test.go:165 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
166 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
167 }
168 }
169
170 func TestLevelFormatting(t *testing.T) {
171 testCases := []struct {
172 name string
173 format func(io.Writer) log.Logger
174 output string
175 }{
176 {
177 name: "logfmt",
178 format: log.NewLogfmtLogger,
179 output: `level=info foo=bar`,
180 },
181 {
182 name: "JSON",
183 format: log.NewJSONLogger,
184 output: `{"foo":"bar","level":"info"}`,
185 },
186 }
187
188 for _, tc := range testCases {
189 t.Run(tc.name, func(t *testing.T) {
190 var buf bytes.Buffer
191
192 logger := tc.format(&buf)
193 level.Info(logger).Log("foo", "bar")
194 if want, have := tc.output, strings.TrimSpace(buf.String()); want != have {
195 t.Errorf("\nwant: '%s'\nhave '%s'", want, have)
196 }
197 })
198 }
199 }
200
201 func TestInjector(t *testing.T) {
202 var (
203 output []interface{}
204 logger log.Logger
205 )
206
207 logger = log.LoggerFunc(func(keyvals ...interface{}) error {
208 output = keyvals
209 return nil
210 })
211 logger = level.NewInjector(logger, level.InfoValue())
212
213 logger.Log("foo", "bar")
214 if got, want := len(output), 4; got != want {
215 t.Errorf("missing level not injected: got len==%d, want len==%d", got, want)
216 }
217 if got, want := output[0], level.Key(); got != want {
218 t.Errorf("wrong level key: got %#v, want %#v", got, want)
219 }
220 if got, want := output[1], level.InfoValue(); got != want {
221 t.Errorf("wrong level value: got %#v, want %#v", got, want)
222 }
223
224 level.Error(logger).Log("foo", "bar")
225 if got, want := len(output), 4; got != want {
226 t.Errorf("leveled record modified: got len==%d, want len==%d", got, want)
227 }
228 if got, want := output[0], level.Key(); got != want {
229 t.Errorf("wrong level key: got %#v, want %#v", got, want)
230 }
231 if got, want := output[1], level.ErrorValue(); got != want {
232 t.Errorf("wrong level value: got %#v, want %#v", got, want)
233 }
234 }
0 package level_test
1
2 import (
3 "io/ioutil"
4 "testing"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/log/level"
8 )
9
10 func Benchmark(b *testing.B) {
11 contexts := []struct {
12 name string
13 context func(log.Logger) log.Logger
14 }{
15 {"NoContext", func(l log.Logger) log.Logger {
16 return l
17 }},
18 {"TimeContext", func(l log.Logger) log.Logger {
19 return log.NewContext(l).With("time", log.DefaultTimestampUTC)
20 }},
21 {"CallerContext", func(l log.Logger) log.Logger {
22 return log.NewContext(l).With("caller", log.DefaultCaller)
23 }},
24 {"TimeCallerReqIDContext", func(l log.Logger) log.Logger {
25 return log.NewContext(l).With("time", log.DefaultTimestampUTC, "caller", log.DefaultCaller, "reqID", 29)
26 }},
27 }
28
29 loggers := []struct {
30 name string
31 logger log.Logger
32 }{
33 {"Nop", log.NewNopLogger()},
34 {"Logfmt", log.NewLogfmtLogger(ioutil.Discard)},
35 {"JSON", log.NewJSONLogger(ioutil.Discard)},
36 }
37
38 filters := []struct {
39 name string
40 filter func(log.Logger) log.Logger
41 }{
42 {"Baseline", func(l log.Logger) log.Logger {
43 return l
44 }},
45 {"DisallowedLevel", func(l log.Logger) log.Logger {
46 return level.NewFilter(l, level.AllowInfo())
47 }},
48 {"AllowedLevel", func(l log.Logger) log.Logger {
49 return level.NewFilter(l, level.AllowAll())
50 }},
51 }
52
53 for _, c := range contexts {
54 b.Run(c.name, func(b *testing.B) {
55 for _, f := range filters {
56 b.Run(f.name, func(b *testing.B) {
57 for _, l := range loggers {
58 b.Run(l.name, func(b *testing.B) {
59 logger := c.context(f.filter(l.logger))
60 b.ResetTimer()
61 b.ReportAllocs()
62 for i := 0; i < b.N; i++ {
63 level.Debug(logger).Log("foo", "bar")
64 }
65 })
66 }
67 })
68 }
69 })
70 }
71 }
0 // Package level is an EXPERIMENTAL levelled logging package. The API will
1 // definitely have breaking changes and may be deleted altogether. Be warned!
2 //
3 // To use the level package, create a logger as per normal in your func main,
4 // and wrap it with level.NewFilter.
5 //
6 // var logger log.Logger
7 // logger = log.NewLogfmtLogger(os.Stderr)
8 // logger = level.NewFilter(logger, level.AllowInfoAndAbove()) // <--
9 // logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
10 //
11 // Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
12 // helper methods to emit leveled log events.
13 //
14 // logger.Log("foo", "bar") // as normal, no level
15 // level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get())
16 // if value > 100 {
17 // level.Error(logger).Log("value", value)
18 // }
19 //
20 // NewFilter allows precise control over what happens when a log event is
21 // emitted without a level key, or if a squelched level is used. Check the
22 // Option functions for details.
23 package level
0 package level_test
1
2 import (
3 "errors"
4 "os"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/log/level"
8 )
9
10 func Example_basic() {
11 // setup logger with level filter
12 logger := log.NewLogfmtLogger(os.Stdout)
13 logger = level.NewFilter(logger, level.AllowInfo())
14 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
15
16 // use level helpers to log at different levels
17 level.Error(logger).Log("err", errors.New("bad data"))
18 level.Info(logger).Log("event", "data saved")
19 level.Debug(logger).Log("next item", 17) // filtered
20
21 // Output:
22 // level=error caller=example_test.go:18 err="bad data"
23 // level=info caller=example_test.go:19 event="data saved"
24 }
0 package level
1
2 import "github.com/go-kit/kit/log"
3
4 // Error returns a logger that includes a Key/ErrorValue pair.
5 func Error(logger log.Logger) log.Logger {
6 return log.NewContext(logger).WithPrefix(Key(), ErrorValue())
7 }
8
9 // Warn returns a logger that includes a Key/WarnValue pair.
10 func Warn(logger log.Logger) log.Logger {
11 return log.NewContext(logger).WithPrefix(Key(), WarnValue())
12 }
13
14 // Info returns a logger that includes a Key/InfoValue pair.
15 func Info(logger log.Logger) log.Logger {
16 return log.NewContext(logger).WithPrefix(Key(), InfoValue())
17 }
18
19 // Debug returns a logger that includes a Key/DebugValue pair.
20 func Debug(logger log.Logger) log.Logger {
21 return log.NewContext(logger).WithPrefix(Key(), DebugValue())
22 }
23
24 // NewFilter wraps next and implements level filtering. See the commentary on
25 // the Option functions for a detailed description of how to configure levels.
26 // If no options are provided, all leveled log events created with Debug,
27 // Info, Warn or Error helper methods are squelched and non-leveled log
28 // events are passed to next unmodified.
29 func NewFilter(next log.Logger, options ...Option) log.Logger {
30 l := &logger{
31 next: next,
32 }
33 for _, option := range options {
34 option(l)
35 }
36 return l
37 }
38
39 type logger struct {
40 next log.Logger
41 allowed level
42 squelchNoLevel bool
43 errNotAllowed error
44 errNoLevel error
45 }
46
47 func (l *logger) Log(keyvals ...interface{}) error {
48 var hasLevel, levelAllowed bool
49 for i := 1; i < len(keyvals); i += 2 {
50 if v, ok := keyvals[i].(*levelValue); ok {
51 hasLevel = true
52 levelAllowed = l.allowed&v.level != 0
53 break
54 }
55 }
56 if !hasLevel && l.squelchNoLevel {
57 return l.errNoLevel
58 }
59 if hasLevel && !levelAllowed {
60 return l.errNotAllowed
61 }
62 return l.next.Log(keyvals...)
63 }
64
65 // Option sets a parameter for the leveled logger.
66 type Option func(*logger)
67
68 // AllowAll is an alias for AllowDebug.
69 func AllowAll() Option {
70 return AllowDebug()
71 }
72
73 // AllowDebug allows error, warn, info and debug level log events to pass.
74 func AllowDebug() Option {
75 return allowed(levelError | levelWarn | levelInfo | levelDebug)
76 }
77
78 // AllowInfo allows error, warn and info level log events to pass.
79 func AllowInfo() Option {
80 return allowed(levelError | levelWarn | levelInfo)
81 }
82
83 // AllowWarn allows error and warn level log events to pass.
84 func AllowWarn() Option {
85 return allowed(levelError | levelWarn)
86 }
87
88 // AllowError allows only error level log events to pass.
89 func AllowError() Option {
90 return allowed(levelError)
91 }
92
93 // AllowNone allows no leveled log events to pass.
94 func AllowNone() Option {
95 return allowed(0)
96 }
97
98 func allowed(allowed level) Option {
99 return func(l *logger) { l.allowed = allowed }
100 }
101
102 // ErrNotAllowed sets the error to return from Log when it squelches a log
103 // event disallowed by the configured Allow[Level] option. By default,
104 // ErrNotAllowed is nil; in this case the log event is squelched with no
105 // error.
106 func ErrNotAllowed(err error) Option {
107 return func(l *logger) { l.errNotAllowed = err }
108 }
109
110 // SquelchNoLevel instructs Log to squelch log events with no level, so that
111 // they don't proceed through to the wrapped logger. If SquelchNoLevel is set
112 // to true and a log event is squelched in this way, the error value
113 // configured with ErrNoLevel is returned to the caller.
114 func SquelchNoLevel(squelch bool) Option {
115 return func(l *logger) { l.squelchNoLevel = squelch }
116 }
117
118 // ErrNoLevel sets the error to return from Log when it squelches a log event
119 // with no level. By default, ErrNoLevel is nil; in this case the log event is
120 // squelched with no error.
121 func ErrNoLevel(err error) Option {
122 return func(l *logger) { l.errNoLevel = err }
123 }
124
125 // NewInjector wraps next and returns a logger that adds a Key/level pair to
126 // the beginning of log events that don't already contain a level. In effect,
127 // this gives a default level to logs without a level.
128 func NewInjector(next log.Logger, level Value) log.Logger {
129 return &injector{
130 next: next,
131 level: level,
132 }
133 }
134
135 type injector struct {
136 next log.Logger
137 level interface{}
138 }
139
140 func (l *injector) Log(keyvals ...interface{}) error {
141 for i := 1; i < len(keyvals); i += 2 {
142 if _, ok := keyvals[i].(*levelValue); ok {
143 return l.next.Log(keyvals...)
144 }
145 }
146 kvs := make([]interface{}, len(keyvals)+2)
147 kvs[0], kvs[1] = key, l.level
148 copy(kvs[2:], keyvals)
149 return l.next.Log(kvs...)
150 }
151
152 // Value is the interface that each of the canonical level values implement.
153 // It contains unexported methods that prevent types from other packages from
154 // implementing it and guaranteeing that NewFilter can distinguish the levels
155 // defined in this package from all other values.
156 type Value interface {
157 String() string
158 levelVal()
159 }
160
161 // Key returns the unique key added to log events by the loggers in this
162 // package.
163 func Key() interface{} { return key }
164
165 // ErrorValue returns the unique value added to log events by Error.
166 func ErrorValue() Value { return errorValue }
167
168 // WarnValue returns the unique value added to log events by Warn.
169 func WarnValue() Value { return warnValue }
170
171 // InfoValue returns the unique value added to log events by Info.
172 func InfoValue() Value { return infoValue }
173
174 // DebugValue returns the unique value added to log events by Warn.
175 func DebugValue() Value { return debugValue }
176
177 var (
178 // key is of type interfae{} so that it allocates once during package
179 // initialization and avoids allocating every type the value is added to a
180 // []interface{} later.
181 key interface{} = "level"
182
183 errorValue = &levelValue{level: levelError, name: "error"}
184 warnValue = &levelValue{level: levelWarn, name: "warn"}
185 infoValue = &levelValue{level: levelInfo, name: "info"}
186 debugValue = &levelValue{level: levelDebug, name: "debug"}
187 )
188
189 type level byte
190
191 const (
192 levelDebug level = 1 << iota
193 levelInfo
194 levelWarn
195 levelError
196 )
197
198 type levelValue struct {
199 name string
200 level
201 }
202
203 func (v *levelValue) String() string { return v.name }
204 func (v *levelValue) levelVal() {}
0 package level_test
1
2 import (
3 "bytes"
4 "errors"
5 "io"
6 "strings"
7 "testing"
8
9 "github.com/go-kit/kit/log"
10 "github.com/go-kit/kit/log/level"
11 )
12
13 func TestVariousLevels(t *testing.T) {
14 testCases := []struct {
15 name string
16 allowed level.Option
17 want string
18 }{
19 {
20 "AllowAll",
21 level.AllowAll(),
22 strings.Join([]string{
23 `{"level":"debug","this is":"debug log"}`,
24 `{"level":"info","this is":"info log"}`,
25 `{"level":"warn","this is":"warn log"}`,
26 `{"level":"error","this is":"error log"}`,
27 }, "\n"),
28 },
29 {
30 "AllowDebug",
31 level.AllowDebug(),
32 strings.Join([]string{
33 `{"level":"debug","this is":"debug log"}`,
34 `{"level":"info","this is":"info log"}`,
35 `{"level":"warn","this is":"warn log"}`,
36 `{"level":"error","this is":"error log"}`,
37 }, "\n"),
38 },
39 {
40 "AllowDebug",
41 level.AllowInfo(),
42 strings.Join([]string{
43 `{"level":"info","this is":"info log"}`,
44 `{"level":"warn","this is":"warn log"}`,
45 `{"level":"error","this is":"error log"}`,
46 }, "\n"),
47 },
48 {
49 "AllowWarn",
50 level.AllowWarn(),
51 strings.Join([]string{
52 `{"level":"warn","this is":"warn log"}`,
53 `{"level":"error","this is":"error log"}`,
54 }, "\n"),
55 },
56 {
57 "AllowError",
58 level.AllowError(),
59 strings.Join([]string{
60 `{"level":"error","this is":"error log"}`,
61 }, "\n"),
62 },
63 {
64 "AllowNone",
65 level.AllowNone(),
66 ``,
67 },
68 }
69
70 for _, tc := range testCases {
71 t.Run(tc.name, func(t *testing.T) {
72 var buf bytes.Buffer
73 logger := level.NewFilter(log.NewJSONLogger(&buf), tc.allowed)
74
75 level.Debug(logger).Log("this is", "debug log")
76 level.Info(logger).Log("this is", "info log")
77 level.Warn(logger).Log("this is", "warn log")
78 level.Error(logger).Log("this is", "error log")
79
80 if want, have := tc.want, strings.TrimSpace(buf.String()); want != have {
81 t.Errorf("\nwant:\n%s\nhave:\n%s", want, have)
82 }
83 })
84 }
85 }
86
87 func TestErrNotAllowed(t *testing.T) {
88 myError := errors.New("squelched!")
89 opts := []level.Option{
90 level.AllowWarn(),
91 level.ErrNotAllowed(myError),
92 }
93 logger := level.NewFilter(log.NewNopLogger(), opts...)
94
95 if want, have := myError, level.Info(logger).Log("foo", "bar"); want != have {
96 t.Errorf("want %#+v, have %#+v", want, have)
97 }
98
99 if want, have := error(nil), level.Warn(logger).Log("foo", "bar"); want != have {
100 t.Errorf("want %#+v, have %#+v", want, have)
101 }
102 }
103
104 func TestErrNoLevel(t *testing.T) {
105 myError := errors.New("no level specified")
106
107 var buf bytes.Buffer
108 opts := []level.Option{
109 level.SquelchNoLevel(true),
110 level.ErrNoLevel(myError),
111 }
112 logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
113
114 if want, have := myError, logger.Log("foo", "bar"); want != have {
115 t.Errorf("want %v, have %v", want, have)
116 }
117 if want, have := ``, strings.TrimSpace(buf.String()); want != have {
118 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
119 }
120 }
121
122 func TestAllowNoLevel(t *testing.T) {
123 var buf bytes.Buffer
124 opts := []level.Option{
125 level.SquelchNoLevel(false),
126 level.ErrNoLevel(errors.New("I should never be returned!")),
127 }
128 logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
129
130 if want, have := error(nil), logger.Log("foo", "bar"); want != have {
131 t.Errorf("want %v, have %v", want, have)
132 }
133 if want, have := `{"foo":"bar"}`, strings.TrimSpace(buf.String()); want != have {
134 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
135 }
136 }
137
138 func TestLevelContext(t *testing.T) {
139 var buf bytes.Buffer
140
141 // Wrapping the level logger with a context allows users to use
142 // log.DefaultCaller as per normal.
143 var logger log.Logger
144 logger = log.NewLogfmtLogger(&buf)
145 logger = level.NewFilter(logger, level.AllowAll())
146 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
147
148 level.Info(logger).Log("foo", "bar")
149 if want, have := `level=info caller=level_test.go:149 foo=bar`, strings.TrimSpace(buf.String()); want != have {
150 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
151 }
152 }
153
154 func TestContextLevel(t *testing.T) {
155 var buf bytes.Buffer
156
157 // Wrapping a context with the level logger still works, but requires users
158 // to specify a higher callstack depth value.
159 var logger log.Logger
160 logger = log.NewLogfmtLogger(&buf)
161 logger = log.NewContext(logger).With("caller", log.Caller(5))
162 logger = level.NewFilter(logger, level.AllowAll())
163
164 level.Info(logger).Log("foo", "bar")
165 if want, have := `caller=level_test.go:165 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
166 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
167 }
168 }
169
170 func TestLevelFormatting(t *testing.T) {
171 testCases := []struct {
172 name string
173 format func(io.Writer) log.Logger
174 output string
175 }{
176 {
177 name: "logfmt",
178 format: log.NewLogfmtLogger,
179 output: `level=info foo=bar`,
180 },
181 {
182 name: "JSON",
183 format: log.NewJSONLogger,
184 output: `{"foo":"bar","level":"info"}`,
185 },
186 }
187
188 for _, tc := range testCases {
189 t.Run(tc.name, func(t *testing.T) {
190 var buf bytes.Buffer
191
192 logger := tc.format(&buf)
193 level.Info(logger).Log("foo", "bar")
194 if want, have := tc.output, strings.TrimSpace(buf.String()); want != have {
195 t.Errorf("\nwant: '%s'\nhave '%s'", want, have)
196 }
197 })
198 }
199 }
200
201 func TestInjector(t *testing.T) {
202 var (
203 output []interface{}
204 logger log.Logger
205 )
206
207 logger = log.LoggerFunc(func(keyvals ...interface{}) error {
208 output = keyvals
209 return nil
210 })
211 logger = level.NewInjector(logger, level.InfoValue())
212
213 logger.Log("foo", "bar")
214 if got, want := len(output), 4; got != want {
215 t.Errorf("missing level not injected: got len==%d, want len==%d", got, want)
216 }
217 if got, want := output[0], level.Key(); got != want {
218 t.Errorf("wrong level key: got %#v, want %#v", got, want)
219 }
220 if got, want := output[1], level.InfoValue(); got != want {
221 t.Errorf("wrong level value: got %#v, want %#v", got, want)
222 }
223
224 level.Error(logger).Log("foo", "bar")
225 if got, want := len(output), 4; got != want {
226 t.Errorf("leveled record modified: got len==%d, want len==%d", got, want)
227 }
228 if got, want := output[0], level.Key(); got != want {
229 t.Errorf("wrong level key: got %#v, want %#v", got, want)
230 }
231 if got, want := output[1], level.ErrorValue(); got != want {
232 t.Errorf("wrong level value: got %#v, want %#v", got, want)
233 }
234 }