Replace kit/log with log (#1173)
* Implement log/... packages with github.com/go-kit/log
* Use github.com/go-kit/log/... in all the other packages
Chris Hines authored 1 year, 7 months ago
GitHub committed 1 year, 7 months ago
94 | 94 | "context" |
95 | 95 | |
96 | 96 | "github.com/go-kit/kit/auth/jwt" |
97 | "github.com/go-kit/kit/log" | |
97 | "github.com/go-kit/log" | |
98 | 98 | grpctransport "github.com/go-kit/kit/transport/grpc" |
99 | 99 | ) |
100 | 100 |
15 | 15 | github.com/edsrzf/mmap-go v1.0.0 // indirect |
16 | 16 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db // indirect |
17 | 17 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect |
18 | github.com/go-logfmt/logfmt v0.5.0 | |
19 | github.com/go-stack/stack v1.8.0 | |
18 | github.com/go-kit/log v0.1.0 | |
20 | 19 | github.com/go-zookeeper/zk v1.0.2 |
21 | 20 | github.com/hashicorp/consul/api v1.8.1 |
22 | 21 | github.com/hudl/fargo v1.3.0 |
78 | 78 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= |
79 | 79 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= |
80 | 80 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= |
81 | github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= | |
81 | 82 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= |
82 | 83 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= |
83 | 84 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= |
0 | 0 | # package log |
1 | ||
2 | **Deprecation notice:** The core Go kit log packages (log, log/level, log/term, and | |
3 | log/syslog) have been moved to their own repository at github.com/go-kit/log. | |
4 | The corresponding packages in this directory remain for backwards compatibility. | |
5 | Their types alias the types and their functions call the functions provided by | |
6 | the new repository. Using either import path should be equivalent. Prefer the | |
7 | new import path when practical. | |
8 | ||
9 | ______ | |
1 | 10 | |
2 | 11 | `package log` provides a minimal interface for structured logging in services. |
3 | 12 | It may be wrapped to encode conventions, enforce type-safety, provide leveled |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | ) | |
7 | ||
8 | func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { | |
9 | lc := log.With(logger, "common_key", "common_value") | |
10 | b.ReportAllocs() | |
11 | b.ResetTimer() | |
12 | for i := 0; i < b.N; i++ { | |
13 | f(lc) | |
14 | } | |
15 | } | |
16 | ||
17 | var ( | |
18 | baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") } | |
19 | withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") } | |
20 | ) |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "math" | |
4 | "testing" | |
5 | ||
6 | "github.com/go-kit/kit/log" | |
7 | ) | |
8 | ||
9 | // These test are designed to be run with the race detector. | |
10 | ||
11 | func testConcurrency(t *testing.T, logger log.Logger, total int) { | |
12 | n := int(math.Sqrt(float64(total))) | |
13 | share := total / n | |
14 | ||
15 | errC := make(chan error, n) | |
16 | ||
17 | for i := 0; i < n; i++ { | |
18 | go func() { | |
19 | errC <- spam(logger, share) | |
20 | }() | |
21 | } | |
22 | ||
23 | for i := 0; i < n; i++ { | |
24 | err := <-errC | |
25 | if err != nil { | |
26 | t.Fatalf("concurrent logging error: %v", err) | |
27 | } | |
28 | } | |
29 | } | |
30 | ||
31 | func spam(logger log.Logger, count int) error { | |
32 | for i := 0; i < count; i++ { | |
33 | err := logger.Log("key", i) | |
34 | if err != nil { | |
35 | return err | |
36 | } | |
37 | } | |
38 | return nil | |
39 | } |
0 | // Package levels implements leveled logging on top of Go kit's log package. | |
1 | // | |
2 | // Deprecated: Use github.com/go-kit/log/level instead. | |
0 | 3 | package levels |
1 | 4 | |
2 | import "github.com/go-kit/kit/log" | |
5 | import "github.com/go-kit/log" | |
3 | 6 | |
4 | 7 | // Levels provides a leveled logging wrapper around a logger. It has five |
5 | 8 | // levels: debug, info, warning (warn), error, and critical (crit). If you |
4 | 4 | "os" |
5 | 5 | "testing" |
6 | 6 | |
7 | "github.com/go-kit/kit/log" | |
8 | 7 | levels "github.com/go-kit/kit/log/deprecated_levels" |
8 | "github.com/go-kit/log" | |
9 | 9 | ) |
10 | 10 | |
11 | 11 | func TestDefaultLevels(t *testing.T) { |
0 | 0 | // Package log provides a structured logger. |
1 | // | |
2 | // Deprecated: Use github.com/go-kit/log instead. | |
1 | 3 | // |
2 | 4 | // Structured logging produces logs easily consumed later by humans or |
3 | 5 | // machines. Humans might be interested in debugging errors, or tracing |
0 | 0 | package log |
1 | 1 | |
2 | 2 | import ( |
3 | "encoding" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | 3 | "io" |
7 | "reflect" | |
4 | ||
5 | "github.com/go-kit/log" | |
8 | 6 | ) |
9 | ||
10 | type jsonLogger struct { | |
11 | io.Writer | |
12 | } | |
13 | 7 | |
14 | 8 | // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a |
15 | 9 | // single JSON object. Each log event produces no more than one call to |
16 | 10 | // w.Write. The passed Writer must be safe for concurrent use by multiple |
17 | 11 | // goroutines if the returned Logger will be used concurrently. |
18 | 12 | func NewJSONLogger(w io.Writer) Logger { |
19 | return &jsonLogger{w} | |
13 | return log.NewJSONLogger(w) | |
20 | 14 | } |
21 | ||
22 | func (l *jsonLogger) Log(keyvals ...interface{}) error { | |
23 | n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd | |
24 | m := make(map[string]interface{}, n) | |
25 | for i := 0; i < len(keyvals); i += 2 { | |
26 | k := keyvals[i] | |
27 | var v interface{} = ErrMissingValue | |
28 | if i+1 < len(keyvals) { | |
29 | v = keyvals[i+1] | |
30 | } | |
31 | merge(m, k, v) | |
32 | } | |
33 | enc := json.NewEncoder(l.Writer) | |
34 | enc.SetEscapeHTML(false) | |
35 | return enc.Encode(m) | |
36 | } | |
37 | ||
38 | func merge(dst map[string]interface{}, k, v interface{}) { | |
39 | var key string | |
40 | switch x := k.(type) { | |
41 | case string: | |
42 | key = x | |
43 | case fmt.Stringer: | |
44 | key = safeString(x) | |
45 | default: | |
46 | key = fmt.Sprint(x) | |
47 | } | |
48 | ||
49 | // We want json.Marshaler and encoding.TextMarshaller to take priority over | |
50 | // err.Error() and v.String(). But json.Marshall (called later) does that by | |
51 | // default so we force a no-op if it's one of those 2 case. | |
52 | switch x := v.(type) { | |
53 | case json.Marshaler: | |
54 | case encoding.TextMarshaler: | |
55 | case error: | |
56 | v = safeError(x) | |
57 | case fmt.Stringer: | |
58 | v = safeString(x) | |
59 | } | |
60 | ||
61 | dst[key] = v | |
62 | } | |
63 | ||
64 | func safeString(str fmt.Stringer) (s string) { | |
65 | defer func() { | |
66 | if panicVal := recover(); panicVal != nil { | |
67 | if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { | |
68 | s = "NULL" | |
69 | } else { | |
70 | panic(panicVal) | |
71 | } | |
72 | } | |
73 | }() | |
74 | s = str.String() | |
75 | return | |
76 | } | |
77 | ||
78 | func safeError(err error) (s interface{}) { | |
79 | defer func() { | |
80 | if panicVal := recover(); panicVal != nil { | |
81 | if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { | |
82 | s = nil | |
83 | } else { | |
84 | panic(panicVal) | |
85 | } | |
86 | } | |
87 | }() | |
88 | s = err.Error() | |
89 | return | |
90 | } |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "errors" | |
5 | "io/ioutil" | |
6 | "testing" | |
7 | ||
8 | "github.com/go-kit/kit/log" | |
9 | ) | |
10 | ||
11 | func TestJSONLoggerCaller(t *testing.T) { | |
12 | t.Parallel() | |
13 | buf := &bytes.Buffer{} | |
14 | logger := log.NewJSONLogger(buf) | |
15 | logger = log.With(logger, "caller", log.DefaultCaller) | |
16 | ||
17 | if err := logger.Log(); err != nil { | |
18 | t.Fatal(err) | |
19 | } | |
20 | if want, have := `{"caller":"json_logger_test.go:18"}`+"\n", buf.String(); want != have { | |
21 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
22 | } | |
23 | } | |
24 | ||
25 | func TestJSONLogger(t *testing.T) { | |
26 | t.Parallel() | |
27 | buf := &bytes.Buffer{} | |
28 | logger := log.NewJSONLogger(buf) | |
29 | if err := logger.Log("err", errors.New("err"), "m", map[string]int{"0": 0}, "a", []int{1, 2, 3}); err != nil { | |
30 | t.Fatal(err) | |
31 | } | |
32 | if want, have := `{"a":[1,2,3],"err":"err","m":{"0":0}}`+"\n", buf.String(); want != have { | |
33 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
34 | } | |
35 | } | |
36 | ||
37 | func TestJSONLoggerMissingValue(t *testing.T) { | |
38 | t.Parallel() | |
39 | buf := &bytes.Buffer{} | |
40 | logger := log.NewJSONLogger(buf) | |
41 | if err := logger.Log("k"); err != nil { | |
42 | t.Fatal(err) | |
43 | } | |
44 | if want, have := `{"k":"(MISSING)"}`+"\n", buf.String(); want != have { | |
45 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
46 | } | |
47 | } | |
48 | ||
49 | func TestJSONLoggerNilStringerKey(t *testing.T) { | |
50 | t.Parallel() | |
51 | ||
52 | buf := &bytes.Buffer{} | |
53 | logger := log.NewJSONLogger(buf) | |
54 | if err := logger.Log((*stringer)(nil), "v"); err != nil { | |
55 | t.Fatal(err) | |
56 | } | |
57 | if want, have := `{"NULL":"v"}`+"\n", buf.String(); want != have { | |
58 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
59 | } | |
60 | } | |
61 | ||
62 | func TestJSONLoggerNilErrorValue(t *testing.T) { | |
63 | t.Parallel() | |
64 | ||
65 | buf := &bytes.Buffer{} | |
66 | logger := log.NewJSONLogger(buf) | |
67 | if err := logger.Log("err", (*stringError)(nil)); err != nil { | |
68 | t.Fatal(err) | |
69 | } | |
70 | if want, have := `{"err":null}`+"\n", buf.String(); want != have { | |
71 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
72 | } | |
73 | } | |
74 | ||
75 | func TestJSONLoggerNoHTMLEscape(t *testing.T) { | |
76 | t.Parallel() | |
77 | buf := &bytes.Buffer{} | |
78 | logger := log.NewJSONLogger(buf) | |
79 | if err := logger.Log("k", "<&>"); err != nil { | |
80 | t.Fatal(err) | |
81 | } | |
82 | if want, have := `{"k":"<&>"}`+"\n", buf.String(); want != have { | |
83 | t.Errorf("\nwant %#v\nhave%#v", want, have) | |
84 | } | |
85 | } | |
86 | ||
87 | // aller implements json.Marshaler, encoding.TextMarshaler, and fmt.Stringer. | |
88 | type aller struct{} | |
89 | ||
90 | func (aller) MarshalJSON() ([]byte, error) { | |
91 | return []byte("\"json\""), nil | |
92 | } | |
93 | ||
94 | func (aller) MarshalText() ([]byte, error) { | |
95 | return []byte("text"), nil | |
96 | } | |
97 | ||
98 | func (aller) String() string { | |
99 | return "string" | |
100 | } | |
101 | ||
102 | func (aller) Error() string { | |
103 | return "error" | |
104 | } | |
105 | ||
106 | // textstringer implements encoding.TextMarshaler and fmt.Stringer. | |
107 | type textstringer struct{} | |
108 | ||
109 | func (textstringer) MarshalText() ([]byte, error) { | |
110 | return []byte("text"), nil | |
111 | } | |
112 | ||
113 | func (textstringer) String() string { | |
114 | return "string" | |
115 | } | |
116 | ||
117 | func TestJSONLoggerStringValue(t *testing.T) { | |
118 | t.Parallel() | |
119 | tests := []struct { | |
120 | v interface{} | |
121 | expected string | |
122 | }{ | |
123 | { | |
124 | v: aller{}, | |
125 | expected: `{"v":"json"}`, | |
126 | }, | |
127 | { | |
128 | v: textstringer{}, | |
129 | expected: `{"v":"text"}`, | |
130 | }, | |
131 | { | |
132 | v: stringer("string"), | |
133 | expected: `{"v":"string"}`, | |
134 | }, | |
135 | } | |
136 | ||
137 | for _, test := range tests { | |
138 | buf := &bytes.Buffer{} | |
139 | logger := log.NewJSONLogger(buf) | |
140 | if err := logger.Log("v", test.v); err != nil { | |
141 | t.Fatal(err) | |
142 | } | |
143 | ||
144 | if want, have := test.expected+"\n", buf.String(); want != have { | |
145 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
146 | } | |
147 | } | |
148 | } | |
149 | ||
150 | type stringer string | |
151 | ||
152 | func (s stringer) String() string { | |
153 | return string(s) | |
154 | } | |
155 | ||
156 | type stringError string | |
157 | ||
158 | func (s stringError) Error() string { | |
159 | return string(s) | |
160 | } | |
161 | ||
162 | func BenchmarkJSONLoggerSimple(b *testing.B) { | |
163 | benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), baseMessage) | |
164 | } | |
165 | ||
166 | func BenchmarkJSONLoggerContextual(b *testing.B) { | |
167 | benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), withMessage) | |
168 | } | |
169 | ||
170 | func TestJSONLoggerConcurrency(t *testing.T) { | |
171 | t.Parallel() | |
172 | testConcurrency(t, log.NewJSONLogger(ioutil.Discard), 10000) | |
173 | } |
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.With(l, "time", log.DefaultTimestampUTC) | |
20 | }}, | |
21 | {"CallerContext", func(l log.Logger) log.Logger { | |
22 | return log.With(l, "caller", log.DefaultCaller) | |
23 | }}, | |
24 | {"TimeCallerReqIDContext", func(l log.Logger) log.Logger { | |
25 | return log.With(l, "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 implements leveled logging on top of Go kit's log package. To | |
1 | // use the level package, create a logger as per normal in your func main, and | |
2 | // wrap it with level.NewFilter. | |
0 | // Package level implements leveled logging on top of Go kit's log package. | |
1 | // | |
2 | // Deprecated: Use github.com/go-kit/log/level instead. | |
3 | // | |
4 | // To use the level package, create a logger as per normal in your func main, | |
5 | // and wrap it with level.NewFilter. | |
3 | 6 | // |
4 | 7 | // var logger log.Logger |
5 | 8 | // logger = log.NewLogfmtLogger(os.Stderr) |
0 | 0 | package level |
1 | 1 | |
2 | import "github.com/go-kit/kit/log" | |
2 | import ( | |
3 | "github.com/go-kit/log" | |
4 | "github.com/go-kit/log/level" | |
5 | ) | |
3 | 6 | |
4 | 7 | // Error returns a logger that includes a Key/ErrorValue pair. |
5 | 8 | func Error(logger log.Logger) log.Logger { |
6 | return log.WithPrefix(logger, Key(), ErrorValue()) | |
9 | return level.Error(logger) | |
7 | 10 | } |
8 | 11 | |
9 | 12 | // Warn returns a logger that includes a Key/WarnValue pair. |
10 | 13 | func Warn(logger log.Logger) log.Logger { |
11 | return log.WithPrefix(logger, Key(), WarnValue()) | |
14 | return level.Warn(logger) | |
12 | 15 | } |
13 | 16 | |
14 | 17 | // Info returns a logger that includes a Key/InfoValue pair. |
15 | 18 | func Info(logger log.Logger) log.Logger { |
16 | return log.WithPrefix(logger, Key(), InfoValue()) | |
19 | return level.Info(logger) | |
17 | 20 | } |
18 | 21 | |
19 | 22 | // Debug returns a logger that includes a Key/DebugValue pair. |
20 | 23 | func Debug(logger log.Logger) log.Logger { |
21 | return log.WithPrefix(logger, Key(), DebugValue()) | |
24 | return level.Debug(logger) | |
22 | 25 | } |
23 | 26 | |
24 | 27 | // NewFilter wraps next and implements level filtering. See the commentary on |
27 | 30 | // Info, Warn or Error helper methods are squelched and non-leveled log |
28 | 31 | // events are passed to next unmodified. |
29 | 32 | 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...) | |
33 | return level.NewFilter(next, options...) | |
63 | 34 | } |
64 | 35 | |
65 | 36 | // Option sets a parameter for the leveled logger. |
66 | type Option func(*logger) | |
37 | type Option = level.Option | |
67 | 38 | |
68 | 39 | // AllowAll is an alias for AllowDebug. |
69 | 40 | func AllowAll() Option { |
70 | return AllowDebug() | |
41 | return level.AllowAll() | |
71 | 42 | } |
72 | 43 | |
73 | 44 | // AllowDebug allows error, warn, info and debug level log events to pass. |
74 | 45 | func AllowDebug() Option { |
75 | return allowed(levelError | levelWarn | levelInfo | levelDebug) | |
46 | return level.AllowDebug() | |
76 | 47 | } |
77 | 48 | |
78 | 49 | // AllowInfo allows error, warn and info level log events to pass. |
79 | 50 | func AllowInfo() Option { |
80 | return allowed(levelError | levelWarn | levelInfo) | |
51 | return level.AllowInfo() | |
81 | 52 | } |
82 | 53 | |
83 | 54 | // AllowWarn allows error and warn level log events to pass. |
84 | 55 | func AllowWarn() Option { |
85 | return allowed(levelError | levelWarn) | |
56 | return level.AllowWarn() | |
86 | 57 | } |
87 | 58 | |
88 | 59 | // AllowError allows only error level log events to pass. |
89 | 60 | func AllowError() Option { |
90 | return allowed(levelError) | |
61 | return level.AllowError() | |
91 | 62 | } |
92 | 63 | |
93 | 64 | // AllowNone allows no leveled log events to pass. |
94 | 65 | func AllowNone() Option { |
95 | return allowed(0) | |
96 | } | |
97 | ||
98 | func allowed(allowed level) Option { | |
99 | return func(l *logger) { l.allowed = allowed } | |
66 | return level.AllowNone() | |
100 | 67 | } |
101 | 68 | |
102 | 69 | // ErrNotAllowed sets the error to return from Log when it squelches a log |
104 | 71 | // ErrNotAllowed is nil; in this case the log event is squelched with no |
105 | 72 | // error. |
106 | 73 | func ErrNotAllowed(err error) Option { |
107 | return func(l *logger) { l.errNotAllowed = err } | |
74 | return level.ErrNotAllowed(err) | |
108 | 75 | } |
109 | 76 | |
110 | 77 | // SquelchNoLevel instructs Log to squelch log events with no level, so that |
112 | 79 | // to true and a log event is squelched in this way, the error value |
113 | 80 | // configured with ErrNoLevel is returned to the caller. |
114 | 81 | func SquelchNoLevel(squelch bool) Option { |
115 | return func(l *logger) { l.squelchNoLevel = squelch } | |
82 | return level.SquelchNoLevel(squelch) | |
116 | 83 | } |
117 | 84 | |
118 | 85 | // ErrNoLevel sets the error to return from Log when it squelches a log event |
119 | 86 | // with no level. By default, ErrNoLevel is nil; in this case the log event is |
120 | 87 | // squelched with no error. |
121 | 88 | func ErrNoLevel(err error) Option { |
122 | return func(l *logger) { l.errNoLevel = err } | |
89 | return level.ErrNoLevel(err) | |
123 | 90 | } |
124 | 91 | |
125 | 92 | // NewInjector wraps next and returns a logger that adds a Key/level pair to |
126 | 93 | // the beginning of log events that don't already contain a level. In effect, |
127 | 94 | // 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...) | |
95 | func NewInjector(next log.Logger, lvl Value) log.Logger { | |
96 | return level.NewInjector(next, lvl) | |
150 | 97 | } |
151 | 98 | |
152 | 99 | // Value is the interface that each of the canonical level values implement. |
153 | 100 | // It contains unexported methods that prevent types from other packages from |
154 | 101 | // implementing it and guaranteeing that NewFilter can distinguish the levels |
155 | 102 | // defined in this package from all other values. |
156 | type Value interface { | |
157 | String() string | |
158 | levelVal() | |
159 | } | |
103 | type Value = level.Value | |
160 | 104 | |
161 | 105 | // Key returns the unique key added to log events by the loggers in this |
162 | 106 | // package. |
163 | func Key() interface{} { return key } | |
107 | func Key() interface{} { return level.Key() } | |
164 | 108 | |
165 | 109 | // ErrorValue returns the unique value added to log events by Error. |
166 | func ErrorValue() Value { return errorValue } | |
110 | func ErrorValue() Value { return level.ErrorValue() } | |
167 | 111 | |
168 | 112 | // WarnValue returns the unique value added to log events by Warn. |
169 | func WarnValue() Value { return warnValue } | |
113 | func WarnValue() Value { return level.WarnValue() } | |
170 | 114 | |
171 | 115 | // InfoValue returns the unique value added to log events by Info. |
172 | func InfoValue() Value { return infoValue } | |
116 | func InfoValue() Value { return level.InfoValue() } | |
173 | 117 | |
174 | 118 | // DebugValue returns the unique value added to log events by Debug. |
175 | func DebugValue() Value { return debugValue } | |
176 | ||
177 | var ( | |
178 | // key is of type interface{} so that it allocates once during package | |
179 | // initialization and avoids allocating every time 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() {} | |
119 | func DebugValue() Value { return level.DebugValue() } |
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 | "AllowInfo", | |
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.With(logger, "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.With(logger, "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 | 0 | package log |
1 | 1 | |
2 | import "errors" | |
2 | import ( | |
3 | "github.com/go-kit/log" | |
4 | ) | |
3 | 5 | |
4 | 6 | // Logger is the fundamental interface for all log operations. Log creates a |
5 | 7 | // log event from keyvals, a variadic sequence of alternating keys and values. |
6 | 8 | // Implementations must be safe for concurrent use by multiple goroutines. In |
7 | 9 | // particular, any implementation of Logger that appends to keyvals or |
8 | 10 | // modifies or retains any of its elements must make a copy first. |
9 | type Logger interface { | |
10 | Log(keyvals ...interface{}) error | |
11 | } | |
11 | type Logger = log.Logger | |
12 | 12 | |
13 | 13 | // ErrMissingValue is appended to keyvals slices with odd length to substitute |
14 | 14 | // the missing value. |
15 | var ErrMissingValue = errors.New("(MISSING)") | |
15 | var ErrMissingValue = log.ErrMissingValue | |
16 | 16 | |
17 | 17 | // With returns a new contextual logger with keyvals prepended to those passed |
18 | 18 | // to calls to Log. If logger is also a contextual logger created by With, |
21 | 21 | // The returned Logger replaces all value elements (odd indexes) containing a |
22 | 22 | // Valuer with their generated value for each call to its Log method. |
23 | 23 | func With(logger Logger, keyvals ...interface{}) Logger { |
24 | if len(keyvals) == 0 { | |
25 | return logger | |
26 | } | |
27 | l := newContext(logger) | |
28 | kvs := append(l.keyvals, keyvals...) | |
29 | if len(kvs)%2 != 0 { | |
30 | kvs = append(kvs, ErrMissingValue) | |
31 | } | |
32 | return &context{ | |
33 | logger: l.logger, | |
34 | // Limiting the capacity of the stored keyvals ensures that a new | |
35 | // backing array is created if the slice must grow in Log or With. | |
36 | // Using the extra capacity without copying risks a data race that | |
37 | // would violate the Logger interface contract. | |
38 | keyvals: kvs[:len(kvs):len(kvs)], | |
39 | hasValuer: l.hasValuer || containsValuer(keyvals), | |
40 | sKeyvals: l.sKeyvals, | |
41 | sHasValuer: l.sHasValuer, | |
42 | } | |
24 | return log.With(logger, keyvals...) | |
43 | 25 | } |
44 | 26 | |
45 | 27 | // WithPrefix returns a new contextual logger with keyvals prepended to those |
49 | 31 | // The returned Logger replaces all value elements (odd indexes) containing a |
50 | 32 | // Valuer with their generated value for each call to its Log method. |
51 | 33 | func WithPrefix(logger Logger, keyvals ...interface{}) Logger { |
52 | if len(keyvals) == 0 { | |
53 | return logger | |
54 | } | |
55 | l := newContext(logger) | |
56 | // Limiting the capacity of the stored keyvals ensures that a new | |
57 | // backing array is created if the slice must grow in Log or With. | |
58 | // Using the extra capacity without copying risks a data race that | |
59 | // would violate the Logger interface contract. | |
60 | n := len(l.keyvals) + len(keyvals) | |
61 | if len(keyvals)%2 != 0 { | |
62 | n++ | |
63 | } | |
64 | kvs := make([]interface{}, 0, n) | |
65 | kvs = append(kvs, keyvals...) | |
66 | if len(kvs)%2 != 0 { | |
67 | kvs = append(kvs, ErrMissingValue) | |
68 | } | |
69 | kvs = append(kvs, l.keyvals...) | |
70 | return &context{ | |
71 | logger: l.logger, | |
72 | keyvals: kvs, | |
73 | hasValuer: l.hasValuer || containsValuer(keyvals), | |
74 | sKeyvals: l.sKeyvals, | |
75 | sHasValuer: l.sHasValuer, | |
76 | } | |
34 | return log.WithPrefix(logger, keyvals...) | |
77 | 35 | } |
78 | 36 | |
79 | 37 | // WithSuffix returns a new contextual logger with keyvals appended to those |
83 | 41 | // The returned Logger replaces all value elements (odd indexes) containing a |
84 | 42 | // Valuer with their generated value for each call to its Log method. |
85 | 43 | func WithSuffix(logger Logger, keyvals ...interface{}) Logger { |
86 | if len(keyvals) == 0 { | |
87 | return logger | |
88 | } | |
89 | l := newContext(logger) | |
90 | // Limiting the capacity of the stored keyvals ensures that a new | |
91 | // backing array is created if the slice must grow in Log or With. | |
92 | // Using the extra capacity without copying risks a data race that | |
93 | // would violate the Logger interface contract. | |
94 | n := len(l.sKeyvals) + len(keyvals) | |
95 | if len(keyvals)%2 != 0 { | |
96 | n++ | |
97 | } | |
98 | kvs := make([]interface{}, 0, n) | |
99 | kvs = append(kvs, keyvals...) | |
100 | if len(kvs)%2 != 0 { | |
101 | kvs = append(kvs, ErrMissingValue) | |
102 | } | |
103 | kvs = append(l.sKeyvals, kvs...) | |
104 | return &context{ | |
105 | logger: l.logger, | |
106 | keyvals: l.keyvals, | |
107 | hasValuer: l.hasValuer, | |
108 | sKeyvals: kvs, | |
109 | sHasValuer: l.sHasValuer || containsValuer(keyvals), | |
110 | } | |
111 | } | |
112 | ||
113 | // context is the Logger implementation returned by With, WithPrefix, and | |
114 | // WithSuffix. It wraps a Logger and holds keyvals that it includes in all | |
115 | // log events. Its Log method calls bindValues to generate values for each | |
116 | // Valuer in the context keyvals. | |
117 | // | |
118 | // A context must always have the same number of stack frames between calls to | |
119 | // its Log method and the eventual binding of Valuers to their value. This | |
120 | // requirement comes from the functional requirement to allow a context to | |
121 | // resolve application call site information for a Caller stored in the | |
122 | // context. To do this we must be able to predict the number of logging | |
123 | // functions on the stack when bindValues is called. | |
124 | // | |
125 | // Two implementation details provide the needed stack depth consistency. | |
126 | // | |
127 | // 1. newContext avoids introducing an additional layer when asked to | |
128 | // wrap another context. | |
129 | // 2. With, WithPrefix, and WithSuffix avoid introducing an additional | |
130 | // layer by returning a newly constructed context with a merged keyvals | |
131 | // rather than simply wrapping the existing context. | |
132 | type context struct { | |
133 | logger Logger | |
134 | keyvals []interface{} | |
135 | sKeyvals []interface{} // suffixes | |
136 | hasValuer bool | |
137 | sHasValuer bool | |
138 | } | |
139 | ||
140 | func newContext(logger Logger) *context { | |
141 | if c, ok := logger.(*context); ok { | |
142 | return c | |
143 | } | |
144 | return &context{logger: logger} | |
145 | } | |
146 | ||
147 | // Log replaces all value elements (odd indexes) containing a Valuer in the | |
148 | // stored context with their generated value, appends keyvals, and passes the | |
149 | // result to the wrapped Logger. | |
150 | func (l *context) Log(keyvals ...interface{}) error { | |
151 | kvs := append(l.keyvals, keyvals...) | |
152 | if len(kvs)%2 != 0 { | |
153 | kvs = append(kvs, ErrMissingValue) | |
154 | } | |
155 | if l.hasValuer { | |
156 | // If no keyvals were appended above then we must copy l.keyvals so | |
157 | // that future log events will reevaluate the stored Valuers. | |
158 | if len(keyvals) == 0 { | |
159 | kvs = append([]interface{}{}, l.keyvals...) | |
160 | } | |
161 | bindValues(kvs[:(len(l.keyvals))]) | |
162 | } | |
163 | kvs = append(kvs, l.sKeyvals...) | |
164 | if l.sHasValuer { | |
165 | bindValues(kvs[len(kvs) - len(l.sKeyvals):]) | |
166 | } | |
167 | return l.logger.Log(kvs...) | |
44 | return log.WithSuffix(logger, keyvals...) | |
168 | 45 | } |
169 | 46 | |
170 | 47 | // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If |
171 | 48 | // f is a function with the appropriate signature, LoggerFunc(f) is a Logger |
172 | 49 | // object that calls f. |
173 | type LoggerFunc func(...interface{}) error | |
174 | ||
175 | // Log implements Logger by calling f(keyvals...). | |
176 | func (f LoggerFunc) Log(keyvals ...interface{}) error { | |
177 | return f(keyvals...) | |
178 | } | |
50 | type LoggerFunc = log.LoggerFunc |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "fmt" | |
5 | "sync" | |
6 | "testing" | |
7 | ||
8 | "github.com/go-kit/kit/log" | |
9 | "github.com/go-stack/stack" | |
10 | ) | |
11 | ||
12 | func TestContext(t *testing.T) { | |
13 | t.Parallel() | |
14 | buf := &bytes.Buffer{} | |
15 | logger := log.NewLogfmtLogger(buf) | |
16 | ||
17 | kvs := []interface{}{"a", 123} | |
18 | lc := log.With(logger, kvs...) | |
19 | kvs[1] = 0 // With should copy its key values | |
20 | ||
21 | lc = log.With(lc, "b", "c") // With should stack | |
22 | if err := lc.Log("msg", "message"); err != nil { | |
23 | t.Fatal(err) | |
24 | } | |
25 | if want, have := "a=123 b=c msg=message\n", buf.String(); want != have { | |
26 | t.Errorf("\nwant: %shave: %s", want, have) | |
27 | } | |
28 | ||
29 | buf.Reset() | |
30 | lc = log.WithPrefix(lc, "p", "first") | |
31 | if err := lc.Log("msg", "message"); err != nil { | |
32 | t.Fatal(err) | |
33 | } | |
34 | if want, have := "p=first a=123 b=c msg=message\n", buf.String(); want != have { | |
35 | t.Errorf("\nwant: %shave: %s", want, have) | |
36 | } | |
37 | } | |
38 | ||
39 | func TestContextMissingValue(t *testing.T) { | |
40 | t.Parallel() | |
41 | var output []interface{} | |
42 | logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { | |
43 | output = keyvals | |
44 | return nil | |
45 | })) | |
46 | ||
47 | log.WithPrefix(log.With(logger, "k1"), "k0").Log("k2") | |
48 | if want, have := 6, len(output); want != have { | |
49 | t.Errorf("want len(output) == %v, have %v", want, have) | |
50 | } | |
51 | for i := 1; i < 6; i += 2 { | |
52 | if want, have := log.ErrMissingValue, output[i]; want != have { | |
53 | t.Errorf("want output[%d] == %#v, have %#v", i, want, have) | |
54 | } | |
55 | } | |
56 | } | |
57 | ||
58 | func TestWithPrefixAndSuffix(t *testing.T) { | |
59 | t.Parallel() | |
60 | var output []interface{} | |
61 | logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { | |
62 | output = keyvals | |
63 | return nil | |
64 | })) | |
65 | ||
66 | lc := log.WithPrefix(logger, "a", "first") | |
67 | lc = log.WithSuffix(lc, "z", "last") | |
68 | if err := lc.Log("msg", "message"); err != nil { | |
69 | t.Fatal(err) | |
70 | } | |
71 | if want, have := 6, len(output); want != have { | |
72 | t.Errorf("want len(output) == %v, have %v", want, have) | |
73 | } | |
74 | want := []string{"a", "first", "msg", "message", "z", "last"} | |
75 | for i := 0; i < 6; i++ { | |
76 | if want, have := want[i], output[i]; want != have { | |
77 | t.Errorf("want output[%d] == %#v, have %#v", i, want, have) | |
78 | } | |
79 | } | |
80 | ||
81 | lc = log.With(logger, "b", "second") | |
82 | lc = log.WithPrefix(lc, "a", "first") | |
83 | lc = log.With(lc, "c", "third") | |
84 | lc = log.WithSuffix(lc, "z", "last") | |
85 | lc = log.WithSuffix(lc, "aa", "sequel") | |
86 | if err := lc.Log("msg", "message"); err != nil { | |
87 | t.Fatal(err) | |
88 | } | |
89 | if want, have := 12, len(output); want != have { | |
90 | t.Errorf("want len(output) == %v, have %v", want, have) | |
91 | } | |
92 | want = []string{ | |
93 | "a", "first", | |
94 | "b", "second", | |
95 | "c", "third", | |
96 | "msg", "message", | |
97 | "z", "last", | |
98 | "aa", "sequel", | |
99 | } | |
100 | for i := 0; i < 12; i++ { | |
101 | if want, have := want[i], output[i]; want != have { | |
102 | t.Errorf("want output[%d] == %#v, have %#v", i, want, have) | |
103 | } | |
104 | } | |
105 | } | |
106 | ||
107 | // Test that context.Log has a consistent function stack depth when binding | |
108 | // Valuers, regardless of how many times With has been called. | |
109 | func TestContextStackDepth(t *testing.T) { | |
110 | t.Parallel() | |
111 | fn := fmt.Sprintf("%n", stack.Caller(0)) | |
112 | ||
113 | var output []interface{} | |
114 | ||
115 | logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { | |
116 | output = keyvals | |
117 | return nil | |
118 | })) | |
119 | ||
120 | stackValuer := log.Valuer(func() interface{} { | |
121 | for i, c := range stack.Trace() { | |
122 | if fmt.Sprintf("%n", c) == fn { | |
123 | return i | |
124 | } | |
125 | } | |
126 | t.Fatal("Test function not found in stack trace.") | |
127 | return nil | |
128 | }) | |
129 | ||
130 | logger = log.With(logger, "stack", stackValuer) | |
131 | ||
132 | // Call through interface to get baseline. | |
133 | logger.Log("k", "v") | |
134 | want := output[1].(int) | |
135 | ||
136 | for len(output) < 10 { | |
137 | logger.Log("k", "v") | |
138 | if have := output[1]; have != want { | |
139 | t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) | |
140 | } | |
141 | ||
142 | wrapped := log.With(logger) | |
143 | wrapped.Log("k", "v") | |
144 | if have := output[1]; have != want { | |
145 | t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) | |
146 | } | |
147 | ||
148 | logger = log.With(logger, "k", "v") | |
149 | } | |
150 | } | |
151 | ||
152 | // Test that With returns a Logger safe for concurrent use. This test | |
153 | // validates that the stored logging context does not get corrupted when | |
154 | // multiple clients concurrently log additional keyvals. | |
155 | // | |
156 | // This test must be run with go test -cpu 2 (or more) to achieve its goal. | |
157 | func TestWithConcurrent(t *testing.T) { | |
158 | // Create some buckets to count how many events each goroutine logs. | |
159 | const goroutines = 8 | |
160 | counts := [goroutines]int{} | |
161 | ||
162 | // This logger extracts a goroutine id from the last value field and | |
163 | // increments the referenced bucket. | |
164 | logger := log.LoggerFunc(func(kv ...interface{}) error { | |
165 | goroutine := kv[len(kv)-1].(int) | |
166 | counts[goroutine]++ | |
167 | return nil | |
168 | }) | |
169 | ||
170 | // With must be careful about handling slices that can grow without | |
171 | // copying the underlying array, so give it a challenge. | |
172 | l := log.With(logger, make([]interface{}, 0, 2)...) | |
173 | ||
174 | // Start logging concurrently. Each goroutine logs its id so the logger | |
175 | // can bucket the event counts. | |
176 | var wg sync.WaitGroup | |
177 | wg.Add(goroutines) | |
178 | const n = 10000 | |
179 | for i := 0; i < goroutines; i++ { | |
180 | go func(idx int) { | |
181 | defer wg.Done() | |
182 | for j := 0; j < n; j++ { | |
183 | l.Log("goroutineIdx", idx) | |
184 | } | |
185 | }(i) | |
186 | } | |
187 | wg.Wait() | |
188 | ||
189 | for bucket, have := range counts { | |
190 | if want := n; want != have { | |
191 | t.Errorf("bucket %d: want %d, have %d", bucket, want, have) // note Errorf | |
192 | } | |
193 | } | |
194 | } | |
195 | ||
196 | func TestLogCopiesValuers(t *testing.T) { | |
197 | t.Parallel() | |
198 | var output []interface{} | |
199 | logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { | |
200 | output = keyvals | |
201 | return nil | |
202 | })) | |
203 | ||
204 | valuerCallCount := 0 | |
205 | counterValuer := log.Valuer(func() interface{} { | |
206 | valuerCallCount++ | |
207 | return valuerCallCount | |
208 | }) | |
209 | lc := log.WithPrefix(logger, "a", counterValuer) | |
210 | lc = log.WithSuffix(lc, "z", counterValuer) | |
211 | ||
212 | if err := lc.Log(); err != nil { | |
213 | t.Fatal(err) | |
214 | } | |
215 | want := []interface{}{"a", 1, "z", 2} | |
216 | for i := 0; i < 4; i++ { | |
217 | if want, have := want[i], output[i]; want != have { | |
218 | t.Errorf("want output[%d] == %#v, have %#v", i, want, have) | |
219 | } | |
220 | } | |
221 | ||
222 | if err := lc.Log(); err != nil { | |
223 | t.Fatal(err) | |
224 | } | |
225 | want = []interface{}{"a", 3, "z", 4} | |
226 | for i := 0; i < 4; i++ { | |
227 | if want, have := want[i], output[i]; want != have { | |
228 | t.Errorf("want output[%d] == %#v, have %#v", i, want, have) | |
229 | } | |
230 | } | |
231 | } | |
232 | ||
233 | func BenchmarkDiscard(b *testing.B) { | |
234 | logger := log.NewNopLogger() | |
235 | b.ReportAllocs() | |
236 | b.ResetTimer() | |
237 | for i := 0; i < b.N; i++ { | |
238 | logger.Log("k", "v") | |
239 | } | |
240 | } | |
241 | ||
242 | func BenchmarkOneWith(b *testing.B) { | |
243 | logger := log.NewNopLogger() | |
244 | lc := log.With(logger, "k", "v") | |
245 | b.ReportAllocs() | |
246 | b.ResetTimer() | |
247 | for i := 0; i < b.N; i++ { | |
248 | lc.Log("k", "v") | |
249 | } | |
250 | } | |
251 | ||
252 | func BenchmarkTwoWith(b *testing.B) { | |
253 | logger := log.NewNopLogger() | |
254 | lc := log.With(logger, "k", "v") | |
255 | for i := 1; i < 2; i++ { | |
256 | lc = log.With(lc, "k", "v") | |
257 | } | |
258 | b.ReportAllocs() | |
259 | b.ResetTimer() | |
260 | for i := 0; i < b.N; i++ { | |
261 | lc.Log("k", "v") | |
262 | } | |
263 | } | |
264 | ||
265 | func BenchmarkTenWith(b *testing.B) { | |
266 | logger := log.NewNopLogger() | |
267 | lc := log.With(logger, "k", "v") | |
268 | for i := 1; i < 10; i++ { | |
269 | lc = log.With(lc, "k", "v") | |
270 | } | |
271 | b.ReportAllocs() | |
272 | b.ResetTimer() | |
273 | for i := 0; i < b.N; i++ { | |
274 | lc.Log("k", "v") | |
275 | } | |
276 | } | |
277 | ||
278 | func BenchmarkOneWithPrefix(b *testing.B) { | |
279 | logger := log.NewNopLogger() | |
280 | lc := log.WithPrefix(logger, "a", "first") | |
281 | b.ReportAllocs() | |
282 | b.ResetTimer() | |
283 | for i := 0; i < b.N; i++ { | |
284 | lc.Log("k", "v") | |
285 | } | |
286 | } | |
287 | ||
288 | func BenchmarkTenWithPrefix(b *testing.B) { | |
289 | logger := log.NewNopLogger() | |
290 | lc := log.WithPrefix(logger, "a", "first") | |
291 | for i := 1; i < 10; i++ { | |
292 | lc = log.WithPrefix(lc, "a", "first") | |
293 | } | |
294 | b.ReportAllocs() | |
295 | b.ResetTimer() | |
296 | for i := 0; i < b.N; i++ { | |
297 | lc.Log("k", "v") | |
298 | } | |
299 | } | |
300 | ||
301 | func BenchmarkOneWithSuffix(b *testing.B) { | |
302 | logger := log.NewNopLogger() | |
303 | lc := log.WithSuffix(logger, "z", "last") | |
304 | b.ReportAllocs() | |
305 | b.ResetTimer() | |
306 | for i := 0; i < b.N; i++ { | |
307 | lc.Log("k", "v") | |
308 | } | |
309 | } | |
310 | ||
311 | func BenchmarkTenWithSuffix(b *testing.B) { | |
312 | logger := log.NewNopLogger() | |
313 | lc := log.WithSuffix(logger, "z", "last") | |
314 | for i := 1; i < 10; i++ { | |
315 | lc = log.WithSuffix(lc, "z", "last") | |
316 | } | |
317 | b.ReportAllocs() | |
318 | b.ResetTimer() | |
319 | for i := 0; i < b.N; i++ { | |
320 | lc.Log("k", "v") | |
321 | } | |
322 | } | |
323 | ||
324 | func BenchmarkOneWithPrefixSuffix(b *testing.B) { | |
325 | logger := log.NewNopLogger() | |
326 | lc := log.WithSuffix(logger, "a", "first") | |
327 | lc = log.WithSuffix(lc, "z", "last") | |
328 | b.ReportAllocs() | |
329 | b.ResetTimer() | |
330 | for i := 0; i < b.N; i++ { | |
331 | lc.Log("k", "v") | |
332 | } | |
333 | } | |
334 | ||
335 | func BenchmarkTenWithPrefixSuffix(b *testing.B) { | |
336 | logger := log.NewNopLogger() | |
337 | lc := log.WithPrefix(logger, "a", "first") | |
338 | lc = log.WithSuffix(lc, "z", "last") | |
339 | for i := 1; i < 10; i++ { | |
340 | lc = log.WithPrefix(lc, "a", "first") | |
341 | lc = log.WithSuffix(lc, "z", "last") | |
342 | } | |
343 | b.ReportAllocs() | |
344 | b.ResetTimer() | |
345 | for i := 0; i < b.N; i++ { | |
346 | lc.Log("k", "v") | |
347 | } | |
348 | } |
0 | 0 | package log |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
4 | 3 | "io" |
5 | "sync" | |
6 | 4 | |
7 | "github.com/go-logfmt/logfmt" | |
5 | "github.com/go-kit/log" | |
8 | 6 | ) |
9 | ||
10 | type logfmtEncoder struct { | |
11 | *logfmt.Encoder | |
12 | buf bytes.Buffer | |
13 | } | |
14 | ||
15 | func (l *logfmtEncoder) Reset() { | |
16 | l.Encoder.Reset() | |
17 | l.buf.Reset() | |
18 | } | |
19 | ||
20 | var logfmtEncoderPool = sync.Pool{ | |
21 | New: func() interface{} { | |
22 | var enc logfmtEncoder | |
23 | enc.Encoder = logfmt.NewEncoder(&enc.buf) | |
24 | return &enc | |
25 | }, | |
26 | } | |
27 | ||
28 | type logfmtLogger struct { | |
29 | w io.Writer | |
30 | } | |
31 | 7 | |
32 | 8 | // NewLogfmtLogger returns a logger that encodes keyvals to the Writer in |
33 | 9 | // logfmt format. Each log event produces no more than one call to w.Write. |
34 | 10 | // The passed Writer must be safe for concurrent use by multiple goroutines if |
35 | 11 | // the returned Logger will be used concurrently. |
36 | 12 | func NewLogfmtLogger(w io.Writer) Logger { |
37 | return &logfmtLogger{w} | |
13 | return log.NewLogfmtLogger(w) | |
38 | 14 | } |
39 | ||
40 | func (l logfmtLogger) Log(keyvals ...interface{}) error { | |
41 | enc := logfmtEncoderPool.Get().(*logfmtEncoder) | |
42 | enc.Reset() | |
43 | defer logfmtEncoderPool.Put(enc) | |
44 | ||
45 | if err := enc.EncodeKeyvals(keyvals...); err != nil { | |
46 | return err | |
47 | } | |
48 | ||
49 | // Add newline to the end of the buffer | |
50 | if err := enc.EndRecord(); err != nil { | |
51 | return err | |
52 | } | |
53 | ||
54 | // The Logger interface requires implementations to be safe for concurrent | |
55 | // use by multiple goroutines. For this implementation that means making | |
56 | // only one call to l.w.Write() for each call to Log. | |
57 | if _, err := l.w.Write(enc.buf.Bytes()); err != nil { | |
58 | return err | |
59 | } | |
60 | return nil | |
61 | } |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "errors" | |
5 | "io/ioutil" | |
6 | "testing" | |
7 | ||
8 | "github.com/go-kit/kit/log" | |
9 | "github.com/go-logfmt/logfmt" | |
10 | ) | |
11 | ||
12 | func TestLogfmtLogger(t *testing.T) { | |
13 | t.Parallel() | |
14 | buf := &bytes.Buffer{} | |
15 | logger := log.NewLogfmtLogger(buf) | |
16 | ||
17 | if err := logger.Log("hello", "world"); err != nil { | |
18 | t.Fatal(err) | |
19 | } | |
20 | if want, have := "hello=world\n", buf.String(); want != have { | |
21 | t.Errorf("want %#v, have %#v", want, have) | |
22 | } | |
23 | ||
24 | buf.Reset() | |
25 | if err := logger.Log("a", 1, "err", errors.New("error")); err != nil { | |
26 | t.Fatal(err) | |
27 | } | |
28 | if want, have := "a=1 err=error\n", buf.String(); want != have { | |
29 | t.Errorf("want %#v, have %#v", want, have) | |
30 | } | |
31 | ||
32 | buf.Reset() | |
33 | if err := logger.Log("std_map", map[int]int{1: 2}, "my_map", mymap{0: 0}); err != nil { | |
34 | t.Fatal(err) | |
35 | } | |
36 | if want, have := "std_map=\""+logfmt.ErrUnsupportedValueType.Error()+"\" my_map=special_behavior\n", buf.String(); want != have { | |
37 | t.Errorf("want %#v, have %#v", want, have) | |
38 | } | |
39 | } | |
40 | ||
41 | func BenchmarkLogfmtLoggerSimple(b *testing.B) { | |
42 | benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), baseMessage) | |
43 | } | |
44 | ||
45 | func BenchmarkLogfmtLoggerContextual(b *testing.B) { | |
46 | benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), withMessage) | |
47 | } | |
48 | ||
49 | func TestLogfmtLoggerConcurrency(t *testing.T) { | |
50 | t.Parallel() | |
51 | testConcurrency(t, log.NewLogfmtLogger(ioutil.Discard), 10000) | |
52 | } | |
53 | ||
54 | type mymap map[int]int | |
55 | ||
56 | func (m mymap) String() string { return "special_behavior" } |
5 | 5 | "errors" |
6 | 6 | "fmt" |
7 | 7 | |
8 | "github.com/go-kit/kit/log" | |
8 | "github.com/go-kit/log" | |
9 | 9 | "github.com/sirupsen/logrus" |
10 | 10 | ) |
11 | 11 |
0 | 0 | package log |
1 | 1 | |
2 | type nopLogger struct{} | |
2 | import "github.com/go-kit/log" | |
3 | 3 | |
4 | 4 | // NewNopLogger returns a logger that doesn't do anything. |
5 | func NewNopLogger() Logger { return nopLogger{} } | |
6 | ||
7 | func (nopLogger) Log(...interface{}) error { return nil } | |
5 | func NewNopLogger() Logger { | |
6 | return log.NewNopLogger() | |
7 | } |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | ) | |
7 | ||
8 | func TestNopLogger(t *testing.T) { | |
9 | t.Parallel() | |
10 | logger := log.NewNopLogger() | |
11 | if err := logger.Log("abc", 123); err != nil { | |
12 | t.Error(err) | |
13 | } | |
14 | if err := log.With(logger, "def", "ghi").Log(); err != nil { | |
15 | t.Error(err) | |
16 | } | |
17 | } | |
18 | ||
19 | func BenchmarkNopLoggerSimple(b *testing.B) { | |
20 | benchmarkRunner(b, log.NewNopLogger(), baseMessage) | |
21 | } | |
22 | ||
23 | func BenchmarkNopLoggerContextual(b *testing.B) { | |
24 | benchmarkRunner(b, log.NewNopLogger(), withMessage) | |
25 | } |
0 | 0 | package log |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
4 | 3 | "io" |
5 | "log" | |
6 | "regexp" | |
7 | "strings" | |
4 | ||
5 | "github.com/go-kit/log" | |
8 | 6 | ) |
9 | 7 | |
10 | 8 | // StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's |
13 | 11 | // |
14 | 12 | // If you have any choice in the matter, you shouldn't use this. Prefer to |
15 | 13 | // redirect the stdlib log to the Go kit logger via NewStdlibAdapter. |
16 | type StdlibWriter struct{} | |
17 | ||
18 | // Write implements io.Writer. | |
19 | func (w StdlibWriter) Write(p []byte) (int, error) { | |
20 | log.Print(strings.TrimSpace(string(p))) | |
21 | return len(p), nil | |
22 | } | |
14 | type StdlibWriter = log.StdlibWriter | |
23 | 15 | |
24 | 16 | // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib |
25 | 17 | // logger's SetOutput. It will extract date/timestamps, filenames, and |
26 | 18 | // messages, and place them under relevant keys. |
27 | type StdlibAdapter struct { | |
28 | Logger | |
29 | timestampKey string | |
30 | fileKey string | |
31 | messageKey string | |
32 | prefix string | |
33 | joinPrefixToMsg bool | |
34 | } | |
19 | type StdlibAdapter = log.StdlibAdapter | |
35 | 20 | |
36 | 21 | // StdlibAdapterOption sets a parameter for the StdlibAdapter. |
37 | type StdlibAdapterOption func(*StdlibAdapter) | |
22 | type StdlibAdapterOption = log.StdlibAdapterOption | |
38 | 23 | |
39 | 24 | // TimestampKey sets the key for the timestamp field. By default, it's "ts". |
40 | 25 | func TimestampKey(key string) StdlibAdapterOption { |
41 | return func(a *StdlibAdapter) { a.timestampKey = key } | |
26 | return log.TimestampKey(key) | |
42 | 27 | } |
43 | 28 | |
44 | 29 | // FileKey sets the key for the file and line field. By default, it's "caller". |
45 | 30 | func FileKey(key string) StdlibAdapterOption { |
46 | return func(a *StdlibAdapter) { a.fileKey = key } | |
31 | return log.FileKey(key) | |
47 | 32 | } |
48 | 33 | |
49 | 34 | // MessageKey sets the key for the actual log message. By default, it's "msg". |
50 | 35 | func MessageKey(key string) StdlibAdapterOption { |
51 | return func(a *StdlibAdapter) { a.messageKey = key } | |
36 | return log.MessageKey(key) | |
52 | 37 | } |
53 | 38 | |
54 | 39 | // Prefix configures the adapter to parse a prefix from stdlib log events. If |
58 | 43 | // By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to |
59 | 44 | // true if you want to include the parsed prefix in the msg. |
60 | 45 | func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption { |
61 | return func(a *StdlibAdapter) { a.prefix = prefix; a.joinPrefixToMsg = joinPrefixToMsg } | |
46 | return log.Prefix(prefix, joinPrefixToMsg) | |
62 | 47 | } |
63 | 48 | |
64 | 49 | // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed |
65 | 50 | // logger. It's designed to be passed to log.SetOutput. |
66 | 51 | func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { |
67 | a := StdlibAdapter{ | |
68 | Logger: logger, | |
69 | timestampKey: "ts", | |
70 | fileKey: "caller", | |
71 | messageKey: "msg", | |
72 | } | |
73 | for _, option := range options { | |
74 | option(&a) | |
75 | } | |
76 | return a | |
52 | return log.NewStdlibAdapter(logger, options...) | |
77 | 53 | } |
78 | ||
79 | func (a StdlibAdapter) Write(p []byte) (int, error) { | |
80 | p = a.handlePrefix(p) | |
81 | ||
82 | result := subexps(p) | |
83 | keyvals := []interface{}{} | |
84 | var timestamp string | |
85 | if date, ok := result["date"]; ok && date != "" { | |
86 | timestamp = date | |
87 | } | |
88 | if time, ok := result["time"]; ok && time != "" { | |
89 | if timestamp != "" { | |
90 | timestamp += " " | |
91 | } | |
92 | timestamp += time | |
93 | } | |
94 | if timestamp != "" { | |
95 | keyvals = append(keyvals, a.timestampKey, timestamp) | |
96 | } | |
97 | if file, ok := result["file"]; ok && file != "" { | |
98 | keyvals = append(keyvals, a.fileKey, file) | |
99 | } | |
100 | if msg, ok := result["msg"]; ok { | |
101 | msg = a.handleMessagePrefix(msg) | |
102 | keyvals = append(keyvals, a.messageKey, msg) | |
103 | } | |
104 | if err := a.Logger.Log(keyvals...); err != nil { | |
105 | return 0, err | |
106 | } | |
107 | return len(p), nil | |
108 | } | |
109 | ||
110 | func (a StdlibAdapter) handlePrefix(p []byte) []byte { | |
111 | if a.prefix != "" { | |
112 | p = bytes.TrimPrefix(p, []byte(a.prefix)) | |
113 | } | |
114 | return p | |
115 | } | |
116 | ||
117 | func (a StdlibAdapter) handleMessagePrefix(msg string) string { | |
118 | if a.prefix == "" { | |
119 | return msg | |
120 | } | |
121 | ||
122 | msg = strings.TrimPrefix(msg, a.prefix) | |
123 | if a.joinPrefixToMsg { | |
124 | msg = a.prefix + msg | |
125 | } | |
126 | return msg | |
127 | } | |
128 | ||
129 | const ( | |
130 | logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` | |
131 | logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?` | |
132 | logRegexpFile = `(?P<file>.+?:[0-9]+)?` | |
133 | logRegexpMsg = `(: )?(?P<msg>(?s:.*))` | |
134 | ) | |
135 | ||
136 | var ( | |
137 | logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg) | |
138 | ) | |
139 | ||
140 | func subexps(line []byte) map[string]string { | |
141 | m := logRegexp.FindSubmatch(line) | |
142 | if len(m) < len(logRegexp.SubexpNames()) { | |
143 | return map[string]string{} | |
144 | } | |
145 | result := map[string]string{} | |
146 | for i, name := range logRegexp.SubexpNames() { | |
147 | result[name] = strings.TrimRight(string(m[i]), "\n") | |
148 | } | |
149 | return result | |
150 | } |
0 | package log | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "fmt" | |
5 | "log" | |
6 | "testing" | |
7 | "time" | |
8 | ) | |
9 | ||
10 | func TestStdlibWriter(t *testing.T) { | |
11 | buf := &bytes.Buffer{} | |
12 | log.SetOutput(buf) | |
13 | log.SetFlags(log.LstdFlags) | |
14 | logger := NewLogfmtLogger(StdlibWriter{}) | |
15 | logger.Log("key", "val") | |
16 | timestamp := time.Now().Format("2006/01/02 15:04:05") | |
17 | if want, have := timestamp+" key=val\n", buf.String(); want != have { | |
18 | t.Errorf("want %q, have %q", want, have) | |
19 | } | |
20 | } | |
21 | ||
22 | func TestStdlibAdapterUsage(t *testing.T) { | |
23 | buf := &bytes.Buffer{} | |
24 | logger := NewLogfmtLogger(buf) | |
25 | writer := NewStdlibAdapter(logger) | |
26 | stdlog := log.New(writer, "", 0) | |
27 | ||
28 | now := time.Now() | |
29 | date := now.Format("2006/01/02") | |
30 | time := now.Format("15:04:05") | |
31 | ||
32 | for flag, want := range map[int]string{ | |
33 | 0: "msg=hello\n", | |
34 | log.Ldate: "ts=" + date + " msg=hello\n", | |
35 | log.Ltime: "ts=" + time + " msg=hello\n", | |
36 | log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" msg=hello\n", | |
37 | log.Lshortfile: "caller=stdlib_test.go:44 msg=hello\n", | |
38 | log.Lshortfile | log.Ldate: "ts=" + date + " caller=stdlib_test.go:44 msg=hello\n", | |
39 | log.Lshortfile | log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" caller=stdlib_test.go:44 msg=hello\n", | |
40 | } { | |
41 | buf.Reset() | |
42 | stdlog.SetFlags(flag) | |
43 | stdlog.Print("hello") | |
44 | if have := buf.String(); want != have { | |
45 | t.Errorf("flag=%d: want %#v, have %#v", flag, want, have) | |
46 | } | |
47 | } | |
48 | } | |
49 | ||
50 | func TestStdLibAdapterExtraction(t *testing.T) { | |
51 | buf := &bytes.Buffer{} | |
52 | logger := NewLogfmtLogger(buf) | |
53 | writer := NewStdlibAdapter(logger) | |
54 | for input, want := range map[string]string{ | |
55 | "hello": "msg=hello\n", | |
56 | "2009/01/23: hello": "ts=2009/01/23 msg=hello\n", | |
57 | "2009/01/23 01:23:23: hello": "ts=\"2009/01/23 01:23:23\" msg=hello\n", | |
58 | "01:23:23: hello": "ts=01:23:23 msg=hello\n", | |
59 | "2009/01/23 01:23:23.123123: hello": "ts=\"2009/01/23 01:23:23.123123\" msg=hello\n", | |
60 | "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23.123123\" caller=/a/b/c/d.go:23 msg=hello\n", | |
61 | "01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 caller=/a/b/c/d.go:23 msg=hello\n", | |
62 | "2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23\" caller=/a/b/c/d.go:23 msg=hello\n", | |
63 | "2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 caller=/a/b/c/d.go:23 msg=hello\n", | |
64 | "/a/b/c/d.go:23: hello": "caller=/a/b/c/d.go:23 msg=hello\n", | |
65 | } { | |
66 | buf.Reset() | |
67 | fmt.Fprint(writer, input) | |
68 | if have := buf.String(); want != have { | |
69 | t.Errorf("%q: want %#v, have %#v", input, want, have) | |
70 | } | |
71 | } | |
72 | } | |
73 | ||
74 | func TestStdLibAdapterPrefixedExtraction(t *testing.T) { | |
75 | buf := &bytes.Buffer{} | |
76 | logger := NewLogfmtLogger(buf) | |
77 | writer := NewStdlibAdapter(logger, Prefix("some prefix ", false)) | |
78 | for input, want := range map[string]string{ | |
79 | "some prefix hello": "msg=hello\n", | |
80 | "some prefix 2009/01/23: hello": "ts=2009/01/23 msg=hello\n", | |
81 | "some prefix 2009/01/23 01:23:23: hello": "ts=\"2009/01/23 01:23:23\" msg=hello\n", | |
82 | "some prefix 01:23:23: hello": "ts=01:23:23 msg=hello\n", | |
83 | "some prefix 2009/01/23 01:23:23.123123: hello": "ts=\"2009/01/23 01:23:23.123123\" msg=hello\n", | |
84 | "some prefix 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23.123123\" caller=/a/b/c/d.go:23 msg=hello\n", | |
85 | "some prefix 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 caller=/a/b/c/d.go:23 msg=hello\n", | |
86 | "some prefix 2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23\" caller=/a/b/c/d.go:23 msg=hello\n", | |
87 | "some prefix 2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 caller=/a/b/c/d.go:23 msg=hello\n", | |
88 | "some prefix /a/b/c/d.go:23: hello": "caller=/a/b/c/d.go:23 msg=hello\n", | |
89 | "/a/b/c/d.go:23: some prefix hello": "caller=/a/b/c/d.go:23 msg=hello\n", | |
90 | } { | |
91 | buf.Reset() | |
92 | fmt.Fprint(writer, input) | |
93 | if have := buf.String(); want != have { | |
94 | t.Errorf("%q: want %#v, have %#v", input, want, have) | |
95 | } | |
96 | } | |
97 | } | |
98 | ||
99 | func TestStdLibAdapterPrefixedExtractionWithJoinToMessage(t *testing.T) { | |
100 | buf := &bytes.Buffer{} | |
101 | logger := NewLogfmtLogger(buf) | |
102 | writer := NewStdlibAdapter(logger, Prefix("some prefix ", true)) | |
103 | for input, want := range map[string]string{ | |
104 | "some prefix hello": "msg=\"some prefix hello\"\n", | |
105 | "some prefix 2009/01/23: hello": "ts=2009/01/23 msg=\"some prefix hello\"\n", | |
106 | "some prefix 2009/01/23 01:23:23: hello": "ts=\"2009/01/23 01:23:23\" msg=\"some prefix hello\"\n", | |
107 | "some prefix 01:23:23: hello": "ts=01:23:23 msg=\"some prefix hello\"\n", | |
108 | "some prefix 2009/01/23 01:23:23.123123: hello": "ts=\"2009/01/23 01:23:23.123123\" msg=\"some prefix hello\"\n", | |
109 | "some prefix 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23.123123\" caller=/a/b/c/d.go:23 msg=\"some prefix hello\"\n", | |
110 | "some prefix 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 caller=/a/b/c/d.go:23 msg=\"some prefix hello\"\n", | |
111 | "some prefix 2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23\" caller=/a/b/c/d.go:23 msg=\"some prefix hello\"\n", | |
112 | "some prefix 2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 caller=/a/b/c/d.go:23 msg=\"some prefix hello\"\n", | |
113 | "some prefix /a/b/c/d.go:23: hello": "caller=/a/b/c/d.go:23 msg=\"some prefix hello\"\n", | |
114 | "/a/b/c/d.go:23: some prefix hello": "caller=/a/b/c/d.go:23 msg=\"some prefix hello\"\n", | |
115 | } { | |
116 | buf.Reset() | |
117 | fmt.Fprint(writer, input) | |
118 | if have := buf.String(); want != have { | |
119 | t.Errorf("%q: want %#v, have %#v", input, want, have) | |
120 | } | |
121 | } | |
122 | } | |
123 | ||
124 | func TestStdlibAdapterSubexps(t *testing.T) { | |
125 | for input, wantMap := range map[string]map[string]string{ | |
126 | "hello world": { | |
127 | "date": "", | |
128 | "time": "", | |
129 | "file": "", | |
130 | "msg": "hello world", | |
131 | }, | |
132 | "hello\nworld": { | |
133 | "date": "", | |
134 | "time": "", | |
135 | "file": "", | |
136 | "msg": "hello\nworld", | |
137 | }, | |
138 | "2009/01/23: hello world": { | |
139 | "date": "2009/01/23", | |
140 | "time": "", | |
141 | "file": "", | |
142 | "msg": "hello world", | |
143 | }, | |
144 | "2009/01/23 01:23:23: hello world": { | |
145 | "date": "2009/01/23", | |
146 | "time": "01:23:23", | |
147 | "file": "", | |
148 | "msg": "hello world", | |
149 | }, | |
150 | "01:23:23: hello world": { | |
151 | "date": "", | |
152 | "time": "01:23:23", | |
153 | "file": "", | |
154 | "msg": "hello world", | |
155 | }, | |
156 | "2009/01/23 01:23:23.123123: hello world": { | |
157 | "date": "2009/01/23", | |
158 | "time": "01:23:23.123123", | |
159 | "file": "", | |
160 | "msg": "hello world", | |
161 | }, | |
162 | "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello world": { | |
163 | "date": "2009/01/23", | |
164 | "time": "01:23:23.123123", | |
165 | "file": "/a/b/c/d.go:23", | |
166 | "msg": "hello world", | |
167 | }, | |
168 | "01:23:23.123123 /a/b/c/d.go:23: hello world": { | |
169 | "date": "", | |
170 | "time": "01:23:23.123123", | |
171 | "file": "/a/b/c/d.go:23", | |
172 | "msg": "hello world", | |
173 | }, | |
174 | "2009/01/23 01:23:23 /a/b/c/d.go:23: hello world": { | |
175 | "date": "2009/01/23", | |
176 | "time": "01:23:23", | |
177 | "file": "/a/b/c/d.go:23", | |
178 | "msg": "hello world", | |
179 | }, | |
180 | "2009/01/23 /a/b/c/d.go:23: hello world": { | |
181 | "date": "2009/01/23", | |
182 | "time": "", | |
183 | "file": "/a/b/c/d.go:23", | |
184 | "msg": "hello world", | |
185 | }, | |
186 | "/a/b/c/d.go:23: hello world": { | |
187 | "date": "", | |
188 | "time": "", | |
189 | "file": "/a/b/c/d.go:23", | |
190 | "msg": "hello world", | |
191 | }, | |
192 | "2009/01/23 01:23:23.123123 C:/a/b/c/d.go:23: hello world": { | |
193 | "date": "2009/01/23", | |
194 | "time": "01:23:23.123123", | |
195 | "file": "C:/a/b/c/d.go:23", | |
196 | "msg": "hello world", | |
197 | }, | |
198 | "01:23:23.123123 C:/a/b/c/d.go:23: hello world": { | |
199 | "date": "", | |
200 | "time": "01:23:23.123123", | |
201 | "file": "C:/a/b/c/d.go:23", | |
202 | "msg": "hello world", | |
203 | }, | |
204 | "2009/01/23 01:23:23 C:/a/b/c/d.go:23: hello world": { | |
205 | "date": "2009/01/23", | |
206 | "time": "01:23:23", | |
207 | "file": "C:/a/b/c/d.go:23", | |
208 | "msg": "hello world", | |
209 | }, | |
210 | "2009/01/23 C:/a/b/c/d.go:23: hello world": { | |
211 | "date": "2009/01/23", | |
212 | "time": "", | |
213 | "file": "C:/a/b/c/d.go:23", | |
214 | "msg": "hello world", | |
215 | }, | |
216 | "C:/a/b/c/d.go:23: hello world": { | |
217 | "date": "", | |
218 | "time": "", | |
219 | "file": "C:/a/b/c/d.go:23", | |
220 | "msg": "hello world", | |
221 | }, | |
222 | "2009/01/23 01:23:23.123123 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": { | |
223 | "date": "2009/01/23", | |
224 | "time": "01:23:23.123123", | |
225 | "file": "C:/a/b/c/d.go:23", | |
226 | "msg": ":.;<>_#{[]}\"\\", | |
227 | }, | |
228 | "01:23:23.123123 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": { | |
229 | "date": "", | |
230 | "time": "01:23:23.123123", | |
231 | "file": "C:/a/b/c/d.go:23", | |
232 | "msg": ":.;<>_#{[]}\"\\", | |
233 | }, | |
234 | "2009/01/23 01:23:23 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": { | |
235 | "date": "2009/01/23", | |
236 | "time": "01:23:23", | |
237 | "file": "C:/a/b/c/d.go:23", | |
238 | "msg": ":.;<>_#{[]}\"\\", | |
239 | }, | |
240 | "2009/01/23 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": { | |
241 | "date": "2009/01/23", | |
242 | "time": "", | |
243 | "file": "C:/a/b/c/d.go:23", | |
244 | "msg": ":.;<>_#{[]}\"\\", | |
245 | }, | |
246 | "C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": { | |
247 | "date": "", | |
248 | "time": "", | |
249 | "file": "C:/a/b/c/d.go:23", | |
250 | "msg": ":.;<>_#{[]}\"\\", | |
251 | }, | |
252 | } { | |
253 | haveMap := subexps([]byte(input)) | |
254 | for key, want := range wantMap { | |
255 | if have := haveMap[key]; want != have { | |
256 | t.Errorf("%q: %q: want %q, have %q", input, key, want, have) | |
257 | } | |
258 | } | |
259 | } | |
260 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "io" |
4 | "sync" | |
5 | "sync/atomic" | |
4 | ||
5 | "github.com/go-kit/log" | |
6 | 6 | ) |
7 | 7 | |
8 | 8 | // SwapLogger wraps another logger that may be safely replaced while other |
11 | 11 | // |
12 | 12 | // SwapLogger serves well as a package global logger that can be changed by |
13 | 13 | // importers. |
14 | type SwapLogger struct { | |
15 | logger atomic.Value | |
16 | } | |
17 | ||
18 | type loggerStruct struct { | |
19 | Logger | |
20 | } | |
21 | ||
22 | // Log implements the Logger interface by forwarding keyvals to the currently | |
23 | // wrapped logger. It does not log anything if the wrapped logger is nil. | |
24 | func (l *SwapLogger) Log(keyvals ...interface{}) error { | |
25 | s, ok := l.logger.Load().(loggerStruct) | |
26 | if !ok || s.Logger == nil { | |
27 | return nil | |
28 | } | |
29 | return s.Log(keyvals...) | |
30 | } | |
31 | ||
32 | // Swap replaces the currently wrapped logger with logger. Swap may be called | |
33 | // concurrently with calls to Log from other goroutines. | |
34 | func (l *SwapLogger) Swap(logger Logger) { | |
35 | l.logger.Store(loggerStruct{logger}) | |
36 | } | |
14 | type SwapLogger = log.SwapLogger | |
37 | 15 | |
38 | 16 | // NewSyncWriter returns a new writer that is safe for concurrent use by |
39 | 17 | // multiple goroutines. Writes to the returned writer are passed on to w. If |
46 | 24 | // Fd() uintptr |
47 | 25 | // } |
48 | 26 | func NewSyncWriter(w io.Writer) io.Writer { |
49 | switch w := w.(type) { | |
50 | case fdWriter: | |
51 | return &fdSyncWriter{fdWriter: w} | |
52 | default: | |
53 | return &syncWriter{Writer: w} | |
54 | } | |
55 | } | |
56 | ||
57 | // syncWriter synchronizes concurrent writes to an io.Writer. | |
58 | type syncWriter struct { | |
59 | sync.Mutex | |
60 | io.Writer | |
61 | } | |
62 | ||
63 | // Write writes p to the underlying io.Writer. If another write is already in | |
64 | // progress, the calling goroutine blocks until the syncWriter is available. | |
65 | func (w *syncWriter) Write(p []byte) (n int, err error) { | |
66 | w.Lock() | |
67 | defer w.Unlock() | |
68 | return w.Writer.Write(p) | |
69 | } | |
70 | ||
71 | // fdWriter is an io.Writer that also has an Fd method. The most common | |
72 | // example of an fdWriter is an *os.File. | |
73 | type fdWriter interface { | |
74 | io.Writer | |
75 | Fd() uintptr | |
76 | } | |
77 | ||
78 | // fdSyncWriter synchronizes concurrent writes to an fdWriter. | |
79 | type fdSyncWriter struct { | |
80 | sync.Mutex | |
81 | fdWriter | |
82 | } | |
83 | ||
84 | // Write writes p to the underlying io.Writer. If another write is already in | |
85 | // progress, the calling goroutine blocks until the fdSyncWriter is available. | |
86 | func (w *fdSyncWriter) Write(p []byte) (n int, err error) { | |
87 | w.Lock() | |
88 | defer w.Unlock() | |
89 | return w.fdWriter.Write(p) | |
90 | } | |
91 | ||
92 | // syncLogger provides concurrent safe logging for another Logger. | |
93 | type syncLogger struct { | |
94 | mu sync.Mutex | |
95 | logger Logger | |
27 | return log.NewSyncWriter(w) | |
96 | 28 | } |
97 | 29 | |
98 | 30 | // NewSyncLogger returns a logger that synchronizes concurrent use of the |
100 | 32 | // only one goroutine will be allowed to log to the wrapped logger at a time. |
101 | 33 | // The other goroutines will block until the logger is available. |
102 | 34 | func NewSyncLogger(logger Logger) Logger { |
103 | return &syncLogger{logger: logger} | |
35 | return log.NewSyncLogger(logger) | |
104 | 36 | } |
105 | ||
106 | // Log logs keyvals to the underlying Logger. If another log is already in | |
107 | // progress, the calling goroutine blocks until the syncLogger is available. | |
108 | func (l *syncLogger) Log(keyvals ...interface{}) error { | |
109 | l.mu.Lock() | |
110 | defer l.mu.Unlock() | |
111 | return l.logger.Log(keyvals...) | |
112 | } |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "io" | |
5 | "os" | |
6 | "testing" | |
7 | ||
8 | "github.com/go-kit/kit/log" | |
9 | ) | |
10 | ||
11 | func TestSwapLogger(t *testing.T) { | |
12 | t.Parallel() | |
13 | var logger log.SwapLogger | |
14 | ||
15 | // Zero value does not panic or error. | |
16 | err := logger.Log("k", "v") | |
17 | if got, want := err, error(nil); got != want { | |
18 | t.Errorf("got %v, want %v", got, want) | |
19 | } | |
20 | ||
21 | buf := &bytes.Buffer{} | |
22 | json := log.NewJSONLogger(buf) | |
23 | logger.Swap(json) | |
24 | ||
25 | if err := logger.Log("k", "v"); err != nil { | |
26 | t.Error(err) | |
27 | } | |
28 | if got, want := buf.String(), `{"k":"v"}`+"\n"; got != want { | |
29 | t.Errorf("got %v, want %v", got, want) | |
30 | } | |
31 | ||
32 | buf.Reset() | |
33 | prefix := log.NewLogfmtLogger(buf) | |
34 | logger.Swap(prefix) | |
35 | ||
36 | if err := logger.Log("k", "v"); err != nil { | |
37 | t.Error(err) | |
38 | } | |
39 | if got, want := buf.String(), "k=v\n"; got != want { | |
40 | t.Errorf("got %v, want %v", got, want) | |
41 | } | |
42 | ||
43 | buf.Reset() | |
44 | logger.Swap(nil) | |
45 | ||
46 | if err := logger.Log("k", "v"); err != nil { | |
47 | t.Error(err) | |
48 | } | |
49 | if got, want := buf.String(), ""; got != want { | |
50 | t.Errorf("got %v, want %v", got, want) | |
51 | } | |
52 | } | |
53 | ||
54 | func TestSwapLoggerConcurrency(t *testing.T) { | |
55 | t.Parallel() | |
56 | testConcurrency(t, &log.SwapLogger{}, 10000) | |
57 | } | |
58 | ||
59 | func TestSyncLoggerConcurrency(t *testing.T) { | |
60 | var w io.Writer | |
61 | w = &bytes.Buffer{} | |
62 | logger := log.NewLogfmtLogger(w) | |
63 | logger = log.NewSyncLogger(logger) | |
64 | testConcurrency(t, logger, 10000) | |
65 | } | |
66 | ||
67 | func TestSyncWriterConcurrency(t *testing.T) { | |
68 | var w io.Writer | |
69 | w = &bytes.Buffer{} | |
70 | w = log.NewSyncWriter(w) | |
71 | testConcurrency(t, log.NewLogfmtLogger(w), 10000) | |
72 | } | |
73 | ||
74 | func TestSyncWriterFd(t *testing.T) { | |
75 | _, ok := log.NewSyncWriter(os.Stdout).(interface { | |
76 | Fd() uintptr | |
77 | }) | |
78 | ||
79 | if !ok { | |
80 | t.Error("NewSyncWriter does not pass through Fd method") | |
81 | } | |
82 | } | |
83 | ||
84 | func TestSyncLoggerPanic(t *testing.T) { | |
85 | var logger log.Logger | |
86 | logger = log.LoggerFunc(func(...interface{}) error { panic("!") }) | |
87 | logger = log.NewSyncLogger(logger) | |
88 | ||
89 | f := func() { | |
90 | defer func() { | |
91 | if x := recover(); x != nil { | |
92 | t.Log(x) | |
93 | } | |
94 | }() | |
95 | logger.Log("hello", "world") | |
96 | } | |
97 | ||
98 | f() | |
99 | f() // without defer Unlock, this one can deadlock | |
100 | } |
0 | // +build !windows | |
1 | // +build !plan9 | |
2 | // +build !nacl | |
0 | //go:build !windows && !plan9 && !nacl | |
1 | // +build !windows,!plan9,!nacl | |
3 | 2 | |
3 | // Deprecated: Use github.com/go-kit/log/syslog instead. | |
4 | 4 | package syslog |
5 | 5 | |
6 | 6 | import ( |
7 | "bytes" | |
8 | 7 | "io" |
9 | "sync" | |
10 | 8 | |
11 | gosyslog "log/syslog" | |
12 | ||
13 | "github.com/go-kit/kit/log" | |
14 | "github.com/go-kit/kit/log/level" | |
9 | "github.com/go-kit/log" | |
10 | "github.com/go-kit/log/syslog" | |
15 | 11 | ) |
16 | 12 | |
17 | 13 | // SyslogWriter is an interface wrapping stdlib syslog Writer. |
18 | type SyslogWriter interface { | |
19 | Write([]byte) (int, error) | |
20 | Close() error | |
21 | Emerg(string) error | |
22 | Alert(string) error | |
23 | Crit(string) error | |
24 | Err(string) error | |
25 | Warning(string) error | |
26 | Notice(string) error | |
27 | Info(string) error | |
28 | Debug(string) error | |
29 | } | |
14 | type SyslogWriter = syslog.SyslogWriter | |
30 | 15 | |
31 | 16 | // NewSyslogLogger returns a new Logger which writes to syslog in syslog format. |
32 | 17 | // The body of the log message is the formatted output from the Logger returned |
33 | 18 | // by newLogger. |
34 | 19 | func NewSyslogLogger(w SyslogWriter, newLogger func(io.Writer) log.Logger, options ...Option) log.Logger { |
35 | l := &syslogLogger{ | |
36 | w: w, | |
37 | newLogger: newLogger, | |
38 | prioritySelector: defaultPrioritySelector, | |
39 | bufPool: sync.Pool{New: func() interface{} { | |
40 | return &loggerBuf{} | |
41 | }}, | |
42 | } | |
43 | ||
44 | for _, option := range options { | |
45 | option(l) | |
46 | } | |
47 | ||
48 | return l | |
49 | } | |
50 | ||
51 | type syslogLogger struct { | |
52 | w SyslogWriter | |
53 | newLogger func(io.Writer) log.Logger | |
54 | prioritySelector PrioritySelector | |
55 | bufPool sync.Pool | |
56 | } | |
57 | ||
58 | func (l *syslogLogger) Log(keyvals ...interface{}) error { | |
59 | level := l.prioritySelector(keyvals...) | |
60 | ||
61 | lb := l.getLoggerBuf() | |
62 | defer l.putLoggerBuf(lb) | |
63 | if err := lb.logger.Log(keyvals...); err != nil { | |
64 | return err | |
65 | } | |
66 | ||
67 | switch level { | |
68 | case gosyslog.LOG_EMERG: | |
69 | return l.w.Emerg(lb.buf.String()) | |
70 | case gosyslog.LOG_ALERT: | |
71 | return l.w.Alert(lb.buf.String()) | |
72 | case gosyslog.LOG_CRIT: | |
73 | return l.w.Crit(lb.buf.String()) | |
74 | case gosyslog.LOG_ERR: | |
75 | return l.w.Err(lb.buf.String()) | |
76 | case gosyslog.LOG_WARNING: | |
77 | return l.w.Warning(lb.buf.String()) | |
78 | case gosyslog.LOG_NOTICE: | |
79 | return l.w.Notice(lb.buf.String()) | |
80 | case gosyslog.LOG_INFO: | |
81 | return l.w.Info(lb.buf.String()) | |
82 | case gosyslog.LOG_DEBUG: | |
83 | return l.w.Debug(lb.buf.String()) | |
84 | default: | |
85 | _, err := l.w.Write(lb.buf.Bytes()) | |
86 | return err | |
87 | } | |
88 | } | |
89 | ||
90 | type loggerBuf struct { | |
91 | buf *bytes.Buffer | |
92 | logger log.Logger | |
93 | } | |
94 | ||
95 | func (l *syslogLogger) getLoggerBuf() *loggerBuf { | |
96 | lb := l.bufPool.Get().(*loggerBuf) | |
97 | if lb.buf == nil { | |
98 | lb.buf = &bytes.Buffer{} | |
99 | lb.logger = l.newLogger(lb.buf) | |
100 | } else { | |
101 | lb.buf.Reset() | |
102 | } | |
103 | return lb | |
104 | } | |
105 | ||
106 | func (l *syslogLogger) putLoggerBuf(lb *loggerBuf) { | |
107 | l.bufPool.Put(lb) | |
20 | return syslog.NewSyslogLogger(w, newLogger, options...) | |
108 | 21 | } |
109 | 22 | |
110 | 23 | // Option sets a parameter for syslog loggers. |
111 | type Option func(*syslogLogger) | |
24 | type Option = syslog.Option | |
112 | 25 | |
113 | 26 | // PrioritySelector inspects the list of keyvals and selects a syslog priority. |
114 | type PrioritySelector func(keyvals ...interface{}) gosyslog.Priority | |
27 | type PrioritySelector = syslog.PrioritySelector | |
115 | 28 | |
116 | 29 | // PrioritySelectorOption sets priority selector function to choose syslog |
117 | 30 | // priority. |
118 | 31 | func PrioritySelectorOption(selector PrioritySelector) Option { |
119 | return func(l *syslogLogger) { l.prioritySelector = selector } | |
32 | return syslog.PrioritySelectorOption(selector) | |
120 | 33 | } |
121 | ||
122 | func defaultPrioritySelector(keyvals ...interface{}) gosyslog.Priority { | |
123 | l := len(keyvals) | |
124 | for i := 0; i < l; i += 2 { | |
125 | if keyvals[i] == level.Key() { | |
126 | var val interface{} | |
127 | if i+1 < l { | |
128 | val = keyvals[i+1] | |
129 | } | |
130 | if v, ok := val.(level.Value); ok { | |
131 | switch v { | |
132 | case level.DebugValue(): | |
133 | return gosyslog.LOG_DEBUG | |
134 | case level.InfoValue(): | |
135 | return gosyslog.LOG_INFO | |
136 | case level.WarnValue(): | |
137 | return gosyslog.LOG_WARNING | |
138 | case level.ErrorValue(): | |
139 | return gosyslog.LOG_ERR | |
140 | } | |
141 | } | |
142 | } | |
143 | } | |
144 | ||
145 | return gosyslog.LOG_INFO | |
146 | } |
0 | // +build !windows | |
1 | // +build !plan9 | |
2 | // +build !nacl | |
3 | ||
4 | package syslog | |
5 | ||
6 | import ( | |
7 | "fmt" | |
8 | "reflect" | |
9 | "testing" | |
10 | ||
11 | gosyslog "log/syslog" | |
12 | ||
13 | "github.com/go-kit/kit/log" | |
14 | "github.com/go-kit/kit/log/level" | |
15 | ) | |
16 | ||
17 | func TestSyslogLoggerDefaultPrioritySelector(t *testing.T) { | |
18 | w := &testSyslogWriter{} | |
19 | l := NewSyslogLogger(w, log.NewLogfmtLogger) | |
20 | ||
21 | l.Log("level", level.WarnValue(), "msg", "one") | |
22 | l.Log("level", "undefined", "msg", "two") | |
23 | l.Log("level", level.InfoValue(), "msg", "three") | |
24 | l.Log("level", level.ErrorValue(), "msg", "four") | |
25 | l.Log("level", level.DebugValue(), "msg", "five") | |
26 | ||
27 | l.Log("msg", "six", "level", level.ErrorValue()) | |
28 | l.Log("msg", "seven", "level", level.DebugValue()) | |
29 | l.Log("msg", "eight", "level", level.InfoValue()) | |
30 | l.Log("msg", "nine", "level", "undefined") | |
31 | l.Log("msg", "ten", "level", level.WarnValue()) | |
32 | ||
33 | l.Log("level", level.ErrorValue(), "msg") | |
34 | l.Log("msg", "eleven", "level") | |
35 | ||
36 | want := []string{ | |
37 | "warning: level=warn msg=one\n", | |
38 | "info: level=undefined msg=two\n", | |
39 | "info: level=info msg=three\n", | |
40 | "err: level=error msg=four\n", | |
41 | "debug: level=debug msg=five\n", | |
42 | ||
43 | "err: msg=six level=error\n", | |
44 | "debug: msg=seven level=debug\n", | |
45 | "info: msg=eight level=info\n", | |
46 | "info: msg=nine level=undefined\n", | |
47 | "warning: msg=ten level=warn\n", | |
48 | ||
49 | "err: level=error msg=null\n", | |
50 | "info: msg=eleven level=null\n", | |
51 | } | |
52 | have := w.writes | |
53 | if !reflect.DeepEqual(want, have) { | |
54 | t.Errorf("wrong writes: want %s, have %s", want, have) | |
55 | } | |
56 | } | |
57 | ||
58 | func TestSyslogLoggerExhaustivePrioritySelector(t *testing.T) { | |
59 | w := &testSyslogWriter{} | |
60 | selector := func(keyvals ...interface{}) gosyslog.Priority { | |
61 | for i := 0; i < len(keyvals); i += 2 { | |
62 | if keyvals[i] == level.Key() { | |
63 | if v, ok := keyvals[i+1].(string); ok { | |
64 | switch v { | |
65 | case "emergency": | |
66 | return gosyslog.LOG_EMERG | |
67 | case "alert": | |
68 | return gosyslog.LOG_ALERT | |
69 | case "critical": | |
70 | return gosyslog.LOG_CRIT | |
71 | case "error": | |
72 | return gosyslog.LOG_ERR | |
73 | case "warning": | |
74 | return gosyslog.LOG_WARNING | |
75 | case "notice": | |
76 | return gosyslog.LOG_NOTICE | |
77 | case "info": | |
78 | return gosyslog.LOG_INFO | |
79 | case "debug": | |
80 | return gosyslog.LOG_DEBUG | |
81 | } | |
82 | return gosyslog.LOG_LOCAL0 | |
83 | } | |
84 | } | |
85 | } | |
86 | return gosyslog.LOG_LOCAL0 | |
87 | } | |
88 | l := NewSyslogLogger(w, log.NewLogfmtLogger, PrioritySelectorOption(selector)) | |
89 | ||
90 | l.Log("level", "warning", "msg", "one") | |
91 | l.Log("level", "error", "msg", "two") | |
92 | l.Log("level", "critical", "msg", "three") | |
93 | l.Log("level", "debug", "msg", "four") | |
94 | l.Log("level", "info", "msg", "five") | |
95 | l.Log("level", "alert", "msg", "six") | |
96 | l.Log("level", "emergency", "msg", "seven") | |
97 | l.Log("level", "notice", "msg", "eight") | |
98 | l.Log("level", "unknown", "msg", "nine") | |
99 | ||
100 | want := []string{ | |
101 | "warning: level=warning msg=one\n", | |
102 | "err: level=error msg=two\n", | |
103 | "crit: level=critical msg=three\n", | |
104 | "debug: level=debug msg=four\n", | |
105 | "info: level=info msg=five\n", | |
106 | "alert: level=alert msg=six\n", | |
107 | "emerg: level=emergency msg=seven\n", | |
108 | "notice: level=notice msg=eight\n", | |
109 | "write: level=unknown msg=nine\n", | |
110 | } | |
111 | have := w.writes | |
112 | if !reflect.DeepEqual(want, have) { | |
113 | t.Errorf("wrong writes: want %s, have %s", want, have) | |
114 | } | |
115 | } | |
116 | ||
117 | type testSyslogWriter struct { | |
118 | writes []string | |
119 | } | |
120 | ||
121 | func (w *testSyslogWriter) Write(b []byte) (int, error) { | |
122 | msg := string(b) | |
123 | w.writes = append(w.writes, fmt.Sprintf("write: %s", msg)) | |
124 | return len(msg), nil | |
125 | } | |
126 | ||
127 | func (w *testSyslogWriter) Close() error { | |
128 | return nil | |
129 | } | |
130 | ||
131 | func (w *testSyslogWriter) Emerg(msg string) error { | |
132 | w.writes = append(w.writes, fmt.Sprintf("emerg: %s", msg)) | |
133 | return nil | |
134 | } | |
135 | ||
136 | func (w *testSyslogWriter) Alert(msg string) error { | |
137 | w.writes = append(w.writes, fmt.Sprintf("alert: %s", msg)) | |
138 | return nil | |
139 | } | |
140 | ||
141 | func (w *testSyslogWriter) Crit(msg string) error { | |
142 | w.writes = append(w.writes, fmt.Sprintf("crit: %s", msg)) | |
143 | return nil | |
144 | } | |
145 | ||
146 | func (w *testSyslogWriter) Err(msg string) error { | |
147 | w.writes = append(w.writes, fmt.Sprintf("err: %s", msg)) | |
148 | return nil | |
149 | } | |
150 | ||
151 | func (w *testSyslogWriter) Warning(msg string) error { | |
152 | w.writes = append(w.writes, fmt.Sprintf("warning: %s", msg)) | |
153 | return nil | |
154 | } | |
155 | ||
156 | func (w *testSyslogWriter) Notice(msg string) error { | |
157 | w.writes = append(w.writes, fmt.Sprintf("notice: %s", msg)) | |
158 | return nil | |
159 | } | |
160 | ||
161 | func (w *testSyslogWriter) Info(msg string) error { | |
162 | w.writes = append(w.writes, fmt.Sprintf("info: %s", msg)) | |
163 | return nil | |
164 | } | |
165 | ||
166 | func (w *testSyslogWriter) Debug(msg string) error { | |
167 | w.writes = append(w.writes, fmt.Sprintf("debug: %s", msg)) | |
168 | return nil | |
169 | } |
0 | The MIT License (MIT) | |
1 | ||
2 | Copyright (c) 2014 Simon Eskildsen | |
3 | ||
4 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | of this software and associated documentation files (the "Software"), to deal | |
6 | in the Software without restriction, including without limitation the rights | |
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | copies of the Software, and to permit persons to whom the Software is | |
9 | furnished to do so, subject to the following conditions: | |
10 | ||
11 | The above copyright notice and this permission notice shall be included in | |
12 | all copies or substantial portions of the Software. | |
13 | ||
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
20 | THE SOFTWARE. |
0 | 0 | package term |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
4 | "fmt" | |
5 | 3 | "io" |
6 | "sync" | |
7 | 4 | |
8 | "github.com/go-kit/kit/log" | |
5 | "github.com/go-kit/log" | |
6 | "github.com/go-kit/log/term" | |
9 | 7 | ) |
10 | 8 | |
11 | 9 | // Color represents an ANSI color. The zero value is Default. |
12 | type Color uint8 | |
10 | type Color = term.Color | |
13 | 11 | |
14 | 12 | // ANSI colors. |
15 | 13 | const ( |
16 | Default = Color(iota) | |
14 | Default = term.Default | |
17 | 15 | |
18 | Black | |
19 | DarkRed | |
20 | DarkGreen | |
21 | Brown | |
22 | DarkBlue | |
23 | DarkMagenta | |
24 | DarkCyan | |
25 | Gray | |
16 | Black = term.Black | |
17 | DarkRed = term.DarkRed | |
18 | DarkGreen = term.DarkGreen | |
19 | Brown = term.Brown | |
20 | DarkBlue = term.DarkBlue | |
21 | DarkMagenta = term.DarkMagenta | |
22 | DarkCyan = term.DarkCyan | |
23 | Gray = term.Gray | |
26 | 24 | |
27 | DarkGray | |
28 | Red | |
29 | Green | |
30 | Yellow | |
31 | Blue | |
32 | Magenta | |
33 | Cyan | |
34 | White | |
35 | ||
36 | numColors | |
25 | DarkGray = term.DarkGray | |
26 | Red = term.Red | |
27 | Green = term.Green | |
28 | Yellow = term.Yellow | |
29 | Blue = term.Blue | |
30 | Magenta = term.Magenta | |
31 | Cyan = term.Cyan | |
32 | White = term.White | |
37 | 33 | ) |
38 | 34 | |
39 | // For more on ANSI escape codes see | |
40 | // https://en.wikipedia.org/wiki/ANSI_escape_code. See in particular | |
41 | // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors. | |
42 | ||
43 | var ( | |
44 | resetColorBytes = []byte("\x1b[39;49;22m") | |
45 | fgColorBytes [][]byte | |
46 | bgColorBytes [][]byte | |
47 | ) | |
48 | ||
49 | func init() { | |
50 | // Default | |
51 | fgColorBytes = append(fgColorBytes, []byte("\x1b[39m")) | |
52 | bgColorBytes = append(bgColorBytes, []byte("\x1b[49m")) | |
53 | ||
54 | // dark colors | |
55 | for color := Black; color < DarkGray; color++ { | |
56 | fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 30+color-Black))) | |
57 | bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 40+color-Black))) | |
58 | } | |
59 | ||
60 | // bright colors | |
61 | for color := DarkGray; color < numColors; color++ { | |
62 | fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 30+color-DarkGray))) | |
63 | bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 40+color-DarkGray))) | |
64 | } | |
65 | } | |
66 | ||
67 | 35 | // FgBgColor represents a foreground and background color. |
68 | type FgBgColor struct { | |
69 | Fg, Bg Color | |
70 | } | |
71 | ||
72 | func (c FgBgColor) isZero() bool { | |
73 | return c.Fg == Default && c.Bg == Default | |
74 | } | |
36 | type FgBgColor = term.FgBgColor | |
75 | 37 | |
76 | 38 | // NewColorLogger returns a Logger which writes colored logs to w. ANSI color |
77 | 39 | // codes for the colors returned by color are added to the formatted output |
78 | 40 | // from the Logger returned by newLogger and the combined result written to w. |
79 | 41 | func NewColorLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger { |
80 | if color == nil { | |
81 | panic("color func nil") | |
82 | } | |
83 | return &colorLogger{ | |
84 | w: w, | |
85 | newLogger: newLogger, | |
86 | color: color, | |
87 | bufPool: sync.Pool{New: func() interface{} { return &loggerBuf{} }}, | |
88 | noColorLogger: newLogger(w), | |
89 | } | |
42 | return term.NewColorLogger(w, newLogger, color) | |
90 | 43 | } |
91 | ||
92 | type colorLogger struct { | |
93 | w io.Writer | |
94 | newLogger func(io.Writer) log.Logger | |
95 | color func(keyvals ...interface{}) FgBgColor | |
96 | bufPool sync.Pool | |
97 | noColorLogger log.Logger | |
98 | } | |
99 | ||
100 | func (l *colorLogger) Log(keyvals ...interface{}) error { | |
101 | color := l.color(keyvals...) | |
102 | if color.isZero() { | |
103 | return l.noColorLogger.Log(keyvals...) | |
104 | } | |
105 | ||
106 | lb := l.getLoggerBuf() | |
107 | defer l.putLoggerBuf(lb) | |
108 | if color.Fg != Default { | |
109 | lb.buf.Write(fgColorBytes[color.Fg]) | |
110 | } | |
111 | if color.Bg != Default { | |
112 | lb.buf.Write(bgColorBytes[color.Bg]) | |
113 | } | |
114 | err := lb.logger.Log(keyvals...) | |
115 | if err != nil { | |
116 | return err | |
117 | } | |
118 | if color.Fg != Default || color.Bg != Default { | |
119 | lb.buf.Write(resetColorBytes) | |
120 | } | |
121 | _, err = io.Copy(l.w, lb.buf) | |
122 | return err | |
123 | } | |
124 | ||
125 | type loggerBuf struct { | |
126 | buf *bytes.Buffer | |
127 | logger log.Logger | |
128 | } | |
129 | ||
130 | func (l *colorLogger) getLoggerBuf() *loggerBuf { | |
131 | lb := l.bufPool.Get().(*loggerBuf) | |
132 | if lb.buf == nil { | |
133 | lb.buf = &bytes.Buffer{} | |
134 | lb.logger = l.newLogger(lb.buf) | |
135 | } else { | |
136 | lb.buf.Reset() | |
137 | } | |
138 | return lb | |
139 | } | |
140 | ||
141 | func (l *colorLogger) putLoggerBuf(cb *loggerBuf) { | |
142 | l.bufPool.Put(cb) | |
143 | } |
0 | package term_test | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "io" | |
5 | "io/ioutil" | |
6 | "strconv" | |
7 | "sync" | |
8 | "testing" | |
9 | ||
10 | "github.com/go-kit/kit/log" | |
11 | "github.com/go-kit/kit/log/term" | |
12 | ) | |
13 | ||
14 | func TestColorLogger(t *testing.T) { | |
15 | var buf bytes.Buffer | |
16 | logger := newColorLogger(&buf) | |
17 | ||
18 | if err := logger.Log("hello", "world"); err != nil { | |
19 | t.Fatal(err) | |
20 | } | |
21 | if want, have := "hello=world\n", buf.String(); want != have { | |
22 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
23 | } | |
24 | ||
25 | buf.Reset() | |
26 | if err := logger.Log("a", 1); err != nil { | |
27 | t.Fatal(err) | |
28 | } | |
29 | if want, have := "\x1b[32;1m\x1b[47;1ma=1\n\x1b[39;49;22m", buf.String(); want != have { | |
30 | t.Errorf("\nwant %#v\nhave %#v", want, have) | |
31 | } | |
32 | } | |
33 | ||
34 | func newColorLogger(w io.Writer) log.Logger { | |
35 | return term.NewColorLogger(w, log.NewLogfmtLogger, | |
36 | func(keyvals ...interface{}) term.FgBgColor { | |
37 | if keyvals[0] == "a" { | |
38 | return term.FgBgColor{Fg: term.Green, Bg: term.White} | |
39 | } | |
40 | return term.FgBgColor{} | |
41 | }) | |
42 | } | |
43 | ||
44 | func BenchmarkColorLoggerSimple(b *testing.B) { | |
45 | benchmarkRunner(b, newColorLogger(ioutil.Discard), baseMessage) | |
46 | } | |
47 | ||
48 | func BenchmarkColorLoggerContextual(b *testing.B) { | |
49 | benchmarkRunner(b, newColorLogger(ioutil.Discard), withMessage) | |
50 | } | |
51 | ||
52 | func TestColorLoggerConcurrency(t *testing.T) { | |
53 | testConcurrency(t, newColorLogger(ioutil.Discard)) | |
54 | } | |
55 | ||
56 | // copied from log/benchmark_test.go | |
57 | func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { | |
58 | lc := log.With(logger, "common_key", "common_value") | |
59 | b.ReportAllocs() | |
60 | b.ResetTimer() | |
61 | for i := 0; i < b.N; i++ { | |
62 | f(lc) | |
63 | } | |
64 | } | |
65 | ||
66 | var ( | |
67 | baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") } | |
68 | withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") } | |
69 | ) | |
70 | ||
71 | // copied from log/concurrency_test.go | |
72 | func testConcurrency(t *testing.T, logger log.Logger) { | |
73 | for _, n := range []int{10, 100, 500} { | |
74 | wg := sync.WaitGroup{} | |
75 | wg.Add(n) | |
76 | for i := 0; i < n; i++ { | |
77 | go func() { spam(logger); wg.Done() }() | |
78 | } | |
79 | wg.Wait() | |
80 | } | |
81 | } | |
82 | ||
83 | func spam(logger log.Logger) { | |
84 | for i := 0; i < 100; i++ { | |
85 | logger.Log("a", strconv.FormatInt(int64(i), 10)) | |
86 | } | |
87 | } |
0 | package term | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | ||
5 | "github.com/go-kit/log/term" | |
6 | ) | |
7 | ||
8 | // NewColorWriter returns an io.Writer that writes to w and provides cross | |
9 | // platform support for ANSI color codes. If w is not a terminal it is | |
10 | // returned unmodified. | |
11 | func NewColorWriter(w io.Writer) io.Writer { | |
12 | return term.NewColorWriter(w) | |
13 | } |
0 | // +build !windows | |
1 | ||
2 | package term | |
3 | ||
4 | import "io" | |
5 | ||
6 | // NewColorWriter returns an io.Writer that writes to w and provides cross | |
7 | // platform support for ANSI color codes. If w is not a terminal it is | |
8 | // returned unmodified. | |
9 | func NewColorWriter(w io.Writer) io.Writer { | |
10 | return w | |
11 | } |
0 | // The code in this file is adapted from github.com/mattn/go-colorable. | |
1 | ||
2 | package term | |
3 | ||
4 | import ( | |
5 | "bytes" | |
6 | "fmt" | |
7 | "io" | |
8 | "strconv" | |
9 | "strings" | |
10 | "syscall" | |
11 | "unsafe" | |
12 | ) | |
13 | ||
14 | type colorWriter struct { | |
15 | out io.Writer | |
16 | handle syscall.Handle | |
17 | lastbuf bytes.Buffer | |
18 | oldattr word | |
19 | } | |
20 | ||
21 | // NewColorWriter returns an io.Writer that writes to w and provides cross | |
22 | // platform support for ANSI color codes. If w is not a terminal it is | |
23 | // returned unmodified. | |
24 | func NewColorWriter(w io.Writer) io.Writer { | |
25 | if !IsConsole(w) { | |
26 | return w | |
27 | } | |
28 | ||
29 | var csbi consoleScreenBufferInfo | |
30 | handle := syscall.Handle(w.(fder).Fd()) | |
31 | procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) | |
32 | ||
33 | return &colorWriter{ | |
34 | out: w, | |
35 | handle: handle, | |
36 | oldattr: csbi.attributes, | |
37 | } | |
38 | } | |
39 | ||
40 | func (w *colorWriter) Write(data []byte) (n int, err error) { | |
41 | var csbi consoleScreenBufferInfo | |
42 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |
43 | ||
44 | er := bytes.NewBuffer(data) | |
45 | loop: | |
46 | for { | |
47 | r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |
48 | if r1 == 0 { | |
49 | break loop | |
50 | } | |
51 | ||
52 | c1, _, err := er.ReadRune() | |
53 | if err != nil { | |
54 | break loop | |
55 | } | |
56 | if c1 != 0x1b { | |
57 | fmt.Fprint(w.out, string(c1)) | |
58 | continue | |
59 | } | |
60 | c2, _, err := er.ReadRune() | |
61 | if err != nil { | |
62 | w.lastbuf.WriteRune(c1) | |
63 | break loop | |
64 | } | |
65 | if c2 != 0x5b { | |
66 | w.lastbuf.WriteRune(c1) | |
67 | w.lastbuf.WriteRune(c2) | |
68 | continue | |
69 | } | |
70 | ||
71 | var buf bytes.Buffer | |
72 | var m rune | |
73 | for { | |
74 | c, _, err := er.ReadRune() | |
75 | if err != nil { | |
76 | w.lastbuf.WriteRune(c1) | |
77 | w.lastbuf.WriteRune(c2) | |
78 | w.lastbuf.Write(buf.Bytes()) | |
79 | break loop | |
80 | } | |
81 | if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { | |
82 | m = c | |
83 | break | |
84 | } | |
85 | buf.Write([]byte(string(c))) | |
86 | } | |
87 | ||
88 | switch m { | |
89 | case 'm': | |
90 | attr := csbi.attributes | |
91 | cs := buf.String() | |
92 | if cs == "" { | |
93 | procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) | |
94 | continue | |
95 | } | |
96 | token := strings.Split(cs, ";") | |
97 | intensityMode := word(0) | |
98 | for _, ns := range token { | |
99 | if n, err = strconv.Atoi(ns); err == nil { | |
100 | switch { | |
101 | case n == 0: | |
102 | attr = w.oldattr | |
103 | case n == 1: | |
104 | attr |= intensityMode | |
105 | case 30 <= n && n <= 37: | |
106 | attr = (attr & backgroundMask) | |
107 | if (n-30)&1 != 0 { | |
108 | attr |= foregroundRed | |
109 | } | |
110 | if (n-30)&2 != 0 { | |
111 | attr |= foregroundGreen | |
112 | } | |
113 | if (n-30)&4 != 0 { | |
114 | attr |= foregroundBlue | |
115 | } | |
116 | intensityMode = foregroundIntensity | |
117 | case n == 39: // reset foreground color | |
118 | attr &= backgroundMask | |
119 | attr |= w.oldattr & foregroundMask | |
120 | case 40 <= n && n <= 47: | |
121 | attr = (attr & foregroundMask) | |
122 | if (n-40)&1 != 0 { | |
123 | attr |= backgroundRed | |
124 | } | |
125 | if (n-40)&2 != 0 { | |
126 | attr |= backgroundGreen | |
127 | } | |
128 | if (n-40)&4 != 0 { | |
129 | attr |= backgroundBlue | |
130 | } | |
131 | intensityMode = backgroundIntensity | |
132 | case n == 49: // reset background color | |
133 | attr &= foregroundMask | |
134 | attr |= w.oldattr & backgroundMask | |
135 | } | |
136 | procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) | |
137 | } | |
138 | } | |
139 | } | |
140 | } | |
141 | return len(data) - w.lastbuf.Len(), nil | |
142 | } | |
143 | ||
144 | var ( | |
145 | procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") | |
146 | procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") | |
147 | ) | |
148 | ||
149 | const ( | |
150 | foregroundBlue = 0x1 | |
151 | foregroundGreen = 0x2 | |
152 | foregroundRed = 0x4 | |
153 | foregroundIntensity = 0x8 | |
154 | foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) | |
155 | backgroundBlue = 0x10 | |
156 | backgroundGreen = 0x20 | |
157 | backgroundRed = 0x40 | |
158 | backgroundIntensity = 0x80 | |
159 | backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) | |
160 | ) | |
161 | ||
162 | type ( | |
163 | wchar uint16 | |
164 | short int16 | |
165 | dword uint32 | |
166 | word uint16 | |
167 | ) | |
168 | ||
169 | type coord struct { | |
170 | x short | |
171 | y short | |
172 | } | |
173 | ||
174 | type smallRect struct { | |
175 | left short | |
176 | top short | |
177 | right short | |
178 | bottom short | |
179 | } | |
180 | ||
181 | type consoleScreenBufferInfo struct { | |
182 | size coord | |
183 | cursorPosition coord | |
184 | attributes word | |
185 | window smallRect | |
186 | maximumWindowSize coord | |
187 | } |
0 | 0 | // Package term provides tools for logging to a terminal. |
1 | // | |
2 | // Deprecated: Use github.com/go-kit/log/term instead. | |
1 | 3 | package term |
2 | 4 | |
3 | 5 | import ( |
4 | 6 | "io" |
5 | 7 | |
6 | "github.com/go-kit/kit/log" | |
8 | "github.com/go-kit/log" | |
9 | "github.com/go-kit/log/term" | |
7 | 10 | ) |
8 | 11 | |
9 | 12 | // NewLogger returns a Logger that takes advantage of terminal features if |
10 | 13 | // possible. Log events are formatted by the Logger returned by newLogger. If |
11 | 14 | // w is a terminal each log event is colored according to the color function. |
12 | 15 | func NewLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger { |
13 | if !IsTerminal(w) { | |
14 | return newLogger(w) | |
15 | } | |
16 | return NewColorLogger(NewColorWriter(w), newLogger, color) | |
16 | return term.NewLogger(w, newLogger, color) | |
17 | 17 | } |
18 | 18 | |
19 | type fder interface { | |
20 | Fd() uintptr | |
19 | // IsTerminal returns true if w writes to a terminal. | |
20 | func IsTerminal(w io.Writer) bool { | |
21 | return term.IsTerminal(w) | |
21 | 22 | } |
0 | // Based on ssh/terminal: | |
1 | // Copyright 2013 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 term | |
6 | ||
7 | import "syscall" | |
8 | ||
9 | const ioctlReadTermios = syscall.TIOCGETA |
0 | // Based on ssh/terminal: | |
1 | // Copyright 2013 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 | // +build !appengine | |
6 | ||
7 | package term | |
8 | ||
9 | import "syscall" | |
10 | ||
11 | const ioctlReadTermios = syscall.TCGETS |
0 | // Based on ssh/terminal: | |
1 | // Copyright 2011 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 | // +build linux,!appengine darwin freebsd openbsd | |
6 | ||
7 | package term | |
8 | ||
9 | import ( | |
10 | "io" | |
11 | "syscall" | |
12 | "unsafe" | |
13 | ) | |
14 | ||
15 | // IsTerminal returns true if w writes to a terminal. | |
16 | func IsTerminal(w io.Writer) bool { | |
17 | fw, ok := w.(fder) | |
18 | if !ok { | |
19 | return false | |
20 | } | |
21 | var termios syscall.Termios | |
22 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fw.Fd(), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) | |
23 | return err == 0 | |
24 | } |
0 | // Based on ssh/terminal: | |
1 | // Copyright 2013 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 | // +build appengine js | |
6 | ||
7 | package term | |
8 | ||
9 | import "io" | |
10 | ||
11 | // IsTerminal always returns false on AppEngine. | |
12 | func IsTerminal(w io.Writer) bool { | |
13 | return false | |
14 | } |
0 | // Based on ssh/terminal: | |
1 | // Copyright 2011 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 | // +build windows | |
6 | ||
7 | package term | |
8 | ||
9 | import ( | |
10 | "encoding/binary" | |
11 | "io" | |
12 | "regexp" | |
13 | "syscall" | |
14 | "unsafe" | |
15 | ) | |
16 | ||
17 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") | |
18 | ||
19 | var ( | |
20 | procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") | |
21 | msysPipeNameRegex = regexp.MustCompile(`\\(cygwin|msys)-\w+-pty\d?-(to|from)-master`) | |
22 | ) | |
23 | ||
24 | const ( | |
25 | fileNameInfo = 0x02 | |
26 | ) | |
27 | ||
28 | // IsTerminal returns true if w writes to a terminal. | |
29 | func IsTerminal(w io.Writer) bool { | |
30 | return IsConsole(w) || IsMSYSTerminal(w) | |
31 | } | |
32 | ||
33 | // IsConsole returns true if w writes to a Windows console. | |
34 | func IsConsole(w io.Writer) bool { | |
35 | var handle syscall.Handle | |
36 | ||
37 | if fw, ok := w.(fder); ok { | |
38 | handle = syscall.Handle(fw.Fd()) | |
39 | } else { | |
40 | // The writer has no file-descriptor and so can't be a terminal. | |
41 | return false | |
42 | } | |
43 | ||
44 | var st uint32 | |
45 | err := syscall.GetConsoleMode(handle, &st) | |
46 | ||
47 | // If the handle is attached to a terminal, GetConsoleMode returns a | |
48 | // non-zero value containing the console mode flags. We don't care about | |
49 | // the specifics of flags, just that it is not zero. | |
50 | return (err == nil && st != 0) | |
51 | } | |
52 | ||
53 | // IsMSYSTerminal returns true if w writes to a MSYS/MSYS2 terminal. | |
54 | func IsMSYSTerminal(w io.Writer) bool { | |
55 | var handle syscall.Handle | |
56 | ||
57 | if fw, ok := w.(fder); ok { | |
58 | handle = syscall.Handle(fw.Fd()) | |
59 | } else { | |
60 | // The writer has no file-descriptor and so can't be a terminal. | |
61 | return false | |
62 | } | |
63 | ||
64 | // MSYS(2) terminal reports as a pipe for STDIN/STDOUT/STDERR. If it isn't | |
65 | // a pipe, it can't be a MSYS(2) terminal. | |
66 | filetype, err := syscall.GetFileType(handle) | |
67 | ||
68 | if filetype != syscall.FILE_TYPE_PIPE || err != nil { | |
69 | return false | |
70 | } | |
71 | ||
72 | // MSYS2/Cygwin terminal's name looks like: \msys-dd50a72ab4668b33-pty2-to-master | |
73 | data := make([]byte, 256, 256) | |
74 | ||
75 | r, _, e := syscall.Syscall6( | |
76 | procGetFileInformationByHandleEx.Addr(), | |
77 | 4, | |
78 | uintptr(handle), | |
79 | uintptr(fileNameInfo), | |
80 | uintptr(unsafe.Pointer(&data[0])), | |
81 | uintptr(len(data)), | |
82 | 0, | |
83 | 0, | |
84 | ) | |
85 | ||
86 | if r != 0 && e == 0 { | |
87 | // The first 4 bytes of the buffer are the size of the UTF16 name, in bytes. | |
88 | unameLen := binary.LittleEndian.Uint32(data[:4]) / 2 | |
89 | uname := make([]uint16, unameLen, unameLen) | |
90 | ||
91 | for i := uint32(0); i < unameLen; i++ { | |
92 | uname[i] = binary.LittleEndian.Uint16(data[i*2+4 : i*2+2+4]) | |
93 | } | |
94 | ||
95 | name := syscall.UTF16ToString(uname) | |
96 | ||
97 | return msysPipeNameRegex.MatchString(name) | |
98 | } | |
99 | ||
100 | return false | |
101 | } |
0 | package term | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "syscall" | |
5 | "testing" | |
6 | ) | |
7 | ||
8 | type myWriter struct { | |
9 | fd uintptr | |
10 | } | |
11 | ||
12 | func (w *myWriter) Write(p []byte) (int, error) { | |
13 | return 0, fmt.Errorf("not implemented") | |
14 | } | |
15 | ||
16 | func (w *myWriter) Fd() uintptr { | |
17 | return w.fd | |
18 | } | |
19 | ||
20 | var procGetStdHandle = kernel32.NewProc("GetStdHandle") | |
21 | ||
22 | const stdOutputHandle = ^uintptr(0) - 11 + 1 | |
23 | ||
24 | func getConsoleHandle() syscall.Handle { | |
25 | ptr, err := syscall.UTF16PtrFromString("CONOUT$") | |
26 | ||
27 | if err != nil { | |
28 | panic(err) | |
29 | } | |
30 | ||
31 | handle, err := syscall.CreateFile(ptr, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, 0, 0) | |
32 | ||
33 | if err != nil { | |
34 | panic(err) | |
35 | } | |
36 | ||
37 | return handle | |
38 | } | |
39 | ||
40 | func TestIsTerminal(t *testing.T) { | |
41 | // This is necessary because depending on whether `go test` is called with | |
42 | // the `-v` option, stdout will or will not be bound, changing the behavior | |
43 | // of the test. So we refer to it directly to avoid flakyness. | |
44 | handle := getConsoleHandle() | |
45 | ||
46 | writer := &myWriter{ | |
47 | fd: uintptr(handle), | |
48 | } | |
49 | ||
50 | if !IsTerminal(writer) { | |
51 | t.Errorf("output is supposed to be a terminal") | |
52 | } | |
53 | } | |
54 | ||
55 | func TestIsConsole(t *testing.T) { | |
56 | // This is necessary because depending on whether `go test` is called with | |
57 | // the `-v` option, stdout will or will not be bound, changing the behavior | |
58 | // of the test. So we refer to it directly to avoid flakyness. | |
59 | handle := getConsoleHandle() | |
60 | ||
61 | writer := &myWriter{ | |
62 | fd: uintptr(handle), | |
63 | } | |
64 | ||
65 | if !IsConsole(writer) { | |
66 | t.Errorf("output is supposed to be a console") | |
67 | } | |
68 | } |
0 | 0 | package log |
1 | 1 | |
2 | 2 | import ( |
3 | "runtime" | |
4 | "strconv" | |
5 | "strings" | |
6 | 3 | "time" |
4 | ||
5 | "github.com/go-kit/log" | |
7 | 6 | ) |
8 | 7 | |
9 | 8 | // A Valuer generates a log value. When passed to With, WithPrefix, or |
10 | 9 | // WithSuffix in a value element (odd indexes), it represents a dynamic |
11 | 10 | // value which is re-evaluated with each log event. |
12 | type Valuer func() interface{} | |
13 | ||
14 | // bindValues replaces all value elements (odd indexes) containing a Valuer | |
15 | // with their generated value. | |
16 | func bindValues(keyvals []interface{}) { | |
17 | for i := 1; i < len(keyvals); i += 2 { | |
18 | if v, ok := keyvals[i].(Valuer); ok { | |
19 | keyvals[i] = v() | |
20 | } | |
21 | } | |
22 | } | |
23 | ||
24 | // containsValuer returns true if any of the value elements (odd indexes) | |
25 | // contain a Valuer. | |
26 | func containsValuer(keyvals []interface{}) bool { | |
27 | for i := 1; i < len(keyvals); i += 2 { | |
28 | if _, ok := keyvals[i].(Valuer); ok { | |
29 | return true | |
30 | } | |
31 | } | |
32 | return false | |
33 | } | |
11 | type Valuer = log.Valuer | |
34 | 12 | |
35 | 13 | // Timestamp returns a timestamp Valuer. It invokes the t function to get the |
36 | 14 | // time; unless you are doing something tricky, pass time.Now. |
38 | 16 | // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which |
39 | 17 | // are TimestampFormats that use the RFC3339Nano format. |
40 | 18 | func Timestamp(t func() time.Time) Valuer { |
41 | return func() interface{} { return t() } | |
19 | return log.Timestamp(t) | |
42 | 20 | } |
43 | 21 | |
44 | 22 | // TimestampFormat returns a timestamp Valuer with a custom time format. It |
49 | 27 | // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which |
50 | 28 | // are TimestampFormats that use the RFC3339Nano format. |
51 | 29 | func TimestampFormat(t func() time.Time, layout string) Valuer { |
52 | return func() interface{} { | |
53 | return timeFormat{ | |
54 | time: t(), | |
55 | layout: layout, | |
56 | } | |
57 | } | |
58 | } | |
59 | ||
60 | // A timeFormat represents an instant in time and a layout used when | |
61 | // marshaling to a text format. | |
62 | type timeFormat struct { | |
63 | time time.Time | |
64 | layout string | |
65 | } | |
66 | ||
67 | func (tf timeFormat) String() string { | |
68 | return tf.time.Format(tf.layout) | |
69 | } | |
70 | ||
71 | // MarshalText implements encoding.TextMarshaller. | |
72 | func (tf timeFormat) MarshalText() (text []byte, err error) { | |
73 | // The following code adapted from the standard library time.Time.Format | |
74 | // method. Using the same undocumented magic constant to extend the size | |
75 | // of the buffer as seen there. | |
76 | b := make([]byte, 0, len(tf.layout)+10) | |
77 | b = tf.time.AppendFormat(b, tf.layout) | |
78 | return b, nil | |
30 | return log.TimestampFormat(t, layout) | |
79 | 31 | } |
80 | 32 | |
81 | 33 | // Caller returns a Valuer that returns a file and line from a specified depth |
82 | 34 | // in the callstack. Users will probably want to use DefaultCaller. |
83 | 35 | func Caller(depth int) Valuer { |
84 | return func() interface{} { | |
85 | _, file, line, _ := runtime.Caller(depth) | |
86 | idx := strings.LastIndexByte(file, '/') | |
87 | // using idx+1 below handles both of following cases: | |
88 | // idx == -1 because no "/" was found, or | |
89 | // idx >= 0 and we want to start at the character after the found "/". | |
90 | return file[idx+1:] + ":" + strconv.Itoa(line) | |
91 | } | |
36 | return log.Caller(depth) | |
92 | 37 | } |
93 | 38 | |
94 | 39 | var ( |
95 | 40 | // DefaultTimestamp is a Valuer that returns the current wallclock time, |
96 | 41 | // respecting time zones, when bound. |
97 | DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano) | |
42 | DefaultTimestamp = log.DefaultTimestamp | |
98 | 43 | |
99 | 44 | // DefaultTimestampUTC is a Valuer that returns the current time in UTC |
100 | 45 | // when bound. |
101 | DefaultTimestampUTC = TimestampFormat( | |
102 | func() time.Time { return time.Now().UTC() }, | |
103 | time.RFC3339Nano, | |
104 | ) | |
46 | DefaultTimestampUTC = log.DefaultTimestampUTC | |
105 | 47 | |
106 | 48 | // DefaultCaller is a Valuer that returns the file and line where the Log |
107 | 49 | // method was invoked. It can only be used with log.With. |
108 | DefaultCaller = Caller(3) | |
50 | DefaultCaller = log.DefaultCaller | |
109 | 51 | ) |
0 | package log_test | |
1 | ||
2 | import ( | |
3 | "encoding" | |
4 | "fmt" | |
5 | "reflect" | |
6 | "testing" | |
7 | "time" | |
8 | ||
9 | "github.com/go-kit/kit/log" | |
10 | ) | |
11 | ||
12 | func TestValueBinding(t *testing.T) { | |
13 | t.Parallel() | |
14 | var output []interface{} | |
15 | ||
16 | logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { | |
17 | output = keyvals | |
18 | return nil | |
19 | })) | |
20 | ||
21 | start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC) | |
22 | now := start | |
23 | mocktime := func() time.Time { | |
24 | now = now.Add(time.Second) | |
25 | return now | |
26 | } | |
27 | ||
28 | lc := log.With(logger, "ts", log.Timestamp(mocktime), "caller", log.DefaultCaller) | |
29 | ||
30 | lc.Log("foo", "bar") | |
31 | timestamp, ok := output[1].(time.Time) | |
32 | if !ok { | |
33 | t.Fatalf("want time.Time, have %T", output[1]) | |
34 | } | |
35 | if want, have := start.Add(time.Second), timestamp; want != have { | |
36 | t.Errorf("output[1]: want %v, have %v", want, have) | |
37 | } | |
38 | if want, have := "value_test.go:31", fmt.Sprint(output[3]); want != have { | |
39 | t.Errorf("output[3]: want %s, have %s", want, have) | |
40 | } | |
41 | ||
42 | // A second attempt to confirm the bindings are truly dynamic. | |
43 | lc.Log("foo", "bar") | |
44 | timestamp, ok = output[1].(time.Time) | |
45 | if !ok { | |
46 | t.Fatalf("want time.Time, have %T", output[1]) | |
47 | } | |
48 | if want, have := start.Add(2*time.Second), timestamp; want != have { | |
49 | t.Errorf("output[1]: want %v, have %v", want, have) | |
50 | } | |
51 | if want, have := "value_test.go:44", fmt.Sprint(output[3]); want != have { | |
52 | t.Errorf("output[3]: want %s, have %s", want, have) | |
53 | } | |
54 | } | |
55 | ||
56 | func TestValueBinding_loggingZeroKeyvals(t *testing.T) { | |
57 | t.Parallel() | |
58 | var output []interface{} | |
59 | ||
60 | logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { | |
61 | output = keyvals | |
62 | return nil | |
63 | })) | |
64 | ||
65 | start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC) | |
66 | now := start | |
67 | mocktime := func() time.Time { | |
68 | now = now.Add(time.Second) | |
69 | return now | |
70 | } | |
71 | ||
72 | logger = log.With(logger, "ts", log.Timestamp(mocktime)) | |
73 | ||
74 | logger.Log() | |
75 | timestamp, ok := output[1].(time.Time) | |
76 | if !ok { | |
77 | t.Fatalf("want time.Time, have %T", output[1]) | |
78 | } | |
79 | if want, have := start.Add(time.Second), timestamp; want != have { | |
80 | t.Errorf("output[1]: want %v, have %v", want, have) | |
81 | } | |
82 | ||
83 | // A second attempt to confirm the bindings are truly dynamic. | |
84 | logger.Log() | |
85 | timestamp, ok = output[1].(time.Time) | |
86 | if !ok { | |
87 | t.Fatalf("want time.Time, have %T", output[1]) | |
88 | } | |
89 | if want, have := start.Add(2*time.Second), timestamp; want != have { | |
90 | t.Errorf("output[1]: want %v, have %v", want, have) | |
91 | } | |
92 | } | |
93 | ||
94 | func TestTimestampFormat(t *testing.T) { | |
95 | t.Parallel() | |
96 | ||
97 | start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC) | |
98 | now := start | |
99 | mocktime := func() time.Time { | |
100 | now = now.Add(time.Second) | |
101 | return now | |
102 | } | |
103 | ||
104 | tv := log.TimestampFormat(mocktime, time.RFC822) | |
105 | ||
106 | if want, have := now.Add(time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have { | |
107 | t.Errorf("wrong time format: want %v, have %v", want, have) | |
108 | } | |
109 | ||
110 | if want, have := now.Add(2*time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have { | |
111 | t.Errorf("wrong time format: want %v, have %v", want, have) | |
112 | } | |
113 | ||
114 | mustMarshal := func(v interface{}) []byte { | |
115 | b, err := v.(encoding.TextMarshaler).MarshalText() | |
116 | if err != nil { | |
117 | t.Fatal("error marshaling text:", err) | |
118 | } | |
119 | return b | |
120 | } | |
121 | ||
122 | if want, have := now.Add(3*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) { | |
123 | t.Errorf("wrong time format: want %s, have %s", want, have) | |
124 | } | |
125 | ||
126 | if want, have := now.Add(4*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) { | |
127 | t.Errorf("wrong time format: want %s, have %s", want, have) | |
128 | } | |
129 | } | |
130 | ||
131 | func BenchmarkValueBindingTimestamp(b *testing.B) { | |
132 | logger := log.NewNopLogger() | |
133 | lc := log.With(logger, "ts", log.DefaultTimestamp) | |
134 | b.ReportAllocs() | |
135 | b.ResetTimer() | |
136 | for i := 0; i < b.N; i++ { | |
137 | lc.Log("k", "v") | |
138 | } | |
139 | } | |
140 | ||
141 | func BenchmarkValueBindingCaller(b *testing.B) { | |