Codebase list golang-github-go-kit-kit / 801da84
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 2 years ago GitHub committed 2 years ago
113 changed file(s) with 223 addition(s) and 3176 deletion(s). Raw diff Collapse all Expand all
9494 "context"
9595
9696 "github.com/go-kit/kit/auth/jwt"
97 "github.com/go-kit/kit/log"
97 "github.com/go-kit/log"
9898 grpctransport "github.com/go-kit/kit/transport/grpc"
9999 )
100100
1515 github.com/edsrzf/mmap-go v1.0.0 // indirect
1616 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db // indirect
1717 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
2019 github.com/go-zookeeper/zk v1.0.2
2120 github.com/hashicorp/consul/api v1.8.1
2221 github.com/hudl/fargo v1.3.0
7878 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
7979 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
8080 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
81 github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ=
8182 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
8283 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
8384 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
00 # 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 ______
110
211 `package log` provides a minimal interface for structured logging in services.
312 It may be wrapped to encode conventions, enforce type-safety, provide leveled
+0
-21
log/benchmark_test.go less more
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
-40
log/concurrency_test.go less more
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.
03 package levels
14
2 import "github.com/go-kit/kit/log"
5 import "github.com/go-kit/log"
36
47 // Levels provides a leveled logging wrapper around a logger. It has five
58 // levels: debug, info, warning (warn), error, and critical (crit). If you
44 "os"
55 "testing"
66
7 "github.com/go-kit/kit/log"
87 levels "github.com/go-kit/kit/log/deprecated_levels"
8 "github.com/go-kit/log"
99 )
1010
1111 func TestDefaultLevels(t *testing.T) {
00 // Package log provides a structured logger.
1 //
2 // Deprecated: Use github.com/go-kit/log instead.
13 //
24 // Structured logging produces logs easily consumed later by humans or
35 // machines. Humans might be interested in debugging errors, or tracing
00 package log
11
22 import (
3 "encoding"
4 "encoding/json"
5 "fmt"
63 "io"
7 "reflect"
4
5 "github.com/go-kit/log"
86 )
9
10 type jsonLogger struct {
11 io.Writer
12 }
137
148 // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
159 // single JSON object. Each log event produces no more than one call to
1610 // w.Write. The passed Writer must be safe for concurrent use by multiple
1711 // goroutines if the returned Logger will be used concurrently.
1812 func NewJSONLogger(w io.Writer) Logger {
19 return &jsonLogger{w}
13 return log.NewJSONLogger(w)
2014 }
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
-174
log/json_logger_test.go less more
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
-72
log/level/benchmark_test.go less more
0 package level_test
1
2 import (
3 "io/ioutil"
4 "testing"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/log/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.
36 //
47 // var logger log.Logger
58 // logger = log.NewLogfmtLogger(os.Stderr)
00 package level
11
2 import "github.com/go-kit/kit/log"
2 import (
3 "github.com/go-kit/log"
4 "github.com/go-kit/log/level"
5 )
36
47 // Error returns a logger that includes a Key/ErrorValue pair.
58 func Error(logger log.Logger) log.Logger {
6 return log.WithPrefix(logger, Key(), ErrorValue())
9 return level.Error(logger)
710 }
811
912 // Warn returns a logger that includes a Key/WarnValue pair.
1013 func Warn(logger log.Logger) log.Logger {
11 return log.WithPrefix(logger, Key(), WarnValue())
14 return level.Warn(logger)
1215 }
1316
1417 // Info returns a logger that includes a Key/InfoValue pair.
1518 func Info(logger log.Logger) log.Logger {
16 return log.WithPrefix(logger, Key(), InfoValue())
19 return level.Info(logger)
1720 }
1821
1922 // Debug returns a logger that includes a Key/DebugValue pair.
2023 func Debug(logger log.Logger) log.Logger {
21 return log.WithPrefix(logger, Key(), DebugValue())
24 return level.Debug(logger)
2225 }
2326
2427 // NewFilter wraps next and implements level filtering. See the commentary on
2730 // Info, Warn or Error helper methods are squelched and non-leveled log
2831 // events are passed to next unmodified.
2932 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...)
6334 }
6435
6536 // Option sets a parameter for the leveled logger.
66 type Option func(*logger)
37 type Option = level.Option
6738
6839 // AllowAll is an alias for AllowDebug.
6940 func AllowAll() Option {
70 return AllowDebug()
41 return level.AllowAll()
7142 }
7243
7344 // AllowDebug allows error, warn, info and debug level log events to pass.
7445 func AllowDebug() Option {
75 return allowed(levelError | levelWarn | levelInfo | levelDebug)
46 return level.AllowDebug()
7647 }
7748
7849 // AllowInfo allows error, warn and info level log events to pass.
7950 func AllowInfo() Option {
80 return allowed(levelError | levelWarn | levelInfo)
51 return level.AllowInfo()
8152 }
8253
8354 // AllowWarn allows error and warn level log events to pass.
8455 func AllowWarn() Option {
85 return allowed(levelError | levelWarn)
56 return level.AllowWarn()
8657 }
8758
8859 // AllowError allows only error level log events to pass.
8960 func AllowError() Option {
90 return allowed(levelError)
61 return level.AllowError()
9162 }
9263
9364 // AllowNone allows no leveled log events to pass.
9465 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()
10067 }
10168
10269 // ErrNotAllowed sets the error to return from Log when it squelches a log
10471 // ErrNotAllowed is nil; in this case the log event is squelched with no
10572 // error.
10673 func ErrNotAllowed(err error) Option {
107 return func(l *logger) { l.errNotAllowed = err }
74 return level.ErrNotAllowed(err)
10875 }
10976
11077 // SquelchNoLevel instructs Log to squelch log events with no level, so that
11279 // to true and a log event is squelched in this way, the error value
11380 // configured with ErrNoLevel is returned to the caller.
11481 func SquelchNoLevel(squelch bool) Option {
115 return func(l *logger) { l.squelchNoLevel = squelch }
82 return level.SquelchNoLevel(squelch)
11683 }
11784
11885 // ErrNoLevel sets the error to return from Log when it squelches a log event
11986 // with no level. By default, ErrNoLevel is nil; in this case the log event is
12087 // squelched with no error.
12188 func ErrNoLevel(err error) Option {
122 return func(l *logger) { l.errNoLevel = err }
89 return level.ErrNoLevel(err)
12390 }
12491
12592 // NewInjector wraps next and returns a logger that adds a Key/level pair to
12693 // the beginning of log events that don't already contain a level. In effect,
12794 // 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)
15097 }
15198
15299 // Value is the interface that each of the canonical level values implement.
153100 // It contains unexported methods that prevent types from other packages from
154101 // implementing it and guaranteeing that NewFilter can distinguish the levels
155102 // defined in this package from all other values.
156 type Value interface {
157 String() string
158 levelVal()
159 }
103 type Value = level.Value
160104
161105 // Key returns the unique key added to log events by the loggers in this
162106 // package.
163 func Key() interface{} { return key }
107 func Key() interface{} { return level.Key() }
164108
165109 // ErrorValue returns the unique value added to log events by Error.
166 func ErrorValue() Value { return errorValue }
110 func ErrorValue() Value { return level.ErrorValue() }
167111
168112 // WarnValue returns the unique value added to log events by Warn.
169 func WarnValue() Value { return warnValue }
113 func WarnValue() Value { return level.WarnValue() }
170114
171115 // InfoValue returns the unique value added to log events by Info.
172 func InfoValue() Value { return infoValue }
116 func InfoValue() Value { return level.InfoValue() }
173117
174118 // 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
-235
log/level/level_test.go less more
0 package level_test
1
2 import (
3 "bytes"
4 "errors"
5 "io"
6 "strings"
7 "testing"
8
9 "github.com/go-kit/kit/log"
10 "github.com/go-kit/kit/log/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 }
00 package log
11
2 import "errors"
2 import (
3 "github.com/go-kit/log"
4 )
35
46 // Logger is the fundamental interface for all log operations. Log creates a
57 // log event from keyvals, a variadic sequence of alternating keys and values.
68 // Implementations must be safe for concurrent use by multiple goroutines. In
79 // particular, any implementation of Logger that appends to keyvals or
810 // 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
1212
1313 // ErrMissingValue is appended to keyvals slices with odd length to substitute
1414 // the missing value.
15 var ErrMissingValue = errors.New("(MISSING)")
15 var ErrMissingValue = log.ErrMissingValue
1616
1717 // With returns a new contextual logger with keyvals prepended to those passed
1818 // to calls to Log. If logger is also a contextual logger created by With,
2121 // The returned Logger replaces all value elements (odd indexes) containing a
2222 // Valuer with their generated value for each call to its Log method.
2323 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...)
4325 }
4426
4527 // WithPrefix returns a new contextual logger with keyvals prepended to those
4931 // The returned Logger replaces all value elements (odd indexes) containing a
5032 // Valuer with their generated value for each call to its Log method.
5133 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...)
7735 }
7836
7937 // WithSuffix returns a new contextual logger with keyvals appended to those
8341 // The returned Logger replaces all value elements (odd indexes) containing a
8442 // Valuer with their generated value for each call to its Log method.
8543 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...)
16845 }
16946
17047 // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
17148 // f is a function with the appropriate signature, LoggerFunc(f) is a Logger
17249 // 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
-349
log/log_test.go less more
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 }
00 package log
11
22 import (
3 "bytes"
43 "io"
5 "sync"
64
7 "github.com/go-logfmt/logfmt"
5 "github.com/go-kit/log"
86 )
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 }
317
328 // NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
339 // logfmt format. Each log event produces no more than one call to w.Write.
3410 // The passed Writer must be safe for concurrent use by multiple goroutines if
3511 // the returned Logger will be used concurrently.
3612 func NewLogfmtLogger(w io.Writer) Logger {
37 return &logfmtLogger{w}
13 return log.NewLogfmtLogger(w)
3814 }
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
-57
log/logfmt_logger_test.go less more
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" }
55 "errors"
66 "fmt"
77
8 "github.com/go-kit/kit/log"
8 "github.com/go-kit/log"
99 "github.com/sirupsen/logrus"
1010 )
1111
00 package log
11
2 type nopLogger struct{}
2 import "github.com/go-kit/log"
33
44 // 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
-26
log/nop_logger_test.go less more
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 }
00 package log
11
22 import (
3 "bytes"
43 "io"
5 "log"
6 "regexp"
7 "strings"
4
5 "github.com/go-kit/log"
86 )
97
108 // StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's
1311 //
1412 // If you have any choice in the matter, you shouldn't use this. Prefer to
1513 // 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
2315
2416 // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib
2517 // logger's SetOutput. It will extract date/timestamps, filenames, and
2618 // 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
3520
3621 // StdlibAdapterOption sets a parameter for the StdlibAdapter.
37 type StdlibAdapterOption func(*StdlibAdapter)
22 type StdlibAdapterOption = log.StdlibAdapterOption
3823
3924 // TimestampKey sets the key for the timestamp field. By default, it's "ts".
4025 func TimestampKey(key string) StdlibAdapterOption {
41 return func(a *StdlibAdapter) { a.timestampKey = key }
26 return log.TimestampKey(key)
4227 }
4328
4429 // FileKey sets the key for the file and line field. By default, it's "caller".
4530 func FileKey(key string) StdlibAdapterOption {
46 return func(a *StdlibAdapter) { a.fileKey = key }
31 return log.FileKey(key)
4732 }
4833
4934 // MessageKey sets the key for the actual log message. By default, it's "msg".
5035 func MessageKey(key string) StdlibAdapterOption {
51 return func(a *StdlibAdapter) { a.messageKey = key }
36 return log.MessageKey(key)
5237 }
5338
5439 // Prefix configures the adapter to parse a prefix from stdlib log events. If
5843 // By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to
5944 // true if you want to include the parsed prefix in the msg.
6045 func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption {
61 return func(a *StdlibAdapter) { a.prefix = prefix; a.joinPrefixToMsg = joinPrefixToMsg }
46 return log.Prefix(prefix, joinPrefixToMsg)
6247 }
6348
6449 // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
6550 // logger. It's designed to be passed to log.SetOutput.
6651 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...)
7753 }
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
-261
log/stdlib_test.go less more
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 }
11
22 import (
33 "io"
4 "sync"
5 "sync/atomic"
4
5 "github.com/go-kit/log"
66 )
77
88 // SwapLogger wraps another logger that may be safely replaced while other
1111 //
1212 // SwapLogger serves well as a package global logger that can be changed by
1313 // 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
3715
3816 // NewSyncWriter returns a new writer that is safe for concurrent use by
3917 // multiple goroutines. Writes to the returned writer are passed on to w. If
4624 // Fd() uintptr
4725 // }
4826 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)
9628 }
9729
9830 // NewSyncLogger returns a logger that synchronizes concurrent use of the
10032 // only one goroutine will be allowed to log to the wrapped logger at a time.
10133 // The other goroutines will block until the logger is available.
10234 func NewSyncLogger(logger Logger) Logger {
103 return &syncLogger{logger: logger}
35 return log.NewSyncLogger(logger)
10436 }
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
-101
log/sync_test.go less more
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
32
3 // Deprecated: Use github.com/go-kit/log/syslog instead.
44 package syslog
55
66 import (
7 "bytes"
87 "io"
9 "sync"
108
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"
1511 )
1612
1713 // 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
3015
3116 // NewSyslogLogger returns a new Logger which writes to syslog in syslog format.
3217 // The body of the log message is the formatted output from the Logger returned
3318 // by newLogger.
3419 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...)
10821 }
10922
11023 // Option sets a parameter for syslog loggers.
111 type Option func(*syslogLogger)
24 type Option = syslog.Option
11225
11326 // PrioritySelector inspects the list of keyvals and selects a syslog priority.
114 type PrioritySelector func(keyvals ...interface{}) gosyslog.Priority
27 type PrioritySelector = syslog.PrioritySelector
11528
11629 // PrioritySelectorOption sets priority selector function to choose syslog
11730 // priority.
11831 func PrioritySelectorOption(selector PrioritySelector) Option {
119 return func(l *syslogLogger) { l.prioritySelector = selector }
32 return syslog.PrioritySelectorOption(selector)
12033 }
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
-170
log/syslog/syslog_test.go less more
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
-21
log/term/LICENSE less more
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.
00 package term
11
22 import (
3 "bytes"
4 "fmt"
53 "io"
6 "sync"
74
8 "github.com/go-kit/kit/log"
5 "github.com/go-kit/log"
6 "github.com/go-kit/log/term"
97 )
108
119 // Color represents an ANSI color. The zero value is Default.
12 type Color uint8
10 type Color = term.Color
1311
1412 // ANSI colors.
1513 const (
16 Default = Color(iota)
14 Default = term.Default
1715
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
2624
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
3733 )
3834
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
6735 // 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
7537
7638 // NewColorLogger returns a Logger which writes colored logs to w. ANSI color
7739 // codes for the colors returned by color are added to the formatted output
7840 // from the Logger returned by newLogger and the combined result written to w.
7941 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)
9043 }
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
-88
log/term/colorlogger_test.go less more
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
-12
log/term/colorwriter_others.go less more
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
-188
log/term/colorwriter_windows.go less more
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 }
00 // Package term provides tools for logging to a terminal.
1 //
2 // Deprecated: Use github.com/go-kit/log/term instead.
13 package term
24
35 import (
46 "io"
57
6 "github.com/go-kit/kit/log"
8 "github.com/go-kit/log"
9 "github.com/go-kit/log/term"
710 )
811
912 // NewLogger returns a Logger that takes advantage of terminal features if
1013 // possible. Log events are formatted by the Logger returned by newLogger. If
1114 // w is a terminal each log event is colored according to the color function.
1215 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)
1717 }
1818
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)
2122 }
+0
-10
log/term/terminal_darwin.go less more
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
-7
log/term/terminal_freebsd.go less more
0 package term
1
2 import (
3 "syscall"
4 )
5
6 const ioctlReadTermios = syscall.TIOCGETA
+0
-12
log/term/terminal_linux.go less more
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
-25
log/term/terminal_notwindows.go less more
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
-5
log/term/terminal_openbsd.go less more
0 package term
1
2 import "syscall"
3
4 const ioctlReadTermios = syscall.TIOCGETA
+0
-15
log/term/terminal_stub.go less more
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
-102
log/term/terminal_windows.go less more
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
-69
log/term/terminal_windows_test.go less more
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 }
00 package log
11
22 import (
3 "runtime"
4 "strconv"
5 "strings"
63 "time"
4
5 "github.com/go-kit/log"
76 )
87
98 // A Valuer generates a log value. When passed to With, WithPrefix, or
109 // WithSuffix in a value element (odd indexes), it represents a dynamic
1110 // 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
3412
3513 // Timestamp returns a timestamp Valuer. It invokes the t function to get the
3614 // time; unless you are doing something tricky, pass time.Now.
3816 // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
3917 // are TimestampFormats that use the RFC3339Nano format.
4018 func Timestamp(t func() time.Time) Valuer {
41 return func() interface{} { return t() }
19 return log.Timestamp(t)
4220 }
4321
4422 // TimestampFormat returns a timestamp Valuer with a custom time format. It
4927 // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
5028 // are TimestampFormats that use the RFC3339Nano format.
5129 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)
7931 }
8032
8133 // Caller returns a Valuer that returns a file and line from a specified depth
8234 // in the callstack. Users will probably want to use DefaultCaller.
8335 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)
9237 }
9338
9439 var (
9540 // DefaultTimestamp is a Valuer that returns the current wallclock time,
9641 // respecting time zones, when bound.
97 DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano)
42 DefaultTimestamp = log.DefaultTimestamp
9843
9944 // DefaultTimestampUTC is a Valuer that returns the current time in UTC
10045 // when bound.
101 DefaultTimestampUTC = TimestampFormat(
102 func() time.Time { return time.Now().UTC() },
103 time.RFC3339Nano,
104 )
46 DefaultTimestampUTC = log.DefaultTimestampUTC
10547
10648 // DefaultCaller is a Valuer that returns the file and line where the Log
10749 // method was invoked. It can only be used with log.With.
108 DefaultCaller = Caller(3)
50 DefaultCaller = log.DefaultCaller
10951 )
+0
-150
log/value_test.go less more
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