diff --git a/auth/jwt/README.md b/auth/jwt/README.md index 8cf823c..97a3720 100644 --- a/auth/jwt/README.md +++ b/auth/jwt/README.md @@ -95,7 +95,7 @@ "context" "github.com/go-kit/kit/auth/jwt" - "github.com/go-kit/kit/log" + "github.com/go-kit/log" grpctransport "github.com/go-kit/kit/transport/grpc" ) diff --git a/go.mod b/go.mod index ca4014e..473e73b 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,7 @@ github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db // indirect github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect - github.com/go-logfmt/logfmt v0.5.0 - github.com/go-stack/stack v1.8.0 + github.com/go-kit/log v0.1.0 github.com/go-zookeeper/zk v1.0.2 github.com/hashicorp/consul/api v1.8.1 github.com/hudl/fargo v1.3.0 diff --git a/go.sum b/go.sum index d8ab707..7f238d2 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= diff --git a/log/README.md b/log/README.md index a201a3d..5492dd9 100644 --- a/log/README.md +++ b/log/README.md @@ -1,4 +1,13 @@ # package log + +**Deprecation notice:** The core Go kit log packages (log, log/level, log/term, and +log/syslog) have been moved to their own repository at github.com/go-kit/log. +The corresponding packages in this directory remain for backwards compatibility. +Their types alias the types and their functions call the functions provided by +the new repository. Using either import path should be equivalent. Prefer the +new import path when practical. + +______ `package log` provides a minimal interface for structured logging in services. It may be wrapped to encode conventions, enforce type-safety, provide leveled diff --git a/log/benchmark_test.go b/log/benchmark_test.go deleted file mode 100644 index 126bfa5..0000000 --- a/log/benchmark_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package log_test - -import ( - "testing" - - "github.com/go-kit/kit/log" -) - -func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { - lc := log.With(logger, "common_key", "common_value") - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - f(lc) - } -} - -var ( - baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") } - withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") } -) diff --git a/log/concurrency_test.go b/log/concurrency_test.go deleted file mode 100644 index 95a749e..0000000 --- a/log/concurrency_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package log_test - -import ( - "math" - "testing" - - "github.com/go-kit/kit/log" -) - -// These test are designed to be run with the race detector. - -func testConcurrency(t *testing.T, logger log.Logger, total int) { - n := int(math.Sqrt(float64(total))) - share := total / n - - errC := make(chan error, n) - - for i := 0; i < n; i++ { - go func() { - errC <- spam(logger, share) - }() - } - - for i := 0; i < n; i++ { - err := <-errC - if err != nil { - t.Fatalf("concurrent logging error: %v", err) - } - } -} - -func spam(logger log.Logger, count int) error { - for i := 0; i < count; i++ { - err := logger.Log("key", i) - if err != nil { - return err - } - } - return nil -} diff --git a/log/deprecated_levels/levels.go b/log/deprecated_levels/levels.go index a034212..2807cbd 100644 --- a/log/deprecated_levels/levels.go +++ b/log/deprecated_levels/levels.go @@ -1,6 +1,9 @@ +// Package levels implements leveled logging on top of Go kit's log package. +// +// Deprecated: Use github.com/go-kit/log/level instead. package levels -import "github.com/go-kit/kit/log" +import "github.com/go-kit/log" // Levels provides a leveled logging wrapper around a logger. It has five // levels: debug, info, warning (warn), error, and critical (crit). If you diff --git a/log/deprecated_levels/levels_test.go b/log/deprecated_levels/levels_test.go index 8d4a7f5..57854ea 100644 --- a/log/deprecated_levels/levels_test.go +++ b/log/deprecated_levels/levels_test.go @@ -5,8 +5,8 @@ "os" "testing" - "github.com/go-kit/kit/log" levels "github.com/go-kit/kit/log/deprecated_levels" + "github.com/go-kit/log" ) func TestDefaultLevels(t *testing.T) { diff --git a/log/doc.go b/log/doc.go index f744382..c9873f4 100644 --- a/log/doc.go +++ b/log/doc.go @@ -1,4 +1,6 @@ // Package log provides a structured logger. +// +// Deprecated: Use github.com/go-kit/log instead. // // Structured logging produces logs easily consumed later by humans or // machines. Humans might be interested in debugging errors, or tracing diff --git a/log/json_logger.go b/log/json_logger.go index 0cedbf8..edfde2f 100644 --- a/log/json_logger.go +++ b/log/json_logger.go @@ -1,91 +1,15 @@ package log import ( - "encoding" - "encoding/json" - "fmt" "io" - "reflect" + + "github.com/go-kit/log" ) - -type jsonLogger struct { - io.Writer -} // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a // single JSON object. Each log event produces no more than one call to // w.Write. The passed Writer must be safe for concurrent use by multiple // goroutines if the returned Logger will be used concurrently. func NewJSONLogger(w io.Writer) Logger { - return &jsonLogger{w} + return log.NewJSONLogger(w) } - -func (l *jsonLogger) Log(keyvals ...interface{}) error { - n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd - m := make(map[string]interface{}, n) - for i := 0; i < len(keyvals); i += 2 { - k := keyvals[i] - var v interface{} = ErrMissingValue - if i+1 < len(keyvals) { - v = keyvals[i+1] - } - merge(m, k, v) - } - enc := json.NewEncoder(l.Writer) - enc.SetEscapeHTML(false) - return enc.Encode(m) -} - -func merge(dst map[string]interface{}, k, v interface{}) { - var key string - switch x := k.(type) { - case string: - key = x - case fmt.Stringer: - key = safeString(x) - default: - key = fmt.Sprint(x) - } - - // We want json.Marshaler and encoding.TextMarshaller to take priority over - // err.Error() and v.String(). But json.Marshall (called later) does that by - // default so we force a no-op if it's one of those 2 case. - switch x := v.(type) { - case json.Marshaler: - case encoding.TextMarshaler: - case error: - v = safeError(x) - case fmt.Stringer: - v = safeString(x) - } - - dst[key] = v -} - -func safeString(str fmt.Stringer) (s string) { - defer func() { - if panicVal := recover(); panicVal != nil { - if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { - s = "NULL" - } else { - panic(panicVal) - } - } - }() - s = str.String() - return -} - -func safeError(err error) (s interface{}) { - defer func() { - if panicVal := recover(); panicVal != nil { - if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { - s = nil - } else { - panic(panicVal) - } - } - }() - s = err.Error() - return -} diff --git a/log/json_logger_test.go b/log/json_logger_test.go deleted file mode 100644 index 6432fc8..0000000 --- a/log/json_logger_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package log_test - -import ( - "bytes" - "errors" - "io/ioutil" - "testing" - - "github.com/go-kit/kit/log" -) - -func TestJSONLoggerCaller(t *testing.T) { - t.Parallel() - buf := &bytes.Buffer{} - logger := log.NewJSONLogger(buf) - logger = log.With(logger, "caller", log.DefaultCaller) - - if err := logger.Log(); err != nil { - t.Fatal(err) - } - if want, have := `{"caller":"json_logger_test.go:18"}`+"\n", buf.String(); want != have { - t.Errorf("\nwant %#v\nhave %#v", want, have) - } -} - -func TestJSONLogger(t *testing.T) { - t.Parallel() - buf := &bytes.Buffer{} - logger := log.NewJSONLogger(buf) - if err := logger.Log("err", errors.New("err"), "m", map[string]int{"0": 0}, "a", []int{1, 2, 3}); err != nil { - t.Fatal(err) - } - if want, have := `{"a":[1,2,3],"err":"err","m":{"0":0}}`+"\n", buf.String(); want != have { - t.Errorf("\nwant %#v\nhave %#v", want, have) - } -} - -func TestJSONLoggerMissingValue(t *testing.T) { - t.Parallel() - buf := &bytes.Buffer{} - logger := log.NewJSONLogger(buf) - if err := logger.Log("k"); err != nil { - t.Fatal(err) - } - if want, have := `{"k":"(MISSING)"}`+"\n", buf.String(); want != have { - t.Errorf("\nwant %#v\nhave %#v", want, have) - } -} - -func TestJSONLoggerNilStringerKey(t *testing.T) { - t.Parallel() - - buf := &bytes.Buffer{} - logger := log.NewJSONLogger(buf) - if err := logger.Log((*stringer)(nil), "v"); err != nil { - t.Fatal(err) - } - if want, have := `{"NULL":"v"}`+"\n", buf.String(); want != have { - t.Errorf("\nwant %#v\nhave %#v", want, have) - } -} - -func TestJSONLoggerNilErrorValue(t *testing.T) { - t.Parallel() - - buf := &bytes.Buffer{} - logger := log.NewJSONLogger(buf) - if err := logger.Log("err", (*stringError)(nil)); err != nil { - t.Fatal(err) - } - if want, have := `{"err":null}`+"\n", buf.String(); want != have { - t.Errorf("\nwant %#v\nhave %#v", want, have) - } -} - -func TestJSONLoggerNoHTMLEscape(t *testing.T) { - t.Parallel() - buf := &bytes.Buffer{} - logger := log.NewJSONLogger(buf) - if err := logger.Log("k", "<&>"); err != nil { - t.Fatal(err) - } - if want, have := `{"k":"<&>"}`+"\n", buf.String(); want != have { - t.Errorf("\nwant %#v\nhave%#v", want, have) - } -} - -// aller implements json.Marshaler, encoding.TextMarshaler, and fmt.Stringer. -type aller struct{} - -func (aller) MarshalJSON() ([]byte, error) { - return []byte("\"json\""), nil -} - -func (aller) MarshalText() ([]byte, error) { - return []byte("text"), nil -} - -func (aller) String() string { - return "string" -} - -func (aller) Error() string { - return "error" -} - -// textstringer implements encoding.TextMarshaler and fmt.Stringer. -type textstringer struct{} - -func (textstringer) MarshalText() ([]byte, error) { - return []byte("text"), nil -} - -func (textstringer) String() string { - return "string" -} - -func TestJSONLoggerStringValue(t *testing.T) { - t.Parallel() - tests := []struct { - v interface{} - expected string - }{ - { - v: aller{}, - expected: `{"v":"json"}`, - }, - { - v: textstringer{}, - expected: `{"v":"text"}`, - }, - { - v: stringer("string"), - expected: `{"v":"string"}`, - }, - } - - for _, test := range tests { - buf := &bytes.Buffer{} - logger := log.NewJSONLogger(buf) - if err := logger.Log("v", test.v); err != nil { - t.Fatal(err) - } - - if want, have := test.expected+"\n", buf.String(); want != have { - t.Errorf("\nwant %#v\nhave %#v", want, have) - } - } -} - -type stringer string - -func (s stringer) String() string { - return string(s) -} - -type stringError string - -func (s stringError) Error() string { - return string(s) -} - -func BenchmarkJSONLoggerSimple(b *testing.B) { - benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), baseMessage) -} - -func BenchmarkJSONLoggerContextual(b *testing.B) { - benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), withMessage) -} - -func TestJSONLoggerConcurrency(t *testing.T) { - t.Parallel() - testConcurrency(t, log.NewJSONLogger(ioutil.Discard), 10000) -} diff --git a/log/level/benchmark_test.go b/log/level/benchmark_test.go deleted file mode 100644 index 4fca6f0..0000000 --- a/log/level/benchmark_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package level_test - -import ( - "io/ioutil" - "testing" - - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" -) - -func Benchmark(b *testing.B) { - contexts := []struct { - name string - context func(log.Logger) log.Logger - }{ - {"NoContext", func(l log.Logger) log.Logger { - return l - }}, - {"TimeContext", func(l log.Logger) log.Logger { - return log.With(l, "time", log.DefaultTimestampUTC) - }}, - {"CallerContext", func(l log.Logger) log.Logger { - return log.With(l, "caller", log.DefaultCaller) - }}, - {"TimeCallerReqIDContext", func(l log.Logger) log.Logger { - return log.With(l, "time", log.DefaultTimestampUTC, "caller", log.DefaultCaller, "reqID", 29) - }}, - } - - loggers := []struct { - name string - logger log.Logger - }{ - {"Nop", log.NewNopLogger()}, - {"Logfmt", log.NewLogfmtLogger(ioutil.Discard)}, - {"JSON", log.NewJSONLogger(ioutil.Discard)}, - } - - filters := []struct { - name string - filter func(log.Logger) log.Logger - }{ - {"Baseline", func(l log.Logger) log.Logger { - return l - }}, - {"DisallowedLevel", func(l log.Logger) log.Logger { - return level.NewFilter(l, level.AllowInfo()) - }}, - {"AllowedLevel", func(l log.Logger) log.Logger { - return level.NewFilter(l, level.AllowAll()) - }}, - } - - for _, c := range contexts { - b.Run(c.name, func(b *testing.B) { - for _, f := range filters { - b.Run(f.name, func(b *testing.B) { - for _, l := range loggers { - b.Run(l.name, func(b *testing.B) { - logger := c.context(f.filter(l.logger)) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - level.Debug(logger).Log("foo", "bar") - } - }) - } - }) - } - }) - } -} diff --git a/log/level/doc.go b/log/level/doc.go index 505d307..7baf870 100644 --- a/log/level/doc.go +++ b/log/level/doc.go @@ -1,6 +1,9 @@ -// Package level implements leveled logging on top of Go kit's log package. To -// use the level package, create a logger as per normal in your func main, and -// wrap it with level.NewFilter. +// Package level implements leveled logging on top of Go kit's log package. +// +// Deprecated: Use github.com/go-kit/log/level instead. +// +// To use the level package, create a logger as per normal in your func main, +// and wrap it with level.NewFilter. // // var logger log.Logger // logger = log.NewLogfmtLogger(os.Stderr) diff --git a/log/level/level.go b/log/level/level.go index c0c050f..803e8b9 100644 --- a/log/level/level.go +++ b/log/level/level.go @@ -1,25 +1,28 @@ package level -import "github.com/go-kit/kit/log" +import ( + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) // Error returns a logger that includes a Key/ErrorValue pair. func Error(logger log.Logger) log.Logger { - return log.WithPrefix(logger, Key(), ErrorValue()) + return level.Error(logger) } // Warn returns a logger that includes a Key/WarnValue pair. func Warn(logger log.Logger) log.Logger { - return log.WithPrefix(logger, Key(), WarnValue()) + return level.Warn(logger) } // Info returns a logger that includes a Key/InfoValue pair. func Info(logger log.Logger) log.Logger { - return log.WithPrefix(logger, Key(), InfoValue()) + return level.Info(logger) } // Debug returns a logger that includes a Key/DebugValue pair. func Debug(logger log.Logger) log.Logger { - return log.WithPrefix(logger, Key(), DebugValue()) + return level.Debug(logger) } // NewFilter wraps next and implements level filtering. See the commentary on @@ -28,76 +31,40 @@ // Info, Warn or Error helper methods are squelched and non-leveled log // events are passed to next unmodified. func NewFilter(next log.Logger, options ...Option) log.Logger { - l := &logger{ - next: next, - } - for _, option := range options { - option(l) - } - return l -} - -type logger struct { - next log.Logger - allowed level - squelchNoLevel bool - errNotAllowed error - errNoLevel error -} - -func (l *logger) Log(keyvals ...interface{}) error { - var hasLevel, levelAllowed bool - for i := 1; i < len(keyvals); i += 2 { - if v, ok := keyvals[i].(*levelValue); ok { - hasLevel = true - levelAllowed = l.allowed&v.level != 0 - break - } - } - if !hasLevel && l.squelchNoLevel { - return l.errNoLevel - } - if hasLevel && !levelAllowed { - return l.errNotAllowed - } - return l.next.Log(keyvals...) + return level.NewFilter(next, options...) } // Option sets a parameter for the leveled logger. -type Option func(*logger) +type Option = level.Option // AllowAll is an alias for AllowDebug. func AllowAll() Option { - return AllowDebug() + return level.AllowAll() } // AllowDebug allows error, warn, info and debug level log events to pass. func AllowDebug() Option { - return allowed(levelError | levelWarn | levelInfo | levelDebug) + return level.AllowDebug() } // AllowInfo allows error, warn and info level log events to pass. func AllowInfo() Option { - return allowed(levelError | levelWarn | levelInfo) + return level.AllowInfo() } // AllowWarn allows error and warn level log events to pass. func AllowWarn() Option { - return allowed(levelError | levelWarn) + return level.AllowWarn() } // AllowError allows only error level log events to pass. func AllowError() Option { - return allowed(levelError) + return level.AllowError() } // AllowNone allows no leveled log events to pass. func AllowNone() Option { - return allowed(0) -} - -func allowed(allowed level) Option { - return func(l *logger) { l.allowed = allowed } + return level.AllowNone() } // ErrNotAllowed sets the error to return from Log when it squelches a log @@ -105,7 +72,7 @@ // ErrNotAllowed is nil; in this case the log event is squelched with no // error. func ErrNotAllowed(err error) Option { - return func(l *logger) { l.errNotAllowed = err } + return level.ErrNotAllowed(err) } // SquelchNoLevel instructs Log to squelch log events with no level, so that @@ -113,93 +80,41 @@ // to true and a log event is squelched in this way, the error value // configured with ErrNoLevel is returned to the caller. func SquelchNoLevel(squelch bool) Option { - return func(l *logger) { l.squelchNoLevel = squelch } + return level.SquelchNoLevel(squelch) } // ErrNoLevel sets the error to return from Log when it squelches a log event // with no level. By default, ErrNoLevel is nil; in this case the log event is // squelched with no error. func ErrNoLevel(err error) Option { - return func(l *logger) { l.errNoLevel = err } + return level.ErrNoLevel(err) } // NewInjector wraps next and returns a logger that adds a Key/level pair to // the beginning of log events that don't already contain a level. In effect, // this gives a default level to logs without a level. -func NewInjector(next log.Logger, level Value) log.Logger { - return &injector{ - next: next, - level: level, - } -} - -type injector struct { - next log.Logger - level interface{} -} - -func (l *injector) Log(keyvals ...interface{}) error { - for i := 1; i < len(keyvals); i += 2 { - if _, ok := keyvals[i].(*levelValue); ok { - return l.next.Log(keyvals...) - } - } - kvs := make([]interface{}, len(keyvals)+2) - kvs[0], kvs[1] = key, l.level - copy(kvs[2:], keyvals) - return l.next.Log(kvs...) +func NewInjector(next log.Logger, lvl Value) log.Logger { + return level.NewInjector(next, lvl) } // Value is the interface that each of the canonical level values implement. // It contains unexported methods that prevent types from other packages from // implementing it and guaranteeing that NewFilter can distinguish the levels // defined in this package from all other values. -type Value interface { - String() string - levelVal() -} +type Value = level.Value // Key returns the unique key added to log events by the loggers in this // package. -func Key() interface{} { return key } +func Key() interface{} { return level.Key() } // ErrorValue returns the unique value added to log events by Error. -func ErrorValue() Value { return errorValue } +func ErrorValue() Value { return level.ErrorValue() } // WarnValue returns the unique value added to log events by Warn. -func WarnValue() Value { return warnValue } +func WarnValue() Value { return level.WarnValue() } // InfoValue returns the unique value added to log events by Info. -func InfoValue() Value { return infoValue } +func InfoValue() Value { return level.InfoValue() } // DebugValue returns the unique value added to log events by Debug. -func DebugValue() Value { return debugValue } - -var ( - // key is of type interface{} so that it allocates once during package - // initialization and avoids allocating every time the value is added to a - // []interface{} later. - key interface{} = "level" - - errorValue = &levelValue{level: levelError, name: "error"} - warnValue = &levelValue{level: levelWarn, name: "warn"} - infoValue = &levelValue{level: levelInfo, name: "info"} - debugValue = &levelValue{level: levelDebug, name: "debug"} -) - -type level byte - -const ( - levelDebug level = 1 << iota - levelInfo - levelWarn - levelError -) - -type levelValue struct { - name string - level -} - -func (v *levelValue) String() string { return v.name } -func (v *levelValue) levelVal() {} +func DebugValue() Value { return level.DebugValue() } diff --git a/log/level/level_test.go b/log/level/level_test.go deleted file mode 100644 index e362eff..0000000 --- a/log/level/level_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package level_test - -import ( - "bytes" - "errors" - "io" - "strings" - "testing" - - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" -) - -func TestVariousLevels(t *testing.T) { - testCases := []struct { - name string - allowed level.Option - want string - }{ - { - "AllowAll", - level.AllowAll(), - strings.Join([]string{ - `{"level":"debug","this is":"debug log"}`, - `{"level":"info","this is":"info log"}`, - `{"level":"warn","this is":"warn log"}`, - `{"level":"error","this is":"error log"}`, - }, "\n"), - }, - { - "AllowDebug", - level.AllowDebug(), - strings.Join([]string{ - `{"level":"debug","this is":"debug log"}`, - `{"level":"info","this is":"info log"}`, - `{"level":"warn","this is":"warn log"}`, - `{"level":"error","this is":"error log"}`, - }, "\n"), - }, - { - "AllowInfo", - level.AllowInfo(), - strings.Join([]string{ - `{"level":"info","this is":"info log"}`, - `{"level":"warn","this is":"warn log"}`, - `{"level":"error","this is":"error log"}`, - }, "\n"), - }, - { - "AllowWarn", - level.AllowWarn(), - strings.Join([]string{ - `{"level":"warn","this is":"warn log"}`, - `{"level":"error","this is":"error log"}`, - }, "\n"), - }, - { - "AllowError", - level.AllowError(), - strings.Join([]string{ - `{"level":"error","this is":"error log"}`, - }, "\n"), - }, - { - "AllowNone", - level.AllowNone(), - ``, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - logger := level.NewFilter(log.NewJSONLogger(&buf), tc.allowed) - - level.Debug(logger).Log("this is", "debug log") - level.Info(logger).Log("this is", "info log") - level.Warn(logger).Log("this is", "warn log") - level.Error(logger).Log("this is", "error log") - - if want, have := tc.want, strings.TrimSpace(buf.String()); want != have { - t.Errorf("\nwant:\n%s\nhave:\n%s", want, have) - } - }) - } -} - -func TestErrNotAllowed(t *testing.T) { - myError := errors.New("squelched!") - opts := []level.Option{ - level.AllowWarn(), - level.ErrNotAllowed(myError), - } - logger := level.NewFilter(log.NewNopLogger(), opts...) - - if want, have := myError, level.Info(logger).Log("foo", "bar"); want != have { - t.Errorf("want %#+v, have %#+v", want, have) - } - - if want, have := error(nil), level.Warn(logger).Log("foo", "bar"); want != have { - t.Errorf("want %#+v, have %#+v", want, have) - } -} - -func TestErrNoLevel(t *testing.T) { - myError := errors.New("no level specified") - - var buf bytes.Buffer - opts := []level.Option{ - level.SquelchNoLevel(true), - level.ErrNoLevel(myError), - } - logger := level.NewFilter(log.NewJSONLogger(&buf), opts...) - - if want, have := myError, logger.Log("foo", "bar"); want != have { - t.Errorf("want %v, have %v", want, have) - } - if want, have := ``, strings.TrimSpace(buf.String()); want != have { - t.Errorf("\nwant '%s'\nhave '%s'", want, have) - } -} - -func TestAllowNoLevel(t *testing.T) { - var buf bytes.Buffer - opts := []level.Option{ - level.SquelchNoLevel(false), - level.ErrNoLevel(errors.New("I should never be returned!")), - } - logger := level.NewFilter(log.NewJSONLogger(&buf), opts...) - - if want, have := error(nil), logger.Log("foo", "bar"); want != have { - t.Errorf("want %v, have %v", want, have) - } - if want, have := `{"foo":"bar"}`, strings.TrimSpace(buf.String()); want != have { - t.Errorf("\nwant '%s'\nhave '%s'", want, have) - } -} - -func TestLevelContext(t *testing.T) { - var buf bytes.Buffer - - // Wrapping the level logger with a context allows users to use - // log.DefaultCaller as per normal. - var logger log.Logger - logger = log.NewLogfmtLogger(&buf) - logger = level.NewFilter(logger, level.AllowAll()) - logger = log.With(logger, "caller", log.DefaultCaller) - - level.Info(logger).Log("foo", "bar") - if want, have := `level=info caller=level_test.go:149 foo=bar`, strings.TrimSpace(buf.String()); want != have { - t.Errorf("\nwant '%s'\nhave '%s'", want, have) - } -} - -func TestContextLevel(t *testing.T) { - var buf bytes.Buffer - - // Wrapping a context with the level logger still works, but requires users - // to specify a higher callstack depth value. - var logger log.Logger - logger = log.NewLogfmtLogger(&buf) - logger = log.With(logger, "caller", log.Caller(5)) - logger = level.NewFilter(logger, level.AllowAll()) - - level.Info(logger).Log("foo", "bar") - if want, have := `caller=level_test.go:165 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have { - t.Errorf("\nwant '%s'\nhave '%s'", want, have) - } -} - -func TestLevelFormatting(t *testing.T) { - testCases := []struct { - name string - format func(io.Writer) log.Logger - output string - }{ - { - name: "logfmt", - format: log.NewLogfmtLogger, - output: `level=info foo=bar`, - }, - { - name: "JSON", - format: log.NewJSONLogger, - output: `{"foo":"bar","level":"info"}`, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - - logger := tc.format(&buf) - level.Info(logger).Log("foo", "bar") - if want, have := tc.output, strings.TrimSpace(buf.String()); want != have { - t.Errorf("\nwant: '%s'\nhave '%s'", want, have) - } - }) - } -} - -func TestInjector(t *testing.T) { - var ( - output []interface{} - logger log.Logger - ) - - logger = log.LoggerFunc(func(keyvals ...interface{}) error { - output = keyvals - return nil - }) - logger = level.NewInjector(logger, level.InfoValue()) - - logger.Log("foo", "bar") - if got, want := len(output), 4; got != want { - t.Errorf("missing level not injected: got len==%d, want len==%d", got, want) - } - if got, want := output[0], level.Key(); got != want { - t.Errorf("wrong level key: got %#v, want %#v", got, want) - } - if got, want := output[1], level.InfoValue(); got != want { - t.Errorf("wrong level value: got %#v, want %#v", got, want) - } - - level.Error(logger).Log("foo", "bar") - if got, want := len(output), 4; got != want { - t.Errorf("leveled record modified: got len==%d, want len==%d", got, want) - } - if got, want := output[0], level.Key(); got != want { - t.Errorf("wrong level key: got %#v, want %#v", got, want) - } - if got, want := output[1], level.ErrorValue(); got != want { - t.Errorf("wrong level value: got %#v, want %#v", got, want) - } -} diff --git a/log/log.go b/log/log.go index 29b3b82..164a4f9 100644 --- a/log/log.go +++ b/log/log.go @@ -1,19 +1,19 @@ package log -import "errors" +import ( + "github.com/go-kit/log" +) // Logger is the fundamental interface for all log operations. Log creates a // log event from keyvals, a variadic sequence of alternating keys and values. // Implementations must be safe for concurrent use by multiple goroutines. In // particular, any implementation of Logger that appends to keyvals or // modifies or retains any of its elements must make a copy first. -type Logger interface { - Log(keyvals ...interface{}) error -} +type Logger = log.Logger // ErrMissingValue is appended to keyvals slices with odd length to substitute // the missing value. -var ErrMissingValue = errors.New("(MISSING)") +var ErrMissingValue = log.ErrMissingValue // With returns a new contextual logger with keyvals prepended to those passed // to calls to Log. If logger is also a contextual logger created by With, @@ -22,25 +22,7 @@ // The returned Logger replaces all value elements (odd indexes) containing a // Valuer with their generated value for each call to its Log method. func With(logger Logger, keyvals ...interface{}) Logger { - if len(keyvals) == 0 { - return logger - } - l := newContext(logger) - kvs := append(l.keyvals, keyvals...) - if len(kvs)%2 != 0 { - kvs = append(kvs, ErrMissingValue) - } - return &context{ - logger: l.logger, - // Limiting the capacity of the stored keyvals ensures that a new - // backing array is created if the slice must grow in Log or With. - // Using the extra capacity without copying risks a data race that - // would violate the Logger interface contract. - keyvals: kvs[:len(kvs):len(kvs)], - hasValuer: l.hasValuer || containsValuer(keyvals), - sKeyvals: l.sKeyvals, - sHasValuer: l.sHasValuer, - } + return log.With(logger, keyvals...) } // WithPrefix returns a new contextual logger with keyvals prepended to those @@ -50,31 +32,7 @@ // The returned Logger replaces all value elements (odd indexes) containing a // Valuer with their generated value for each call to its Log method. func WithPrefix(logger Logger, keyvals ...interface{}) Logger { - if len(keyvals) == 0 { - return logger - } - l := newContext(logger) - // Limiting the capacity of the stored keyvals ensures that a new - // backing array is created if the slice must grow in Log or With. - // Using the extra capacity without copying risks a data race that - // would violate the Logger interface contract. - n := len(l.keyvals) + len(keyvals) - if len(keyvals)%2 != 0 { - n++ - } - kvs := make([]interface{}, 0, n) - kvs = append(kvs, keyvals...) - if len(kvs)%2 != 0 { - kvs = append(kvs, ErrMissingValue) - } - kvs = append(kvs, l.keyvals...) - return &context{ - logger: l.logger, - keyvals: kvs, - hasValuer: l.hasValuer || containsValuer(keyvals), - sKeyvals: l.sKeyvals, - sHasValuer: l.sHasValuer, - } + return log.WithPrefix(logger, keyvals...) } // WithSuffix returns a new contextual logger with keyvals appended to those @@ -84,96 +42,10 @@ // The returned Logger replaces all value elements (odd indexes) containing a // Valuer with their generated value for each call to its Log method. func WithSuffix(logger Logger, keyvals ...interface{}) Logger { - if len(keyvals) == 0 { - return logger - } - l := newContext(logger) - // Limiting the capacity of the stored keyvals ensures that a new - // backing array is created if the slice must grow in Log or With. - // Using the extra capacity without copying risks a data race that - // would violate the Logger interface contract. - n := len(l.sKeyvals) + len(keyvals) - if len(keyvals)%2 != 0 { - n++ - } - kvs := make([]interface{}, 0, n) - kvs = append(kvs, keyvals...) - if len(kvs)%2 != 0 { - kvs = append(kvs, ErrMissingValue) - } - kvs = append(l.sKeyvals, kvs...) - return &context{ - logger: l.logger, - keyvals: l.keyvals, - hasValuer: l.hasValuer, - sKeyvals: kvs, - sHasValuer: l.sHasValuer || containsValuer(keyvals), - } -} - -// context is the Logger implementation returned by With, WithPrefix, and -// WithSuffix. It wraps a Logger and holds keyvals that it includes in all -// log events. Its Log method calls bindValues to generate values for each -// Valuer in the context keyvals. -// -// A context must always have the same number of stack frames between calls to -// its Log method and the eventual binding of Valuers to their value. This -// requirement comes from the functional requirement to allow a context to -// resolve application call site information for a Caller stored in the -// context. To do this we must be able to predict the number of logging -// functions on the stack when bindValues is called. -// -// Two implementation details provide the needed stack depth consistency. -// -// 1. newContext avoids introducing an additional layer when asked to -// wrap another context. -// 2. With, WithPrefix, and WithSuffix avoid introducing an additional -// layer by returning a newly constructed context with a merged keyvals -// rather than simply wrapping the existing context. -type context struct { - logger Logger - keyvals []interface{} - sKeyvals []interface{} // suffixes - hasValuer bool - sHasValuer bool -} - -func newContext(logger Logger) *context { - if c, ok := logger.(*context); ok { - return c - } - return &context{logger: logger} -} - -// Log replaces all value elements (odd indexes) containing a Valuer in the -// stored context with their generated value, appends keyvals, and passes the -// result to the wrapped Logger. -func (l *context) Log(keyvals ...interface{}) error { - kvs := append(l.keyvals, keyvals...) - if len(kvs)%2 != 0 { - kvs = append(kvs, ErrMissingValue) - } - if l.hasValuer { - // If no keyvals were appended above then we must copy l.keyvals so - // that future log events will reevaluate the stored Valuers. - if len(keyvals) == 0 { - kvs = append([]interface{}{}, l.keyvals...) - } - bindValues(kvs[:(len(l.keyvals))]) - } - kvs = append(kvs, l.sKeyvals...) - if l.sHasValuer { - bindValues(kvs[len(kvs) - len(l.sKeyvals):]) - } - return l.logger.Log(kvs...) + return log.WithSuffix(logger, keyvals...) } // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If // f is a function with the appropriate signature, LoggerFunc(f) is a Logger // object that calls f. -type LoggerFunc func(...interface{}) error - -// Log implements Logger by calling f(keyvals...). -func (f LoggerFunc) Log(keyvals ...interface{}) error { - return f(keyvals...) -} +type LoggerFunc = log.LoggerFunc diff --git a/log/log_test.go b/log/log_test.go deleted file mode 100644 index 39c4a8b..0000000 --- a/log/log_test.go +++ /dev/null @@ -1,349 +0,0 @@ -package log_test - -import ( - "bytes" - "fmt" - "sync" - "testing" - - "github.com/go-kit/kit/log" - "github.com/go-stack/stack" -) - -func TestContext(t *testing.T) { - t.Parallel() - buf := &bytes.Buffer{} - logger := log.NewLogfmtLogger(buf) - - kvs := []interface{}{"a", 123} - lc := log.With(logger, kvs...) - kvs[1] = 0 // With should copy its key values - - lc = log.With(lc, "b", "c") // With should stack - if err := lc.Log("msg", "message"); err != nil { - t.Fatal(err) - } - if want, have := "a=123 b=c msg=message\n", buf.String(); want != have { - t.Errorf("\nwant: %shave: %s", want, have) - } - - buf.Reset() - lc = log.WithPrefix(lc, "p", "first") - if err := lc.Log("msg", "message"); err != nil { - t.Fatal(err) - } - if want, have := "p=first a=123 b=c msg=message\n", buf.String(); want != have { - t.Errorf("\nwant: %shave: %s", want, have) - } -} - -func TestContextMissingValue(t *testing.T) { - t.Parallel() - var output []interface{} - logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { - output = keyvals - return nil - })) - - log.WithPrefix(log.With(logger, "k1"), "k0").Log("k2") - if want, have := 6, len(output); want != have { - t.Errorf("want len(output) == %v, have %v", want, have) - } - for i := 1; i < 6; i += 2 { - if want, have := log.ErrMissingValue, output[i]; want != have { - t.Errorf("want output[%d] == %#v, have %#v", i, want, have) - } - } -} - -func TestWithPrefixAndSuffix(t *testing.T) { - t.Parallel() - var output []interface{} - logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { - output = keyvals - return nil - })) - - lc := log.WithPrefix(logger, "a", "first") - lc = log.WithSuffix(lc, "z", "last") - if err := lc.Log("msg", "message"); err != nil { - t.Fatal(err) - } - if want, have := 6, len(output); want != have { - t.Errorf("want len(output) == %v, have %v", want, have) - } - want := []string{"a", "first", "msg", "message", "z", "last"} - for i := 0; i < 6; i++ { - if want, have := want[i], output[i]; want != have { - t.Errorf("want output[%d] == %#v, have %#v", i, want, have) - } - } - - lc = log.With(logger, "b", "second") - lc = log.WithPrefix(lc, "a", "first") - lc = log.With(lc, "c", "third") - lc = log.WithSuffix(lc, "z", "last") - lc = log.WithSuffix(lc, "aa", "sequel") - if err := lc.Log("msg", "message"); err != nil { - t.Fatal(err) - } - if want, have := 12, len(output); want != have { - t.Errorf("want len(output) == %v, have %v", want, have) - } - want = []string{ - "a", "first", - "b", "second", - "c", "third", - "msg", "message", - "z", "last", - "aa", "sequel", - } - for i := 0; i < 12; i++ { - if want, have := want[i], output[i]; want != have { - t.Errorf("want output[%d] == %#v, have %#v", i, want, have) - } - } -} - -// Test that context.Log has a consistent function stack depth when binding -// Valuers, regardless of how many times With has been called. -func TestContextStackDepth(t *testing.T) { - t.Parallel() - fn := fmt.Sprintf("%n", stack.Caller(0)) - - var output []interface{} - - logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { - output = keyvals - return nil - })) - - stackValuer := log.Valuer(func() interface{} { - for i, c := range stack.Trace() { - if fmt.Sprintf("%n", c) == fn { - return i - } - } - t.Fatal("Test function not found in stack trace.") - return nil - }) - - logger = log.With(logger, "stack", stackValuer) - - // Call through interface to get baseline. - logger.Log("k", "v") - want := output[1].(int) - - for len(output) < 10 { - logger.Log("k", "v") - if have := output[1]; have != want { - t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) - } - - wrapped := log.With(logger) - wrapped.Log("k", "v") - if have := output[1]; have != want { - t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) - } - - logger = log.With(logger, "k", "v") - } -} - -// Test that With returns a Logger safe for concurrent use. This test -// validates that the stored logging context does not get corrupted when -// multiple clients concurrently log additional keyvals. -// -// This test must be run with go test -cpu 2 (or more) to achieve its goal. -func TestWithConcurrent(t *testing.T) { - // Create some buckets to count how many events each goroutine logs. - const goroutines = 8 - counts := [goroutines]int{} - - // This logger extracts a goroutine id from the last value field and - // increments the referenced bucket. - logger := log.LoggerFunc(func(kv ...interface{}) error { - goroutine := kv[len(kv)-1].(int) - counts[goroutine]++ - return nil - }) - - // With must be careful about handling slices that can grow without - // copying the underlying array, so give it a challenge. - l := log.With(logger, make([]interface{}, 0, 2)...) - - // Start logging concurrently. Each goroutine logs its id so the logger - // can bucket the event counts. - var wg sync.WaitGroup - wg.Add(goroutines) - const n = 10000 - for i := 0; i < goroutines; i++ { - go func(idx int) { - defer wg.Done() - for j := 0; j < n; j++ { - l.Log("goroutineIdx", idx) - } - }(i) - } - wg.Wait() - - for bucket, have := range counts { - if want := n; want != have { - t.Errorf("bucket %d: want %d, have %d", bucket, want, have) // note Errorf - } - } -} - -func TestLogCopiesValuers(t *testing.T) { - t.Parallel() - var output []interface{} - logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { - output = keyvals - return nil - })) - - valuerCallCount := 0 - counterValuer := log.Valuer(func() interface{} { - valuerCallCount++ - return valuerCallCount - }) - lc := log.WithPrefix(logger, "a", counterValuer) - lc = log.WithSuffix(lc, "z", counterValuer) - - if err := lc.Log(); err != nil { - t.Fatal(err) - } - want := []interface{}{"a", 1, "z", 2} - for i := 0; i < 4; i++ { - if want, have := want[i], output[i]; want != have { - t.Errorf("want output[%d] == %#v, have %#v", i, want, have) - } - } - - if err := lc.Log(); err != nil { - t.Fatal(err) - } - want = []interface{}{"a", 3, "z", 4} - for i := 0; i < 4; i++ { - if want, have := want[i], output[i]; want != have { - t.Errorf("want output[%d] == %#v, have %#v", i, want, have) - } - } -} - -func BenchmarkDiscard(b *testing.B) { - logger := log.NewNopLogger() - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - logger.Log("k", "v") - } -} - -func BenchmarkOneWith(b *testing.B) { - logger := log.NewNopLogger() - lc := log.With(logger, "k", "v") - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkTwoWith(b *testing.B) { - logger := log.NewNopLogger() - lc := log.With(logger, "k", "v") - for i := 1; i < 2; i++ { - lc = log.With(lc, "k", "v") - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkTenWith(b *testing.B) { - logger := log.NewNopLogger() - lc := log.With(logger, "k", "v") - for i := 1; i < 10; i++ { - lc = log.With(lc, "k", "v") - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkOneWithPrefix(b *testing.B) { - logger := log.NewNopLogger() - lc := log.WithPrefix(logger, "a", "first") - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkTenWithPrefix(b *testing.B) { - logger := log.NewNopLogger() - lc := log.WithPrefix(logger, "a", "first") - for i := 1; i < 10; i++ { - lc = log.WithPrefix(lc, "a", "first") - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkOneWithSuffix(b *testing.B) { - logger := log.NewNopLogger() - lc := log.WithSuffix(logger, "z", "last") - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkTenWithSuffix(b *testing.B) { - logger := log.NewNopLogger() - lc := log.WithSuffix(logger, "z", "last") - for i := 1; i < 10; i++ { - lc = log.WithSuffix(lc, "z", "last") - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkOneWithPrefixSuffix(b *testing.B) { - logger := log.NewNopLogger() - lc := log.WithSuffix(logger, "a", "first") - lc = log.WithSuffix(lc, "z", "last") - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} - -func BenchmarkTenWithPrefixSuffix(b *testing.B) { - logger := log.NewNopLogger() - lc := log.WithPrefix(logger, "a", "first") - lc = log.WithSuffix(lc, "z", "last") - for i := 1; i < 10; i++ { - lc = log.WithPrefix(lc, "a", "first") - lc = log.WithSuffix(lc, "z", "last") - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - lc.Log("k", "v") - } -} diff --git a/log/logfmt_logger.go b/log/logfmt_logger.go index a003052..51cde2c 100644 --- a/log/logfmt_logger.go +++ b/log/logfmt_logger.go @@ -1,62 +1,15 @@ package log import ( - "bytes" "io" - "sync" - "github.com/go-logfmt/logfmt" + "github.com/go-kit/log" ) - -type logfmtEncoder struct { - *logfmt.Encoder - buf bytes.Buffer -} - -func (l *logfmtEncoder) Reset() { - l.Encoder.Reset() - l.buf.Reset() -} - -var logfmtEncoderPool = sync.Pool{ - New: func() interface{} { - var enc logfmtEncoder - enc.Encoder = logfmt.NewEncoder(&enc.buf) - return &enc - }, -} - -type logfmtLogger struct { - w io.Writer -} // NewLogfmtLogger returns a logger that encodes keyvals to the Writer in // logfmt format. Each log event produces no more than one call to w.Write. // The passed Writer must be safe for concurrent use by multiple goroutines if // the returned Logger will be used concurrently. func NewLogfmtLogger(w io.Writer) Logger { - return &logfmtLogger{w} + return log.NewLogfmtLogger(w) } - -func (l logfmtLogger) Log(keyvals ...interface{}) error { - enc := logfmtEncoderPool.Get().(*logfmtEncoder) - enc.Reset() - defer logfmtEncoderPool.Put(enc) - - if err := enc.EncodeKeyvals(keyvals...); err != nil { - return err - } - - // Add newline to the end of the buffer - if err := enc.EndRecord(); err != nil { - return err - } - - // The Logger interface requires implementations to be safe for concurrent - // use by multiple goroutines. For this implementation that means making - // only one call to l.w.Write() for each call to Log. - if _, err := l.w.Write(enc.buf.Bytes()); err != nil { - return err - } - return nil -} diff --git a/log/logfmt_logger_test.go b/log/logfmt_logger_test.go deleted file mode 100644 index 91bbca1..0000000 --- a/log/logfmt_logger_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package log_test - -import ( - "bytes" - "errors" - "io/ioutil" - "testing" - - "github.com/go-kit/kit/log" - "github.com/go-logfmt/logfmt" -) - -func TestLogfmtLogger(t *testing.T) { - t.Parallel() - buf := &bytes.Buffer{} - logger := log.NewLogfmtLogger(buf) - - if err := logger.Log("hello", "world"); err != nil { - t.Fatal(err) - } - if want, have := "hello=world\n", buf.String(); want != have { - t.Errorf("want %#v, have %#v", want, have) - } - - buf.Reset() - if err := logger.Log("a", 1, "err", errors.New("error")); err != nil { - t.Fatal(err) - } - if want, have := "a=1 err=error\n", buf.String(); want != have { - t.Errorf("want %#v, have %#v", want, have) - } - - buf.Reset() - if err := logger.Log("std_map", map[int]int{1: 2}, "my_map", mymap{0: 0}); err != nil { - t.Fatal(err) - } - if want, have := "std_map=\""+logfmt.ErrUnsupportedValueType.Error()+"\" my_map=special_behavior\n", buf.String(); want != have { - t.Errorf("want %#v, have %#v", want, have) - } -} - -func BenchmarkLogfmtLoggerSimple(b *testing.B) { - benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), baseMessage) -} - -func BenchmarkLogfmtLoggerContextual(b *testing.B) { - benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), withMessage) -} - -func TestLogfmtLoggerConcurrency(t *testing.T) { - t.Parallel() - testConcurrency(t, log.NewLogfmtLogger(ioutil.Discard), 10000) -} - -type mymap map[int]int - -func (m mymap) String() string { return "special_behavior" } diff --git a/log/logrus/logrus_logger.go b/log/logrus/logrus_logger.go index a5dc9ca..dbee0f7 100644 --- a/log/logrus/logrus_logger.go +++ b/log/logrus/logrus_logger.go @@ -6,7 +6,7 @@ "errors" "fmt" - "github.com/go-kit/kit/log" + "github.com/go-kit/log" "github.com/sirupsen/logrus" ) diff --git a/log/nop_logger.go b/log/nop_logger.go index 1047d62..b02c686 100644 --- a/log/nop_logger.go +++ b/log/nop_logger.go @@ -1,8 +1,8 @@ package log -type nopLogger struct{} +import "github.com/go-kit/log" // NewNopLogger returns a logger that doesn't do anything. -func NewNopLogger() Logger { return nopLogger{} } - -func (nopLogger) Log(...interface{}) error { return nil } +func NewNopLogger() Logger { + return log.NewNopLogger() +} diff --git a/log/nop_logger_test.go b/log/nop_logger_test.go deleted file mode 100644 index 908ddd8..0000000 --- a/log/nop_logger_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package log_test - -import ( - "testing" - - "github.com/go-kit/kit/log" -) - -func TestNopLogger(t *testing.T) { - t.Parallel() - logger := log.NewNopLogger() - if err := logger.Log("abc", 123); err != nil { - t.Error(err) - } - if err := log.With(logger, "def", "ghi").Log(); err != nil { - t.Error(err) - } -} - -func BenchmarkNopLoggerSimple(b *testing.B) { - benchmarkRunner(b, log.NewNopLogger(), baseMessage) -} - -func BenchmarkNopLoggerContextual(b *testing.B) { - benchmarkRunner(b, log.NewNopLogger(), withMessage) -} diff --git a/log/stdlib.go b/log/stdlib.go index 0338edb..cb604a7 100644 --- a/log/stdlib.go +++ b/log/stdlib.go @@ -1,11 +1,9 @@ package log import ( - "bytes" "io" - "log" - "regexp" - "strings" + + "github.com/go-kit/log" ) // StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's @@ -14,42 +12,29 @@ // // If you have any choice in the matter, you shouldn't use this. Prefer to // redirect the stdlib log to the Go kit logger via NewStdlibAdapter. -type StdlibWriter struct{} - -// Write implements io.Writer. -func (w StdlibWriter) Write(p []byte) (int, error) { - log.Print(strings.TrimSpace(string(p))) - return len(p), nil -} +type StdlibWriter = log.StdlibWriter // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib // logger's SetOutput. It will extract date/timestamps, filenames, and // messages, and place them under relevant keys. -type StdlibAdapter struct { - Logger - timestampKey string - fileKey string - messageKey string - prefix string - joinPrefixToMsg bool -} +type StdlibAdapter = log.StdlibAdapter // StdlibAdapterOption sets a parameter for the StdlibAdapter. -type StdlibAdapterOption func(*StdlibAdapter) +type StdlibAdapterOption = log.StdlibAdapterOption // TimestampKey sets the key for the timestamp field. By default, it's "ts". func TimestampKey(key string) StdlibAdapterOption { - return func(a *StdlibAdapter) { a.timestampKey = key } + return log.TimestampKey(key) } // FileKey sets the key for the file and line field. By default, it's "caller". func FileKey(key string) StdlibAdapterOption { - return func(a *StdlibAdapter) { a.fileKey = key } + return log.FileKey(key) } // MessageKey sets the key for the actual log message. By default, it's "msg". func MessageKey(key string) StdlibAdapterOption { - return func(a *StdlibAdapter) { a.messageKey = key } + return log.MessageKey(key) } // Prefix configures the adapter to parse a prefix from stdlib log events. If @@ -59,93 +44,11 @@ // By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to // true if you want to include the parsed prefix in the msg. func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption { - return func(a *StdlibAdapter) { a.prefix = prefix; a.joinPrefixToMsg = joinPrefixToMsg } + return log.Prefix(prefix, joinPrefixToMsg) } // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed // logger. It's designed to be passed to log.SetOutput. func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { - a := StdlibAdapter{ - Logger: logger, - timestampKey: "ts", - fileKey: "caller", - messageKey: "msg", - } - for _, option := range options { - option(&a) - } - return a + return log.NewStdlibAdapter(logger, options...) } - -func (a StdlibAdapter) Write(p []byte) (int, error) { - p = a.handlePrefix(p) - - result := subexps(p) - keyvals := []interface{}{} - var timestamp string - if date, ok := result["date"]; ok && date != "" { - timestamp = date - } - if time, ok := result["time"]; ok && time != "" { - if timestamp != "" { - timestamp += " " - } - timestamp += time - } - if timestamp != "" { - keyvals = append(keyvals, a.timestampKey, timestamp) - } - if file, ok := result["file"]; ok && file != "" { - keyvals = append(keyvals, a.fileKey, file) - } - if msg, ok := result["msg"]; ok { - msg = a.handleMessagePrefix(msg) - keyvals = append(keyvals, a.messageKey, msg) - } - if err := a.Logger.Log(keyvals...); err != nil { - return 0, err - } - return len(p), nil -} - -func (a StdlibAdapter) handlePrefix(p []byte) []byte { - if a.prefix != "" { - p = bytes.TrimPrefix(p, []byte(a.prefix)) - } - return p -} - -func (a StdlibAdapter) handleMessagePrefix(msg string) string { - if a.prefix == "" { - return msg - } - - msg = strings.TrimPrefix(msg, a.prefix) - if a.joinPrefixToMsg { - msg = a.prefix + msg - } - return msg -} - -const ( - logRegexpDate = `(?P[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` - logRegexpTime = `(?P