From 2b49514fd5bbc6081a1d863ccbc3161d488e419b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 31 Mar 2020 13:25:08 -0500 Subject: [PATCH] Refactor for Context only logging --- README.md | 6 +- context.go | 27 ++++ example_helper_test.go | 4 +- example_marshaller_test.go | 4 +- example_test.go | 51 ++++---- export_test.go | 9 +- map.go | 2 +- map_test.go | 6 +- s.go | 17 ++- s_test.go | 9 +- slog.go | 117 ++++++++++++------ slog_test.go | 54 ++++---- sloggers/sloghuman/sloghuman.go | 6 +- sloggers/sloghuman/sloghuman_test.go | 11 +- sloggers/slogjson/slogjson.go | 6 +- sloggers/slogjson/slogjson_test.go | 6 +- sloggers/slogstackdriver/slogstackdriver.go | 8 +- .../slogstackdriver/slogstackdriver_test.go | 6 +- sloggers/slogtest/assert/assert.go | 2 +- sloggers/slogtest/t.go | 20 +-- sloggers/slogtest/t_test.go | 9 +- 21 files changed, 230 insertions(+), 150 deletions(-) create mode 100644 context.go diff --git a/README.md b/README.md index 9fd2562..50550b8 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ go get cdr.dev/slog Many more examples available at [godoc](https://godoc.org/cdr.dev/slog#pkg-examples). ```go -log := sloghuman.Make(os.Stdout) +ctx := sloghuman.Make(ctx, os.Stdout) -log.Info(context.Background(), "my message here", +slog.Info(ctx, "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), @@ -87,6 +87,8 @@ Here is a list of reasons how we improved on zap with slog. 1. Full [context.Context](https://blog.golang.org/context) support - `slog` lets you set fields in a `context.Context` such that any log with the context prints those fields. + - `slog` stores the actual logger in the `context.Context`, following the example of + [the Go trace library](https://golang.org/pkg/runtime/trace/). Our logger doesn't bloat type and function signatures. - We wanted to be able to pull up all relevant logs for a given trace, user or request. With zap, we were plugging these fields in for every relevant log or passing around a logger with the fields set. This became very verbose. diff --git a/context.go b/context.go new file mode 100644 index 0000000..e3f20c1 --- /dev/null +++ b/context.go @@ -0,0 +1,27 @@ +package slog + +import "context" + +type loggerCtxKey = struct{} + +// SinkContext is used by slog.Make to compose many loggers together. +type SinkContext struct { + Sink + context.Context +} + +func contextWithLogger(ctx context.Context, l logger) SinkContext { + ctx = context.WithValue(ctx, loggerCtxKey{}, l) + return SinkContext{ + Context: ctx, + Sink: l, + } +} + +func loggerFromContext(ctx context.Context) (logger, bool) { + v := ctx.Value(loggerCtxKey{}) + if v == nil { + return logger{}, false + } + return v.(logger), true +} diff --git a/example_helper_test.go b/example_helper_test.go index e06d372..853341d 100644 --- a/example_helper_test.go +++ b/example_helper_test.go @@ -12,12 +12,12 @@ import ( func httpLogHelper(ctx context.Context, status int) { slog.Helper() - l.Info(ctx, "sending HTTP response", + slog.Info(ctx, "sending HTTP response", slog.F("status", status), ) } -var l = sloghuman.Make(os.Stdout) +var l = sloghuman.Make(context.Background(), os.Stdout) func ExampleHelper() { ctx := context.Background() diff --git a/example_marshaller_test.go b/example_marshaller_test.go index e4e6992..514ab62 100644 --- a/example_marshaller_test.go +++ b/example_marshaller_test.go @@ -21,9 +21,9 @@ func (s myStruct) MarshalJSON() ([]byte, error) { } func Example_marshaller() { - l := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) - l.Info(context.Background(), "wow", + slog.Info(ctx, "wow", slog.F("myStruct", myStruct{ foo: 1, bar: 2, diff --git a/example_test.go b/example_test.go index f3577ac..3627532 100644 --- a/example_test.go +++ b/example_test.go @@ -18,14 +18,14 @@ import ( ) func Example() { - log := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) - log.Info(context.Background(), "my message here", + slog.Info(ctx, "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), )), - slog.Error( + slog.Err( xerrors.Errorf("wrap1: %w", xerrors.Errorf("wrap2: %w", io.EOF, @@ -45,7 +45,7 @@ func Example() { } func Example_struct() { - l := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) type hello struct { Meow int `json:"meow"` @@ -53,7 +53,7 @@ func Example_struct() { M time.Time `json:"m"` } - l.Info(context.Background(), "check out my structure", + slog.Info(ctx, "check out my structure", slog.F("hello", hello{ Meow: 1, Bar: "barbar", @@ -76,26 +76,27 @@ func Example_testing() { } func Example_tracing() { - log := sloghuman.Make(os.Stdout) + var ctx context.Context + ctx = sloghuman.Make(context.Background(), os.Stdout) - ctx, _ := trace.StartSpan(context.Background(), "spanName") + ctx, _ = trace.StartSpan(ctx, "spanName") - log.Info(ctx, "my msg", slog.F("hello", "hi")) + slog.Info(ctx, "my msg", slog.F("hello", "hi")) // 2019-12-09 21:59:48.110 [INFO] my msg {"trace": "f143d018d00de835688453d8dc55c9fd", "span": "f214167bf550afc3", "hello": "hi"} } func Example_multiple() { - l := sloghuman.Make(os.Stdout) + ctx := sloghuman.Make(context.Background(), os.Stdout) f, err := os.OpenFile("stackdriver", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { - l.Fatal(context.Background(), "failed to open stackdriver log file", slog.Error(err)) + slog.Fatal(ctx, "failed to open stackdriver log file", slog.Err(err)) } - l = slog.Make(l, slogstackdriver.Make(f)) + ctx = slog.Make(l, slogstackdriver.Make(ctx, f)) - l.Info(context.Background(), "log to stdout and stackdriver") + slog.Info(ctx, "log to stdout and stackdriver") // 2019-12-07 20:59:55.790 [INFO] log to stdout and stackdriver } @@ -103,41 +104,41 @@ func Example_multiple() { func ExampleWith() { ctx := slog.With(context.Background(), slog.F("field", 1)) - l := sloghuman.Make(os.Stdout) - l.Info(ctx, "msg") + ctx = sloghuman.Make(ctx, os.Stdout) + slog.Info(ctx, "msg") // 2019-12-07 20:54:23.986 [INFO] msg {"field": 1} } func ExampleStdlib() { ctx := slog.With(context.Background(), slog.F("field", 1)) - l := slog.Stdlib(ctx, sloghuman.Make(os.Stdout)) + l := slog.Stdlib(sloghuman.Make(ctx, os.Stdout)) l.Print("msg") // 2019-12-07 20:54:23.986 [INFO] (stdlib) msg {"field": 1} } -func ExampleLogger_Named() { +func ExampleNamed() { ctx := context.Background() - l := sloghuman.Make(os.Stdout) - l = l.Named("http") - l.Info(ctx, "received request", slog.F("remote address", net.IPv4(127, 0, 0, 1))) + ctx = sloghuman.Make(ctx, os.Stdout) + ctx = slog.Named(ctx, "http") + slog.Info(ctx, "received request", slog.F("remote address", net.IPv4(127, 0, 0, 1))) // 2019-12-07 21:20:56.974 [INFO] (http) received request {"remote address": "127.0.0.1"} } -func ExampleLogger_Leveled() { +func ExampleLeveled() { ctx := context.Background() - l := sloghuman.Make(os.Stdout) - l.Debug(ctx, "testing1") - l.Info(ctx, "received request") + ctx = sloghuman.Make(ctx, os.Stdout) + slog.Debug(ctx, "testing1") + slog.Info(ctx, "received request") - l = l.Leveled(slog.LevelDebug) + ctx = slog.Leveled(ctx, slog.LevelDebug) - l.Debug(ctx, "testing2") + slog.Debug(ctx, "testing2") // 2019-12-07 21:26:20.945 [INFO] received request // 2019-12-07 21:26:20.945 [DEBUG] testing2 diff --git a/export_test.go b/export_test.go index 1266811..a682e20 100644 --- a/export_test.go +++ b/export_test.go @@ -1,5 +1,12 @@ package slog -func (l *Logger) SetExit(fn func(int)) { +import "context" + +func SetExit(ctx context.Context, fn func(int)) context.Context { + l, ok := loggerFromContext(ctx) + if !ok { + return ctx + } l.exit = fn + return contextWithLogger(ctx, l) } diff --git a/map.go b/map.go index 636f4ee..a1b594b 100644 --- a/map.go +++ b/map.go @@ -128,7 +128,7 @@ func encodeJSON(v interface{}) []byte { b, err := json.Marshal(v) if err != nil { return encode(M( - Error(xerrors.Errorf("failed to marshal to JSON: %w", err)), + Err(xerrors.Errorf("failed to marshal to JSON: %w", err)), F("type", reflect.TypeOf(v)), F("value", fmt.Sprintf("%+v", v)), )) diff --git a/map_test.go b/map_test.go index e15a6ee..03d7886 100644 --- a/map_test.go +++ b/map_test.go @@ -37,7 +37,7 @@ func TestMap(t *testing.T) { } test(t, slog.M( - slog.Error( + slog.Err( xerrors.Errorf("wrap1: %w", xerrors.Errorf("wrap2: %w", io.EOF, @@ -222,10 +222,6 @@ func TestMap(t *testing.T) { }) } -type meow struct { - a int -} - func indentJSON(t *testing.T, j string) string { b := &bytes.Buffer{} err := json.Indent(b, []byte(j), "", strings.Repeat(" ", 4)) diff --git a/s.go b/s.go index fa8917e..e85195c 100644 --- a/s.go +++ b/s.go @@ -3,6 +3,7 @@ package slog import ( "context" "log" + "os" "strings" ) @@ -15,14 +16,19 @@ import ( // You can redirect the stdlib default logger with log.SetOutput // to the Writer on the logger returned by this function. // See the example. -func Stdlib(ctx context.Context, l Logger) *log.Logger { - l.skip += 3 +func Stdlib(ctx context.Context) *log.Logger { + ctx = Named(ctx, "stdlib") - l = l.Named("stdlib") + l, ok := loggerFromContext(ctx) + if !ok { + // Give stderr logger if no slog. + return log.New(os.Stderr, "", 0) + } + l.skip += 3 + ctx = contextWithLogger(ctx, l) w := &stdlogWriter{ ctx: ctx, - l: l, } return log.New(w, "", 0) @@ -30,7 +36,6 @@ func Stdlib(ctx context.Context, l Logger) *log.Logger { type stdlogWriter struct { ctx context.Context - l Logger } func (w stdlogWriter) Write(p []byte) (n int, err error) { @@ -39,7 +44,7 @@ func (w stdlogWriter) Write(p []byte) (n int, err error) { // we do not want. msg = strings.TrimSuffix(msg, "\n") - w.l.Info(w.ctx, msg) + Info(w.ctx, msg) return len(p), nil } diff --git a/s_test.go b/s_test.go index a589460..5006c04 100644 --- a/s_test.go +++ b/s_test.go @@ -2,6 +2,7 @@ package slog_test import ( "bytes" + "context" "testing" "cdr.dev/slog" @@ -14,14 +15,16 @@ func TestStdlib(t *testing.T) { t.Parallel() b := &bytes.Buffer{} - l := slog.Make(sloghuman.Make(b)).With( + ctx := context.Background() + ctx = slog.Make(sloghuman.Make(ctx, b)) + ctx = slog.With(ctx, slog.F("hi", "we"), ) - stdlibLog := slog.Stdlib(bg, l) + stdlibLog := slog.Stdlib(ctx) stdlibLog.Println("stdlib") et, rest, err := entryhuman.StripTimestamp(b.String()) assert.Success(t, "strip timestamp", err) assert.False(t, "timestamp", et.IsZero()) - assert.Equal(t, "entry", " [INFO]\t(stdlib)\t\tstdlib\t{\"hi\": \"we\"}\n", rest) + assert.Equal(t, "entry", " [INFO]\t(stdlib)\t\tstdlib\t{\"hi\": \"we\"}\n", rest) } diff --git a/slog.go b/slog.go index a291f27..bfbecde 100644 --- a/slog.go +++ b/slog.go @@ -4,8 +4,8 @@ // // The examples are the best way to understand how to use this library effectively. // -// The Logger type implements a high level API around the Sink interface. -// Logger implements Sink as well to allow composition. +// The logger type implements a high level API around the Sink interface. +// logger implements Sink as well to allow composition. // // Implementations of the Sink interface are available in the sloggers subdirectory. package slog // import "cdr.dev/slog" @@ -21,7 +21,7 @@ import ( "go.opencensus.io/trace" ) -// Sink is the destination of a Logger. +// Sink is the destination of a logger. // // All sinks must be safe for concurrent use. type Sink interface { @@ -33,7 +33,7 @@ type Sink interface { // underlying sinks. // // It extends the entry with the set fields and names. -func (l Logger) LogEntry(ctx context.Context, e SinkEntry) { +func (l logger) LogEntry(ctx context.Context, e SinkEntry) { if e.Level < l.level { return } @@ -46,17 +46,27 @@ func (l Logger) LogEntry(ctx context.Context, e SinkEntry) { } } -// Sync calls Sync on all the underlying sinks. -func (l Logger) Sync() { +func (l logger) Sync() { for _, s := range l.sinks { s.Sync() } } -// Logger wraps Sink with a nice API to log entries. +// Sync calls Sync on all the underlying sinks. +func Sync(ctx context.Context) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } + l.Sync() + return +} + +// logger wraps Sink with a nice API to log entries. // -// Logger is safe for concurrent use. -type Logger struct { +// logger is safe for concurrent use. +// It is unexported because callers should only log via a context. +type logger struct { sinks []Sink level Level @@ -68,34 +78,53 @@ type Logger struct { } // Make creates a logger that writes logs to the passed sinks at LevelInfo. -func Make(sinks ...Sink) Logger { - return Logger{ - sinks: sinks, - level: LevelInfo, - - exit: os.Exit, +func Make(ctx context.Context, sinks ...Sink) SinkContext { + // Just in case the ctx has a logger, start with it. + l, _ := loggerFromContext(ctx) + l.sinks = append(l.sinks, sinks...) + if l.level == 0 { + l.level = LevelInfo } + l.exit = os.Exit + + return contextWithLogger(ctx, l) } // Debug logs the msg and fields at LevelDebug. -func (l Logger) Debug(ctx context.Context, msg string, fields ...Field) { +func Debug(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelDebug, msg, fields) } // Info logs the msg and fields at LevelInfo. -func (l Logger) Info(ctx context.Context, msg string, fields ...Field) { +func Info(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelInfo, msg, fields) } // Warn logs the msg and fields at LevelWarn. -func (l Logger) Warn(ctx context.Context, msg string, fields ...Field) { +func Warn(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelWarn, msg, fields) } // Error logs the msg and fields at LevelError. // // It will then Sync(). -func (l Logger) Error(ctx context.Context, msg string, fields ...Field) { +func Error(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelError, msg, fields) l.Sync() } @@ -103,7 +132,11 @@ func (l Logger) Error(ctx context.Context, msg string, fields ...Field) { // Critical logs the msg and fields at LevelCritical. // // It will then Sync(). -func (l Logger) Critical(ctx context.Context, msg string, fields ...Field) { +func Critical(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + return + } l.log(ctx, LevelCritical, msg, fields) l.Sync() } @@ -111,41 +144,47 @@ func (l Logger) Critical(ctx context.Context, msg string, fields ...Field) { // Fatal logs the msg and fields at LevelFatal. // // It will then Sync() and os.Exit(1). -func (l Logger) Fatal(ctx context.Context, msg string, fields ...Field) { +func Fatal(ctx context.Context, msg string, fields ...Field) { + l, ok := loggerFromContext(ctx) + if !ok { + os.Stderr.WriteString("Fatal called but no Logger in context") + // The caller expects the program to terminate after Fatal no matter what. + l.exit(1) + return + } l.log(ctx, LevelFatal, msg, fields) l.Sync() l.exit(1) } -// With returns a Logger that prepends the given fields on every -// logged entry. -// -// It will append to any fields already in the Logger. -func (l Logger) With(fields ...Field) Logger { - l.fields = l.fields.append(fields) - return l -} - // Named appends the name to the set names // on the logger. -func (l Logger) Named(name string) Logger { +func Named(ctx context.Context, name string) context.Context { + l, ok := loggerFromContext(ctx) + if !ok { + return ctx + } l.names = appendNames(l.names, name) - return l + return contextWithLogger(ctx, l) } -// Leveled returns a Logger that only logs entries +// Leveled returns a logger that only logs entries // equal to or above the given level. -func (l Logger) Leveled(level Level) Logger { +func Leveled(ctx context.Context, level Level) context.Context { + l, ok := loggerFromContext(ctx) + if !ok { + return ctx + } l.level = level - return l + return contextWithLogger(ctx, l) } -func (l Logger) log(ctx context.Context, level Level, msg string, fields Map) { +func (l logger) log(ctx context.Context, level Level, msg string, fields Map) { ent := l.entry(ctx, level, msg, fields) l.LogEntry(ctx, ent) } -func (l Logger) entry(ctx context.Context, level Level, msg string, fields Map) SinkEntry { +func (l logger) entry(ctx context.Context, level Level, msg string, fields Map) SinkEntry { ent := SinkEntry{ Time: time.Now().UTC(), Level: level, @@ -226,8 +265,8 @@ func M(fs ...Field) Map { return fs } -// Error is the standard key used for logging a Go error value. -func Error(err error) Field { +// Err is the standard key used for logging a Go error value. +func Err(err error) Field { return F("error", err) } diff --git a/slog_test.go b/slog_test.go index a55def2..3c88989 100644 --- a/slog_test.go +++ b/slog_test.go @@ -28,8 +28,6 @@ func (s *fakeSink) Sync() { s.syncs++ } -var bg = context.Background() - func TestLogger(t *testing.T) { t.Parallel() @@ -38,11 +36,12 @@ func TestLogger(t *testing.T) { s1 := &fakeSink{} s2 := &fakeSink{} - l := slog.Make(s1, s2) - l = l.Leveled(slog.LevelError) + var ctx context.Context + ctx = slog.Make(context.Background(), s1, s2) + ctx = slog.Leveled(ctx, slog.LevelError) - l.Info(bg, "wow", slog.Error(io.EOF)) - l.Error(bg, "meow", slog.Error(io.ErrUnexpectedEOF)) + slog.Info(ctx, "wow", slog.Err(io.EOF)) + slog.Error(ctx, "meow", slog.Err(io.ErrUnexpectedEOF)) assert.Equal(t, "syncs", 1, s1.syncs) assert.Len(t, "entries", 1, s1.entries) @@ -54,13 +53,14 @@ func TestLogger(t *testing.T) { t.Parallel() s := &fakeSink{} - l := slog.Make(s) + var ctx context.Context + ctx = slog.Make(context.Background(), s) h := func(ctx context.Context) { slog.Helper() - l.Info(ctx, "logging in helper") + slog.Info(ctx, "logging in helper") } - ctx := slog.With(bg, slog.F( + ctx = slog.With(ctx, slog.F( "ctx", 1024), ) h(ctx) @@ -86,15 +86,16 @@ func TestLogger(t *testing.T) { t.Parallel() s := &fakeSink{} - l := slog.Make(s) - l = l.Named("hello") - l = l.Named("hello2") + var ctx context.Context + ctx = slog.Make(context.Background(), s) + ctx = slog.Named(ctx, "hello") + ctx = slog.Named(ctx, "hello2") - ctx, span := trace.StartSpan(bg, "trace") + ctx, span := trace.StartSpan(ctx, "trace") ctx = slog.With(ctx, slog.F("ctx", io.EOF)) - l = l.With(slog.F("with", 2)) + ctx = slog.With(ctx, slog.F("with", 2)) - l.Info(ctx, "meow", slog.F("hi", "xd")) + slog.Info(ctx, "meow", slog.F("hi", "xd")) assert.Len(t, "entries", 1, s.entries) assert.Equal(t, "entry", slog.SinkEntry{ @@ -107,13 +108,13 @@ func TestLogger(t *testing.T) { File: slogTestFile, Func: "cdr.dev/slog_test.TestLogger.func3", - Line: 97, + Line: 98, SpanContext: span.SpanContext(), Fields: slog.M( - slog.F("with", 2), slog.F("ctx", io.EOF), + slog.F("with", 2), slog.F("hi", "xd"), ), }, s.entries[0]) @@ -123,20 +124,21 @@ func TestLogger(t *testing.T) { t.Parallel() s := &fakeSink{} - l := slog.Make(s) + var ctx context.Context + ctx = slog.Make(context.Background(), s) exits := 0 - l.SetExit(func(int) { + ctx = slog.SetExit(ctx, func(int) { exits++ }) - l = l.Leveled(slog.LevelDebug) - l.Debug(bg, "") - l.Info(bg, "") - l.Warn(bg, "") - l.Error(bg, "") - l.Critical(bg, "") - l.Fatal(bg, "") + ctx = slog.Leveled(ctx, slog.LevelDebug) + slog.Debug(ctx, "") + slog.Info(ctx, "") + slog.Warn(ctx, "") + slog.Error(ctx, "") + slog.Critical(ctx, "") + slog.Fatal(ctx, "") assert.Len(t, "entries", 6, s.entries) assert.Equal(t, "syncs", 3, s.syncs) diff --git a/sloggers/sloghuman/sloghuman.go b/sloggers/sloghuman/sloghuman.go index 719db7c..18cbb58 100644 --- a/sloggers/sloghuman/sloghuman.go +++ b/sloggers/sloghuman/sloghuman.go @@ -17,8 +17,8 @@ import ( // // If the writer implements Sync() error then // it will be called when syncing. -func Make(w io.Writer) slog.Logger { - return slog.Make(&humanSink{ +func Make(ctx context.Context, w io.Writer) slog.SinkContext { + return slog.Make(ctx, &humanSink{ w: syncwriter.New(w), w2: w, }) @@ -29,7 +29,7 @@ type humanSink struct { w2 io.Writer } -func (s humanSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { +func (s humanSink) LogEntry(_ context.Context, ent slog.SinkEntry) { str := entryhuman.Fmt(s.w2, ent) lines := strings.Split(str, "\n") diff --git a/sloggers/sloghuman/sloghuman_test.go b/sloggers/sloghuman/sloghuman_test.go index 5c28afb..86fe888 100644 --- a/sloggers/sloghuman/sloghuman_test.go +++ b/sloggers/sloghuman/sloghuman_test.go @@ -11,18 +11,17 @@ import ( "cdr.dev/slog/sloggers/sloghuman" ) -var bg = context.Background() - func TestMake(t *testing.T) { t.Parallel() b := &bytes.Buffer{} - l := sloghuman.Make(b) - l.Info(bg, "line1\n\nline2", slog.F("wowow", "me\nyou")) - l.Sync() + ctx := context.Background() + ctx = sloghuman.Make(ctx, b) + slog.Info(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) + slog.Sync(ctx) et, rest, err := entryhuman.StripTimestamp(b.String()) assert.Success(t, "strip timestamp", err) assert.False(t, "timestamp", et.IsZero()) - assert.Equal(t, "entry", " [INFO]\t\t...\t{\"wowow\": \"me\\nyou\"}\n \"msg\": line1\n\n line2\n", rest) + assert.Equal(t, "entry", " [INFO]\t\t...\t{\"wowow\": \"me\\nyou\"}\n \"msg\": line1\n\n line2\n", rest) } diff --git a/sloggers/slogjson/slogjson.go b/sloggers/slogjson/slogjson.go index 5069ddc..06c0446 100644 --- a/sloggers/slogjson/slogjson.go +++ b/sloggers/slogjson/slogjson.go @@ -34,8 +34,8 @@ import ( // for the format. // If the writer implements Sync() error then // it will be called when syncing. -func Make(w io.Writer) slog.Logger { - return slog.Make(jsonSink{ +func Make(ctx context.Context, w io.Writer) context.Context { + return slog.Make(ctx, jsonSink{ w: syncwriter.New(w), }) } @@ -44,7 +44,7 @@ type jsonSink struct { w *syncwriter.Writer } -func (s jsonSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { +func (s jsonSink) LogEntry(_ context.Context, ent slog.SinkEntry) { m := slog.M( slog.F("ts", ent.Time), slog.F("level", ent.Level), diff --git a/sloggers/slogjson/slogjson_test.go b/sloggers/slogjson/slogjson_test.go index 77ee4e4..103be7a 100644 --- a/sloggers/slogjson/slogjson_test.go +++ b/sloggers/slogjson/slogjson_test.go @@ -24,9 +24,9 @@ func TestMake(t *testing.T) { ctx, s := trace.StartSpan(bg, "meow") b := &bytes.Buffer{} - l := slogjson.Make(b) - l = l.Named("named") - l.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) + ctx = slogjson.Make(ctx, b) + ctx = slog.Named(ctx, "named") + slog.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) j := entryjson.Filter(b.String(), "ts") exp := fmt.Sprintf(`{"level":"ERROR","msg":"line1\n\nline2","caller":"%v:29","func":"cdr.dev/slog/sloggers/slogjson_test.TestMake","logger_names":["named"],"trace":"%v","span":"%v","fields":{"wowow":"me\nyou"}} diff --git a/sloggers/slogstackdriver/slogstackdriver.go b/sloggers/slogstackdriver/slogstackdriver.go index 3b4045e..bac1294 100644 --- a/sloggers/slogstackdriver/slogstackdriver.go +++ b/sloggers/slogstackdriver/slogstackdriver.go @@ -17,14 +17,14 @@ import ( "cdr.dev/slog/internal/syncwriter" ) -// Make creates a slog.Logger configured to write JSON logs +// Make creates a slog.logger configured to write JSON logs // to stdout for stackdriver. // // See https://cloud.google.com/logging/docs/agent -func Make(w io.Writer) slog.Logger { +func Make(ctx context.Context, w io.Writer) slog.SinkContext { projectID, _ := metadata.ProjectID() - return slog.Make(stackdriverSink{ + return slog.Make(ctx, stackdriverSink{ projectID: projectID, w: syncwriter.New(w), }) @@ -35,7 +35,7 @@ type stackdriverSink struct { w *syncwriter.Writer } -func (s stackdriverSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { +func (s stackdriverSink) LogEntry(_ context.Context, ent slog.SinkEntry) { // https://cloud.google.com/logging/docs/agent/configuration#special-fields e := slog.M( slog.F("severity", sev(ent.Level)), diff --git a/sloggers/slogstackdriver/slogstackdriver_test.go b/sloggers/slogstackdriver/slogstackdriver_test.go index 3a4a295..025ad9d 100644 --- a/sloggers/slogstackdriver/slogstackdriver_test.go +++ b/sloggers/slogstackdriver/slogstackdriver_test.go @@ -24,9 +24,9 @@ func TestStackdriver(t *testing.T) { ctx, s := trace.StartSpan(bg, "meow") b := &bytes.Buffer{} - l := slogstackdriver.Make(b) - l = l.Named("meow") - l.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) + ctx = slogstackdriver.Make(ctx, b) + ctx = slog.Named(ctx, "meow") + slog.Error(ctx, "line1\n\nline2", slog.F("wowow", "me\nyou")) j := entryjson.Filter(b.String(), "timestamp") exp := fmt.Sprintf(`{"severity":"ERROR","message":"line1\n\nline2","logging.googleapis.com/sourceLocation":{"file":"%v","line":29,"function":"cdr.dev/slog/sloggers/slogstackdriver_test.TestStackdriver"},"logging.googleapis.com/operation":{"producer":"meow"},"logging.googleapis.com/trace":"projects//traces/%v","logging.googleapis.com/spanId":"%v","logging.googleapis.com/trace_sampled":false,"wowow":"me\nyou"} diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index e246a2f..7aafdd4 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -39,7 +39,7 @@ func Success(t testing.TB, name string, err error) { if err != nil { slogtest.Fatal(t, "unexpected error", slog.F("name", name), - slog.Error(err), + slog.Err(err), ) } } diff --git a/sloggers/slogtest/t.go b/sloggers/slogtest/t.go index e315184..ffb972a 100644 --- a/sloggers/slogtest/t.go +++ b/sloggers/slogtest/t.go @@ -18,8 +18,8 @@ import ( // Ensure all stdlib logs go through slog. func init() { - l := sloghuman.Make(os.Stderr) - log.SetOutput(slog.Stdlib(context.Background(), l).Writer()) + ctx := sloghuman.Make(ctx, os.Stderr) + log.SetOutput(slog.Stdlib(ctx).Writer()) } // Options represents the options for the logger returned @@ -30,12 +30,12 @@ type Options struct { IgnoreErrors bool } -// Make creates a Logger that writes logs to tb in a human readable format. -func Make(tb testing.TB, opts *Options) slog.Logger { +// Make creates a logger that writes logs to tb in a human readable format. +func Make(tb testing.TB, opts *Options) slog.SinkContext { if opts == nil { opts = &Options{} } - return slog.Make(testSink{ + return slog.Make(context.Background(), testSink{ tb: tb, opts: opts, }) @@ -72,30 +72,30 @@ func (ts testSink) Sync() {} var ctx = context.Background() -func l(t testing.TB) slog.Logger { +func l(t testing.TB) context.Context { return Make(t, nil) } // Debug logs the given msg and fields to t via t.Log at the debug level. func Debug(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Debug(ctx, msg, fields...) + slog.Debug(l(t), msg, fields...) } // Info logs the given msg and fields to t via t.Log at the info level. func Info(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Info(ctx, msg, fields...) + slog.Info(l(t), msg, fields...) } // Error logs the given msg and fields to t via t.Error at the error level. func Error(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Error(ctx, msg, fields...) + slog.Error(l(t), msg, fields...) } // Fatal logs the given msg and fields to t via t.Fatal at the fatal level. func Fatal(t testing.TB, msg string, fields ...slog.Field) { slog.Helper() - l(t).Fatal(ctx, msg, fields...) + slog.Fatal(l(t), msg, fields...) } diff --git a/sloggers/slogtest/t_test.go b/sloggers/slogtest/t_test.go index 9169cbe..77942a5 100644 --- a/sloggers/slogtest/t_test.go +++ b/sloggers/slogtest/t_test.go @@ -31,11 +31,12 @@ func TestIgnoreErrors(t *testing.T) { t.Parallel() tb := &fakeTB{} - l := slog.Make(slogtest.Make(tb, &slogtest.Options{ + ctx := context.Background() + ctx = slog.Make(ctx, slogtest.Make(tb, &slogtest.Options{ IgnoreErrors: true, })) - l.Error(bg, "hello") + slog.Error(ctx, "hello") assert.Equal(t, "errors", 0, tb.errors) defer func() { @@ -43,11 +44,9 @@ func TestIgnoreErrors(t *testing.T) { assert.Equal(t, "fatals", 0, tb.fatals) }() - l.Fatal(bg, "hello") + slog.Fatal(ctx, "hello") } -var bg = context.Background() - type fakeTB struct { testing.TB