From 4a062bbb5506dce778409ff53aa3909587b7d5ec Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 10 Dec 2019 14:03:40 -0600 Subject: [PATCH 01/17] Integrate David's suggestions --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 95c28ff..63ef88a 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,10 @@ log.Info(context.Background(), "my message here", ## Why? -The logging library of choice at [Coder](https://github.com/cdr) has been Uber's [zap](https://github.com/uber-go/zap) -for several years now. +At [Coder](https://github.com/cdr) we’ve used Uber’s [zap](https://github.com/uber-go/zap) for several years. +It is a fantastic library for performance. Thanks Uber! -It's a fantastic library for performance but the API and developer experience is not great. +However, we felt the API and developer experience could be improved. Here is a list of reasons how we improved on zap with slog. From 982b12024d9102c1c729af0f81c82d8a984646b0 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 10 Dec 2019 14:04:02 -0600 Subject: [PATCH 02/17] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63ef88a..85d77d2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ log.Info(context.Background(), "my message here", At [Coder](https://github.com/cdr) we’ve used Uber’s [zap](https://github.com/uber-go/zap) for several years. It is a fantastic library for performance. Thanks Uber! -However, we felt the API and developer experience could be improved. +However we felt the API and developer experience could be improved. Here is a list of reasons how we improved on zap with slog. From 1acb21f423ef48330c3921ba934571218eb8c92f Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 10 Dec 2019 15:01:14 -0600 Subject: [PATCH 03/17] Fix function comments in assert package --- sloggers/slogtest/assert/assert.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index d46b78b..7d95e59 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -1,5 +1,5 @@ // Package assert is a helper package for test assertions. -package assert +package assert // import "cdr.dev/slog/sloggers/slogtest/assert" import ( "testing" @@ -11,8 +11,8 @@ import ( // Equal asserts exp == act. // -// If they are not equal, it will fatal the test -// with a diff of the differences. +// If they are not equal, it will fatal the test with a diff of the +// two objects. func Equal(t testing.TB, exp, act interface{}, name string) { slog.Helper() if diff := assert.CmpDiff(exp, act); diff != "" { @@ -24,6 +24,8 @@ func Equal(t testing.TB, exp, act interface{}, name string) { } // Success asserts err == nil. +// +// If err isn't nil, it will fatal the test with the error. func Success(t testing.TB, err error, name string) { slog.Helper() if err != nil { @@ -34,7 +36,9 @@ func Success(t testing.TB, err error, name string) { } } -// True act == true. +// True asserts act == true. +// +// If act isn't true, it will fatal the test. func True(t testing.TB, act bool, name string) { slog.Helper() Equal(t, true, act, name) From 5e6d3c41feb65a147958c96d5c0bd48d146e5780 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 11 Dec 2019 18:47:22 -0600 Subject: [PATCH 04/17] Document the logging levels Closes #71 --- slog.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/slog.go b/slog.go index 6ca8160..d2cf1b3 100644 --- a/slog.go +++ b/slog.go @@ -287,11 +287,23 @@ type Level int // // The default level is Info. const ( + // LevelDebug is used for development and debugging messages. LevelDebug Level = iota + + // LevelInfo is used for normal informational messages. LevelInfo + + // LevelWarn is used when something has possibly gone wrong. LevelWarn + + // LevelError is used when something has certainly gone wrong. LevelError + + // LevelCritical is used when when something has gone wrong and should + // be immediately investigated. LevelCritical + + // LevelFatal is used when the process is about to exit due to an error. LevelFatal ) From 8d8b8377b08febd39373771bfb112598a75f150f Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 11 Dec 2019 19:07:47 -0600 Subject: [PATCH 05/17] Remove direct coupling of opencensus Closes #69 --- README.md | 4 ++-- internal/entryhuman/entry.go | 8 ++++---- internal/entryhuman/entry_test.go | 21 ++++++++++----------- slog.go | 13 ++++--------- slog_test.go | 11 +++-------- sloggers/sloghuman/sloghuman.go | 4 +++- sloggers/slogjson/slogjson.go | 7 ++++--- sloggers/slogstackdriver/slogstackdriver.go | 9 +++++---- sloggers/slogtest/t.go | 4 +++- 9 files changed, 38 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 85d77d2..e16d14e 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ go get cdr.dev/slog - [Stdlib](https://godoc.org/cdr.dev/slog#Stdlib) log adapter - Skip caller frames with [slog.Helper](https://godoc.org/cdr.dev/slog#Helper) - Encodes values as if with `json.Marshal` -- Transparently log [opencensus](https://godoc.org/go.opencensus.io/trace) trace and span IDs -- [Single dependency](https://godoc.org/cdr.dev/slog?imports) on go.opencensus.io +- Transparently log [opencensus](https://godoc.org/go.opencensus.io/trace) span's from the passed context +- [Zero dependencies](https://godoc.org/cdr.dev/slog?imports) - Log to multiple sinks ## Example diff --git a/internal/entryhuman/entry.go b/internal/entryhuman/entry.go index fcda4e9..dd99256 100644 --- a/internal/entryhuman/entry.go +++ b/internal/entryhuman/entry.go @@ -49,7 +49,7 @@ func c(w io.Writer, attrs ...color.Attribute) *color.Color { // We also do not indent the fields as go's test does that automatically // for extra lines in a log so if we did it here, the fields would be indented // twice in test logs. So the Stderr logger indents all the fields itself. -func Fmt(w io.Writer, ent slog.SinkEntry) string { +func Fmt(w io.Writer, ent slog.SinkEntry, sc trace.SpanContext) string { var ents string ts := ent.Time.Format(TimeFormat) ents += ts + " " @@ -79,10 +79,10 @@ func Fmt(w io.Writer, ent slog.SinkEntry) string { msg = quote(msg) ents += msg - if ent.SpanContext != (trace.SpanContext{}) { + if sc != (trace.SpanContext{}) { ent.Fields = append(slog.M( - slog.F("trace", ent.SpanContext.TraceID), - slog.F("span", ent.SpanContext.SpanID), + slog.F("trace", sc.TraceID), + slog.F("span", sc.SpanID), ), ent.Fields...) } diff --git a/internal/entryhuman/entry_test.go b/internal/entryhuman/entry_test.go index 0246ddb..3dbd4c1 100644 --- a/internal/entryhuman/entry_test.go +++ b/internal/entryhuman/entry_test.go @@ -14,11 +14,11 @@ import ( var kt = time.Date(2000, time.February, 5, 4, 4, 4, 4, time.UTC) -func TestEntry(t *testing.T) { +func TestFmt(t *testing.T) { t.Parallel() - test := func(t *testing.T, in slog.SinkEntry, exp string) { - act := entryhuman.Fmt(ioutil.Discard, in) + test := func(t *testing.T, in slog.SinkEntry, sc trace.SpanContext, exp string) { + act := entryhuman.Fmt(ioutil.Discard, in, sc) assert.Equal(t, exp, act, "entry") } @@ -33,7 +33,7 @@ func TestEntry(t *testing.T) { File: "myfile", Line: 100, Func: "ignored", - }, `2000-02-05 04:04:04.000 [DEBUG] "wowowow\tizi"`) + }, trace.SpanContext{}, `2000-02-05 04:04:04.000 [DEBUG] "wowowow\tizi"`) }) t.Run("multilineMessage", func(t *testing.T) { @@ -42,7 +42,7 @@ func TestEntry(t *testing.T) { test(t, slog.SinkEntry{ Message: "line1\nline2", Level: slog.LevelInfo, - }, `0001-01-01 00:00:00.000 [INFO] <.:0> ... + }, trace.SpanContext{}, `0001-01-01 00:00:00.000 [INFO] <.:0> ... "msg": line1 line2`) }) @@ -53,7 +53,7 @@ line2`) test(t, slog.SinkEntry{ Level: slog.LevelWarn, LoggerNames: []string{"named", "meow"}, - }, `0001-01-01 00:00:00.000 [WARN] (named.meow) <.:0> ""`) + }, trace.SpanContext{}, `0001-01-01 00:00:00.000 [WARN] (named.meow) <.:0> ""`) }) t.Run("trace", func(t *testing.T) { @@ -61,10 +61,9 @@ line2`) test(t, slog.SinkEntry{ Level: slog.LevelError, - SpanContext: trace.SpanContext{ - SpanID: trace.SpanID{0, 1, 2, 3, 4, 5, 6, 7}, - TraceID: trace.TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - }, + }, trace.SpanContext{ + SpanID: trace.SpanID{0, 1, 2, 3, 4, 5, 6, 7}, + TraceID: trace.TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, }, `0001-01-01 00:00:00.000 [ERROR] <.:0> "" {"trace": "000102030405060708090a0b0c0d0e0f", "span": "0001020304050607"}`) }) @@ -76,7 +75,7 @@ line2`) Fields: slog.M( slog.F("hey", "hi"), ), - }) + }, trace.SpanContext{}) assert.Equal(t, "0001-01-01 00:00:00.000 \x1b[91m[CRITICAL]\x1b[0m\t\x1b[36m<.:0>\x1b[0m\t\"\"\t{\x1b[34m\"hey\"\x1b[0m: \x1b[32m\"hi\"\x1b[0m}", act, "entry") }) } diff --git a/slog.go b/slog.go index d2cf1b3..de2177b 100644 --- a/slog.go +++ b/slog.go @@ -17,8 +17,6 @@ import ( "runtime" "sync" "time" - - "go.opencensus.io/trace" ) // Sink is the destination of a Logger. @@ -147,11 +145,10 @@ func (l Logger) log(ctx context.Context, level Level, msg string, fields Map) { func (l Logger) entry(ctx context.Context, level Level, msg string, fields Map) SinkEntry { ent := SinkEntry{ - Time: time.Now().UTC(), - Level: level, - Message: msg, - Fields: fieldsFromContext(ctx).append(fields), - SpanContext: trace.FromContext(ctx).SpanContext(), + Time: time.Now().UTC(), + Level: level, + Message: msg, + Fields: fieldsFromContext(ctx).append(fields), } ent = ent.fillLoc(l.skip + 3) return ent @@ -275,8 +272,6 @@ type SinkEntry struct { File string Line int - SpanContext trace.SpanContext - Fields Map } diff --git a/slog_test.go b/slog_test.go index 741bddb..7b6ab9f 100644 --- a/slog_test.go +++ b/slog_test.go @@ -6,8 +6,6 @@ import ( "runtime" "testing" - "go.opencensus.io/trace" - "cdr.dev/slog" "cdr.dev/slog/internal/assert" ) @@ -74,7 +72,7 @@ func TestLogger(t *testing.T) { File: slogTestFile, Func: "cdr.dev/slog_test.TestLogger.func2", - Line: 66, + Line: 64, Fields: slog.M( slog.F("ctx", 1024), @@ -90,8 +88,7 @@ func TestLogger(t *testing.T) { l = l.Named("hello") l = l.Named("hello2") - ctx, span := trace.StartSpan(bg, "trace") - ctx = slog.With(ctx, slog.F("ctx", io.EOF)) + ctx := slog.With(bg, slog.F("ctx", io.EOF)) l = l.With(slog.F("with", 2)) l.Info(ctx, "meow", slog.F("hi", "xd")) @@ -107,9 +104,7 @@ func TestLogger(t *testing.T) { File: slogTestFile, Func: "cdr.dev/slog_test.TestLogger.func3", - Line: 97, - - SpanContext: span.SpanContext(), + Line: 94, Fields: slog.M( slog.F("with", 2), diff --git a/sloggers/sloghuman/sloghuman.go b/sloggers/sloghuman/sloghuman.go index 719db7c..ec5554e 100644 --- a/sloggers/sloghuman/sloghuman.go +++ b/sloggers/sloghuman/sloghuman.go @@ -7,6 +7,8 @@ import ( "io" "strings" + "go.opencensus.io/trace" + "cdr.dev/slog" "cdr.dev/slog/internal/entryhuman" "cdr.dev/slog/internal/syncwriter" @@ -30,7 +32,7 @@ type humanSink struct { } func (s humanSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { - str := entryhuman.Fmt(s.w2, ent) + str := entryhuman.Fmt(s.w2, ent, trace.FromContext(ctx).SpanContext()) lines := strings.Split(str, "\n") // We need to add 4 spaces before every field line for readability. diff --git a/sloggers/slogjson/slogjson.go b/sloggers/slogjson/slogjson.go index 5069ddc..e08abb7 100644 --- a/sloggers/slogjson/slogjson.go +++ b/sloggers/slogjson/slogjson.go @@ -57,10 +57,11 @@ func (s jsonSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { m = append(m, slog.F("logger_names", ent.LoggerNames)) } - if ent.SpanContext != (trace.SpanContext{}) { + sc := trace.FromContext(ctx).SpanContext() + if sc != (trace.SpanContext{}) { m = append(m, - slog.F("trace", ent.SpanContext.TraceID), - slog.F("span", ent.SpanContext.SpanID), + slog.F("trace", sc.TraceID), + slog.F("span", sc.SpanID), ) } diff --git a/sloggers/slogstackdriver/slogstackdriver.go b/sloggers/slogstackdriver/slogstackdriver.go index 3b4045e..55282d2 100644 --- a/sloggers/slogstackdriver/slogstackdriver.go +++ b/sloggers/slogstackdriver/slogstackdriver.go @@ -54,11 +54,12 @@ func (s stackdriverSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { })) } - if ent.SpanContext != (trace.SpanContext{}) { + sc := trace.FromContext(ctx).SpanContext() + if sc != (trace.SpanContext{}) { e = append(e, - slog.F("logging.googleapis.com/trace", s.traceField(ent.SpanContext.TraceID)), - slog.F("logging.googleapis.com/spanId", ent.SpanContext.SpanID.String()), - slog.F("logging.googleapis.com/trace_sampled", ent.SpanContext.IsSampled()), + slog.F("logging.googleapis.com/trace", s.traceField(sc.TraceID)), + slog.F("logging.googleapis.com/spanId", sc.SpanID.String()), + slog.F("logging.googleapis.com/trace_sampled", sc.IsSampled()), ) } diff --git a/sloggers/slogtest/t.go b/sloggers/slogtest/t.go index e315184..f1d6619 100644 --- a/sloggers/slogtest/t.go +++ b/sloggers/slogtest/t.go @@ -11,6 +11,8 @@ import ( "os" "testing" + "go.opencensus.io/trace" + "cdr.dev/slog" "cdr.dev/slog/internal/entryhuman" "cdr.dev/slog/sloggers/sloghuman" @@ -49,7 +51,7 @@ type testSink struct { func (ts testSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { // The testing package logs to stdout and not stderr. - s := entryhuman.Fmt(os.Stdout, ent) + s := entryhuman.Fmt(os.Stdout, ent, trace.FromContext(ctx).SpanContext()) switch ent.Level { case slog.LevelDebug, slog.LevelInfo, slog.LevelWarn: From 97798d61379c4de0c16ea514166718ba1bc1d469 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 11 Dec 2019 19:16:56 -0600 Subject: [PATCH 06/17] Revert "Merge pull request #73 from cdr/opencensus" This reverts commit a44f627049e1cd327a34fdbf4d5ac04a6abd8c64, reversing changes made to 8f021e191dda8c2a343b59c6733bc59aaeafb282. --- README.md | 4 ++-- internal/entryhuman/entry.go | 8 ++++---- internal/entryhuman/entry_test.go | 21 +++++++++++---------- slog.go | 13 +++++++++---- slog_test.go | 11 ++++++++--- sloggers/sloghuman/sloghuman.go | 4 +--- sloggers/slogjson/slogjson.go | 7 +++---- sloggers/slogstackdriver/slogstackdriver.go | 9 ++++----- sloggers/slogtest/t.go | 4 +--- 9 files changed, 43 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index e16d14e..85d77d2 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ go get cdr.dev/slog - [Stdlib](https://godoc.org/cdr.dev/slog#Stdlib) log adapter - Skip caller frames with [slog.Helper](https://godoc.org/cdr.dev/slog#Helper) - Encodes values as if with `json.Marshal` -- Transparently log [opencensus](https://godoc.org/go.opencensus.io/trace) span's from the passed context -- [Zero dependencies](https://godoc.org/cdr.dev/slog?imports) +- Transparently log [opencensus](https://godoc.org/go.opencensus.io/trace) trace and span IDs +- [Single dependency](https://godoc.org/cdr.dev/slog?imports) on go.opencensus.io - Log to multiple sinks ## Example diff --git a/internal/entryhuman/entry.go b/internal/entryhuman/entry.go index dd99256..fcda4e9 100644 --- a/internal/entryhuman/entry.go +++ b/internal/entryhuman/entry.go @@ -49,7 +49,7 @@ func c(w io.Writer, attrs ...color.Attribute) *color.Color { // We also do not indent the fields as go's test does that automatically // for extra lines in a log so if we did it here, the fields would be indented // twice in test logs. So the Stderr logger indents all the fields itself. -func Fmt(w io.Writer, ent slog.SinkEntry, sc trace.SpanContext) string { +func Fmt(w io.Writer, ent slog.SinkEntry) string { var ents string ts := ent.Time.Format(TimeFormat) ents += ts + " " @@ -79,10 +79,10 @@ func Fmt(w io.Writer, ent slog.SinkEntry, sc trace.SpanContext) string { msg = quote(msg) ents += msg - if sc != (trace.SpanContext{}) { + if ent.SpanContext != (trace.SpanContext{}) { ent.Fields = append(slog.M( - slog.F("trace", sc.TraceID), - slog.F("span", sc.SpanID), + slog.F("trace", ent.SpanContext.TraceID), + slog.F("span", ent.SpanContext.SpanID), ), ent.Fields...) } diff --git a/internal/entryhuman/entry_test.go b/internal/entryhuman/entry_test.go index 3dbd4c1..0246ddb 100644 --- a/internal/entryhuman/entry_test.go +++ b/internal/entryhuman/entry_test.go @@ -14,11 +14,11 @@ import ( var kt = time.Date(2000, time.February, 5, 4, 4, 4, 4, time.UTC) -func TestFmt(t *testing.T) { +func TestEntry(t *testing.T) { t.Parallel() - test := func(t *testing.T, in slog.SinkEntry, sc trace.SpanContext, exp string) { - act := entryhuman.Fmt(ioutil.Discard, in, sc) + test := func(t *testing.T, in slog.SinkEntry, exp string) { + act := entryhuman.Fmt(ioutil.Discard, in) assert.Equal(t, exp, act, "entry") } @@ -33,7 +33,7 @@ func TestFmt(t *testing.T) { File: "myfile", Line: 100, Func: "ignored", - }, trace.SpanContext{}, `2000-02-05 04:04:04.000 [DEBUG] "wowowow\tizi"`) + }, `2000-02-05 04:04:04.000 [DEBUG] "wowowow\tizi"`) }) t.Run("multilineMessage", func(t *testing.T) { @@ -42,7 +42,7 @@ func TestFmt(t *testing.T) { test(t, slog.SinkEntry{ Message: "line1\nline2", Level: slog.LevelInfo, - }, trace.SpanContext{}, `0001-01-01 00:00:00.000 [INFO] <.:0> ... + }, `0001-01-01 00:00:00.000 [INFO] <.:0> ... "msg": line1 line2`) }) @@ -53,7 +53,7 @@ line2`) test(t, slog.SinkEntry{ Level: slog.LevelWarn, LoggerNames: []string{"named", "meow"}, - }, trace.SpanContext{}, `0001-01-01 00:00:00.000 [WARN] (named.meow) <.:0> ""`) + }, `0001-01-01 00:00:00.000 [WARN] (named.meow) <.:0> ""`) }) t.Run("trace", func(t *testing.T) { @@ -61,9 +61,10 @@ line2`) test(t, slog.SinkEntry{ Level: slog.LevelError, - }, trace.SpanContext{ - SpanID: trace.SpanID{0, 1, 2, 3, 4, 5, 6, 7}, - TraceID: trace.TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + SpanContext: trace.SpanContext{ + SpanID: trace.SpanID{0, 1, 2, 3, 4, 5, 6, 7}, + TraceID: trace.TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, }, `0001-01-01 00:00:00.000 [ERROR] <.:0> "" {"trace": "000102030405060708090a0b0c0d0e0f", "span": "0001020304050607"}`) }) @@ -75,7 +76,7 @@ line2`) Fields: slog.M( slog.F("hey", "hi"), ), - }, trace.SpanContext{}) + }) assert.Equal(t, "0001-01-01 00:00:00.000 \x1b[91m[CRITICAL]\x1b[0m\t\x1b[36m<.:0>\x1b[0m\t\"\"\t{\x1b[34m\"hey\"\x1b[0m: \x1b[32m\"hi\"\x1b[0m}", act, "entry") }) } diff --git a/slog.go b/slog.go index de2177b..d2cf1b3 100644 --- a/slog.go +++ b/slog.go @@ -17,6 +17,8 @@ import ( "runtime" "sync" "time" + + "go.opencensus.io/trace" ) // Sink is the destination of a Logger. @@ -145,10 +147,11 @@ func (l Logger) log(ctx context.Context, level Level, msg string, fields Map) { func (l Logger) entry(ctx context.Context, level Level, msg string, fields Map) SinkEntry { ent := SinkEntry{ - Time: time.Now().UTC(), - Level: level, - Message: msg, - Fields: fieldsFromContext(ctx).append(fields), + Time: time.Now().UTC(), + Level: level, + Message: msg, + Fields: fieldsFromContext(ctx).append(fields), + SpanContext: trace.FromContext(ctx).SpanContext(), } ent = ent.fillLoc(l.skip + 3) return ent @@ -272,6 +275,8 @@ type SinkEntry struct { File string Line int + SpanContext trace.SpanContext + Fields Map } diff --git a/slog_test.go b/slog_test.go index 7b6ab9f..741bddb 100644 --- a/slog_test.go +++ b/slog_test.go @@ -6,6 +6,8 @@ import ( "runtime" "testing" + "go.opencensus.io/trace" + "cdr.dev/slog" "cdr.dev/slog/internal/assert" ) @@ -72,7 +74,7 @@ func TestLogger(t *testing.T) { File: slogTestFile, Func: "cdr.dev/slog_test.TestLogger.func2", - Line: 64, + Line: 66, Fields: slog.M( slog.F("ctx", 1024), @@ -88,7 +90,8 @@ func TestLogger(t *testing.T) { l = l.Named("hello") l = l.Named("hello2") - ctx := slog.With(bg, slog.F("ctx", io.EOF)) + ctx, span := trace.StartSpan(bg, "trace") + ctx = slog.With(ctx, slog.F("ctx", io.EOF)) l = l.With(slog.F("with", 2)) l.Info(ctx, "meow", slog.F("hi", "xd")) @@ -104,7 +107,9 @@ func TestLogger(t *testing.T) { File: slogTestFile, Func: "cdr.dev/slog_test.TestLogger.func3", - Line: 94, + Line: 97, + + SpanContext: span.SpanContext(), Fields: slog.M( slog.F("with", 2), diff --git a/sloggers/sloghuman/sloghuman.go b/sloggers/sloghuman/sloghuman.go index ec5554e..719db7c 100644 --- a/sloggers/sloghuman/sloghuman.go +++ b/sloggers/sloghuman/sloghuman.go @@ -7,8 +7,6 @@ import ( "io" "strings" - "go.opencensus.io/trace" - "cdr.dev/slog" "cdr.dev/slog/internal/entryhuman" "cdr.dev/slog/internal/syncwriter" @@ -32,7 +30,7 @@ type humanSink struct { } func (s humanSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { - str := entryhuman.Fmt(s.w2, ent, trace.FromContext(ctx).SpanContext()) + str := entryhuman.Fmt(s.w2, ent) lines := strings.Split(str, "\n") // We need to add 4 spaces before every field line for readability. diff --git a/sloggers/slogjson/slogjson.go b/sloggers/slogjson/slogjson.go index e08abb7..5069ddc 100644 --- a/sloggers/slogjson/slogjson.go +++ b/sloggers/slogjson/slogjson.go @@ -57,11 +57,10 @@ func (s jsonSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { m = append(m, slog.F("logger_names", ent.LoggerNames)) } - sc := trace.FromContext(ctx).SpanContext() - if sc != (trace.SpanContext{}) { + if ent.SpanContext != (trace.SpanContext{}) { m = append(m, - slog.F("trace", sc.TraceID), - slog.F("span", sc.SpanID), + slog.F("trace", ent.SpanContext.TraceID), + slog.F("span", ent.SpanContext.SpanID), ) } diff --git a/sloggers/slogstackdriver/slogstackdriver.go b/sloggers/slogstackdriver/slogstackdriver.go index 55282d2..3b4045e 100644 --- a/sloggers/slogstackdriver/slogstackdriver.go +++ b/sloggers/slogstackdriver/slogstackdriver.go @@ -54,12 +54,11 @@ func (s stackdriverSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { })) } - sc := trace.FromContext(ctx).SpanContext() - if sc != (trace.SpanContext{}) { + if ent.SpanContext != (trace.SpanContext{}) { e = append(e, - slog.F("logging.googleapis.com/trace", s.traceField(sc.TraceID)), - slog.F("logging.googleapis.com/spanId", sc.SpanID.String()), - slog.F("logging.googleapis.com/trace_sampled", sc.IsSampled()), + slog.F("logging.googleapis.com/trace", s.traceField(ent.SpanContext.TraceID)), + slog.F("logging.googleapis.com/spanId", ent.SpanContext.SpanID.String()), + slog.F("logging.googleapis.com/trace_sampled", ent.SpanContext.IsSampled()), ) } diff --git a/sloggers/slogtest/t.go b/sloggers/slogtest/t.go index f1d6619..e315184 100644 --- a/sloggers/slogtest/t.go +++ b/sloggers/slogtest/t.go @@ -11,8 +11,6 @@ import ( "os" "testing" - "go.opencensus.io/trace" - "cdr.dev/slog" "cdr.dev/slog/internal/entryhuman" "cdr.dev/slog/sloggers/sloghuman" @@ -51,7 +49,7 @@ type testSink struct { func (ts testSink) LogEntry(ctx context.Context, ent slog.SinkEntry) { // The testing package logs to stdout and not stderr. - s := entryhuman.Fmt(os.Stdout, ent, trace.FromContext(ctx).SpanContext()) + s := entryhuman.Fmt(os.Stdout, ent) switch ent.Level { case slog.LevelDebug, slog.LevelInfo, slog.LevelWarn: From 1e111e47a212988025ccd1e06dcabb3a323efc42 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Thu, 12 Dec 2019 15:18:25 -0600 Subject: [PATCH 07/17] assert: Add Error and handle error unwrapping in Equal --- sloggers/slogtest/assert/assert.go | 35 ++++++++++++++++++++++--- sloggers/slogtest/assert/assert_test.go | 27 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index 7d95e59..ab2228d 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -1,7 +1,12 @@ // Package assert is a helper package for test assertions. +// +// On failure, every assertion will fatal the test. +// +// The name parameter is available in each assertion for easier debugging. package assert // import "cdr.dev/slog/sloggers/slogtest/assert" import ( + "errors" "testing" "cdr.dev/slog" @@ -13,8 +18,15 @@ import ( // // If they are not equal, it will fatal the test with a diff of the // two objects. +// +// If act is an error it will be unwrapped. func Equal(t testing.TB, exp, act interface{}, name string) { slog.Helper() + + if err, ok := act.(error); ok { + act = unwrapErr(err) + } + if diff := assert.CmpDiff(exp, act); diff != "" { slogtest.Fatal(t, "unexpected value", slog.F("name", name), @@ -24,8 +36,6 @@ func Equal(t testing.TB, exp, act interface{}, name string) { } // Success asserts err == nil. -// -// If err isn't nil, it will fatal the test with the error. func Success(t testing.TB, err error, name string) { slog.Helper() if err != nil { @@ -37,9 +47,26 @@ func Success(t testing.TB, err error, name string) { } // True asserts act == true. -// -// If act isn't true, it will fatal the test. func True(t testing.TB, act bool, name string) { slog.Helper() Equal(t, true, act, name) } + +// Error asserts err != nil. +func Error(t testing.TB, err error, name string) { + slog.Helper() + if err == nil { + slogtest.Fatal(t, "expected error", + slog.F("name", name), + ) + } +} + +func unwrapErr(err error) error { + uerr := errors.Unwrap(err) + for uerr != nil { + err = uerr + uerr = errors.Unwrap(uerr) + } + return err +} diff --git a/sloggers/slogtest/assert/assert_test.go b/sloggers/slogtest/assert/assert_test.go index 8b8255d..3085d60 100644 --- a/sloggers/slogtest/assert/assert_test.go +++ b/sloggers/slogtest/assert/assert_test.go @@ -1,6 +1,7 @@ package assert_test import ( + "fmt" "io" "testing" @@ -21,6 +22,19 @@ func TestEqual(t *testing.T) { assert.Equal(tb, 3, 4, "meow") } +func TestEqual_error(t *testing.T) { + t.Parallel() + + tb := &fakeTB{} + assert.Equal(tb, io.EOF, fmt.Errorf("failed: %w", io.EOF), "meow") + + defer func() { + recover() + simpleassert.Equal(t, 1, tb.fatals, "fatals") + }() + assert.Equal(tb, io.ErrClosedPipe, fmt.Errorf("failed: %w", io.EOF), "meow") +} + func TestSuccess(t *testing.T) { t.Parallel() @@ -47,6 +61,19 @@ func TestTrue(t *testing.T) { assert.True(tb, false, "meow") } +func TestError(t *testing.T) { + t.Parallel() + + tb := &fakeTB{} + assert.Error(tb, io.EOF, "meow") + + defer func() { + recover() + simpleassert.Equal(t, 1, tb.fatals, "fatals") + }() + assert.Error(tb, nil, "meow") +} + type fakeTB struct { testing.TB From 517dff0a06e7723121bb05c56739cd9a33d932c2 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 13 Dec 2019 18:41:07 -0600 Subject: [PATCH 08/17] Remove google/go-cmp See https://github.com/google/go-cmp/issues/174 In general its a very heavy library and I'm not convinced it offers enough. --- go.mod | 1 - go.sum | 2 - internal/assert/assert.go | 9 ++--- internal/assert/cmp.go | 54 ------------------------- internal/entryhuman/entry_test.go | 12 ++++++ sloggers/slogtest/assert/assert.go | 20 +++++---- sloggers/slogtest/assert/assert_test.go | 2 +- 7 files changed, 29 insertions(+), 71 deletions(-) delete mode 100644 internal/assert/cmp.go diff --git a/go.mod b/go.mod index 0b417c5..74d66ae 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( cloud.google.com/go v0.43.0 github.com/alecthomas/chroma v0.6.6 github.com/fatih/color v1.7.0 - github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65 github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-isatty v0.0.9 // indirect go.opencensus.io v0.22.1 diff --git a/go.sum b/go.sum index f21f294..3ecd103 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65 h1:B3yqxlLHBEoav+FDQM8ph7IIRA6AhQ70w119k3hoT2Y= -github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= diff --git a/internal/assert/assert.go b/internal/assert/assert.go index 4ac73a2..3f920c8 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -10,17 +10,16 @@ import ( // Equal asserts exp == act. func Equal(t testing.TB, exp, act interface{}, name string) { t.Helper() - diff := CmpDiff(exp, act) - if diff != "" { - t.Fatalf("unexpected %v: %v", name, diff) + if !reflect.DeepEqual(exp, act) { + t.Fatalf("unexpected %v: exp: %q but got %q", name, exp, act) } } // NotEqual asserts exp != act. func NotEqual(t testing.TB, exp, act interface{}, name string) { t.Helper() - if CmpDiff(exp, act) == "" { - t.Fatalf("expected different %v: %+v", name, act) + if reflect.DeepEqual(exp, act) { + t.Fatalf("expected different %v: %q", name, act) } } diff --git a/internal/assert/cmp.go b/internal/assert/cmp.go deleted file mode 100644 index c3768be..0000000 --- a/internal/assert/cmp.go +++ /dev/null @@ -1,54 +0,0 @@ -package assert - -import ( - "reflect" - - "github.com/google/go-cmp/cmp" -) - -// CmpDiff compares exp to act and returns a human readable string representing their diff. -// https://github.com/google/go-cmp/issues/40#issuecomment-328615283 -func CmpDiff(exp, act interface{}) string { - return cmp.Diff(exp, act, deepAllowUnexported(exp, act)) -} - -func deepAllowUnexported(vs ...interface{}) cmp.Option { - m := make(map[reflect.Type]struct{}) - for _, v := range vs { - structTypes(reflect.ValueOf(v), m) - } - var typs []interface{} - for t := range m { - typs = append(typs, reflect.New(t).Elem().Interface()) - } - return cmp.AllowUnexported(typs...) -} - -func structTypes(v reflect.Value, m map[reflect.Type]struct{}) { - if !v.IsValid() { - return - } - switch v.Kind() { - case reflect.Ptr: - if !v.IsNil() { - structTypes(v.Elem(), m) - } - case reflect.Interface: - if !v.IsNil() { - structTypes(v.Elem(), m) - } - case reflect.Slice, reflect.Array: - for i := 0; i < v.Len(); i++ { - structTypes(v.Index(i), m) - } - case reflect.Map: - for _, k := range v.MapKeys() { - structTypes(v.MapIndex(k), m) - } - case reflect.Struct: - m[v.Type()] = struct{}{} - for i := 0; i < v.NumField(); i++ { - structTypes(v.Field(i), m) - } - } -} diff --git a/internal/entryhuman/entry_test.go b/internal/entryhuman/entry_test.go index 0246ddb..b476870 100644 --- a/internal/entryhuman/entry_test.go +++ b/internal/entryhuman/entry_test.go @@ -47,6 +47,18 @@ func TestEntry(t *testing.T) { line2`) }) + t.Run("multilineField", func(t *testing.T) { + t.Parallel() + + test(t, slog.SinkEntry{ + Message: "msg", + Level: slog.LevelInfo, + Fields: slog.M(slog.F("field", "line1\nline2")), + }, `0001-01-01 00:00:00.000 [INFO] <.:0> msg ... +"field": line1 +line2`) + }) + t.Run("named", func(t *testing.T) { t.Parallel() diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index ab2228d..b1ac367 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -7,10 +7,10 @@ package assert // import "cdr.dev/slog/sloggers/slogtest/assert" import ( "errors" + "reflect" "testing" "cdr.dev/slog" - "cdr.dev/slog/internal/assert" "cdr.dev/slog/sloggers/slogtest" ) @@ -19,18 +19,18 @@ import ( // If they are not equal, it will fatal the test with a diff of the // two objects. // -// If act is an error it will be unwrapped. +// If act or exp is an error it will be unwrapped. func Equal(t testing.TB, exp, act interface{}, name string) { slog.Helper() - if err, ok := act.(error); ok { - act = unwrapErr(err) - } + exp = unwrapErr(exp) + act = unwrapErr(act) - if diff := assert.CmpDiff(exp, act); diff != "" { + if !reflect.DeepEqual(exp, act) { slogtest.Fatal(t, "unexpected value", slog.F("name", name), - slog.F("diff", diff), + slog.F("exp", exp), + slog.F("act", act), ) } } @@ -62,7 +62,11 @@ func Error(t testing.TB, err error, name string) { } } -func unwrapErr(err error) error { +func unwrapErr(v interface{}) interface{} { + err, ok := v.(error) + if !ok { + return v + } uerr := errors.Unwrap(err) for uerr != nil { err = uerr diff --git a/sloggers/slogtest/assert/assert_test.go b/sloggers/slogtest/assert/assert_test.go index 3085d60..6008294 100644 --- a/sloggers/slogtest/assert/assert_test.go +++ b/sloggers/slogtest/assert/assert_test.go @@ -91,5 +91,5 @@ func (tb *fakeTB) Error(v ...interface{}) { func (tb *fakeTB) Fatal(v ...interface{}) { tb.fatals++ - panic("") + panic(fmt.Sprint(v...)) } From f212120e91555ccf4b0207815b828481fad742ae Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Sun, 15 Dec 2019 15:38:09 -0600 Subject: [PATCH 09/17] Handle structs with unexported fields and all Go values better --- map.go | 87 ++++++++++++++++++++++++++++++++++------------------- map_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 129 insertions(+), 38 deletions(-) diff --git a/map.go b/map.go index 6627f40..c810d60 100644 --- a/map.go +++ b/map.go @@ -27,17 +27,23 @@ var _ json.Marshaler = Map(nil) // // Every field value is encoded with the following process: // -// 1. slog.Value is handled to allow any type to replace its representation for logging. +// 1. slog.Value is checked to allow any type to replace its representation for logging. +// +// 2. json.Marshaller is handled. // // 2. xerrors.Formatter is handled. // -// 3. Protobufs are handled with json.Marshal. +// 3. Protobufs are encoded with json.Marshal. +// +// 4. error and fmt.Stringer are used if possible. // -// 4. error and fmt.Stringer are handled. +// 5. slices and arrays go through the encode function for every element. // -// 5. slices and arrays are handled to go through the encode function for every value. +// 6. If the value is a struct without exported fields or a type that +// cannot be encoded with json.Marshal (like channels) then +// fmt.Sprintf("%+v") is used. // -// 6. json.Marshal is invoked as the default case. +// 8. json.Marshal(v) is used for all other values. func (m Map) MarshalJSON() ([]byte, error) { b := &bytes.Buffer{} b.WriteByte('{') @@ -56,8 +62,11 @@ func (m Map) MarshalJSON() ([]byte, error) { return b.Bytes(), nil } -// ForceJSON ensures the value is logged via json.Marshal even -// if it implements fmt.Stringer or error. +// ForceJSON ensures the value is logged via json.Marshal. +// +// Use it when implementing SlogValue to ensure a structured +// representation of a struct if you know it's capable even +// when it implements fmt.Stringer or error. func ForceJSON(v interface{}) interface{} { return jsonVal{v: v} } @@ -66,13 +75,6 @@ type jsonVal struct { v interface{} } -var _ json.Marshaler = jsonVal{} - -// MarshalJSON implements json.Marshaler. -func (v jsonVal) MarshalJSON() ([]byte, error) { - return json.Marshal(v.v) -} - func marshalList(rv reflect.Value) []byte { b := &bytes.Buffer{} b.WriteByte('[') @@ -93,6 +95,10 @@ func encode(v interface{}) []byte { switch v := v.(type) { case Value: return encode(v.SlogValue()) + case json.Marshaler: + return encodeJSON(v) + case jsonVal: + return encodeJSON(v.v) case xerrors.Formatter: return encode(errorChain(v)) case interface { @@ -103,28 +109,47 @@ func encode(v interface{}) []byte { return encode(fmt.Sprint(v)) default: rv := reflect.Indirect(reflect.ValueOf(v)) - if rv.IsValid() { - switch rv.Type().Kind() { - case reflect.Slice: - if rv.IsNil() { - break - } - fallthrough - case reflect.Array: + if !rv.IsValid() { + return encodeJSON(v) + } + + switch rv.Type().Kind() { + case reflect.Slice: + if !rv.IsNil() { return marshalList(rv) } - } + case reflect.Array: + return marshalList(rv) + case reflect.Struct: + typ := rv.Type() + for i := 0; i < rv.NumField(); i++ { + // Found an exported field. + if typ.Field(i).PkgPath == "" { + return encodeJSON(v) + } + } - b, err := json.Marshal(v) - if err != nil { - return encode(M( - Error(xerrors.Errorf("failed to marshal to JSON: %w", err)), - F("type", reflect.TypeOf(v)), - F("value", fmt.Sprintf("%+v", v)), - )) + return encodeJSON(fmt.Sprintf("%+v", v)) + case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func: + // These types cannot be directly encoded with json.Marshal. + // See https://golang.org/pkg/encoding/json/#Marshal + return encodeJSON(fmt.Sprintf("%+v", v)) } - return b + + return encodeJSON(v) + } +} + +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)), + F("type", reflect.TypeOf(v)), + F("value", fmt.Sprintf("%+v", v)), + )) } + return b } func errorChain(f xerrors.Formatter) []interface{} { diff --git a/map_test.go b/map_test.go index 57f44f0..b02793e 100644 --- a/map_test.go +++ b/map_test.go @@ -3,11 +3,11 @@ package slog_test import ( "bytes" "encoding/json" - "fmt" "io" "runtime" "strings" "testing" + "time" "golang.org/x/xerrors" @@ -86,19 +86,19 @@ func TestMap(t *testing.T) { mapTestFile := strings.Replace(mapTestFile, "_test", "", 1) test(t, slog.M( - slog.F("meow", indentJSON), + slog.F("meow", slog.ForceJSON(complex(10, 10))), ), `{ "meow": { "error": [ { "msg": "failed to marshal to JSON", - "fun": "cdr.dev/slog.encode", - "loc": "`+mapTestFile+`:121" + "fun": "cdr.dev/slog.encodeJSON", + "loc": "`+mapTestFile+`:147" }, - "json: unsupported type: func(*testing.T, string) string" + "json: unsupported type: complex128" ], - "type": "func(*testing.T, string) string", - "value": "`+fmt.Sprint(interface{}(indentJSON))+`" + "type": "complex128", + "value": "(10+10i)" } }`) }) @@ -145,6 +145,24 @@ func TestMap(t *testing.T) { }`) }) + t.Run("array", func(t *testing.T) { + t.Parallel() + + test(t, slog.M( + slog.F("meow", [3]string{ + "1", + "2", + "3", + }), + ), `{ + "meow": [ + "1", + "2", + "3" + ] + }`) + }) + t.Run("forceJSON", func(t *testing.T) { t.Parallel() @@ -174,6 +192,54 @@ func TestMap(t *testing.T) { "slice": null }`) }) + + t.Run("nil", func(t *testing.T) { + t.Parallel() + + test(t, slog.M( + slog.F("val", nil), + ), `{ + "val": null + }`) + }) + + t.Run("json.Marshaler", func(t *testing.T) { + t.Parallel() + + test(t, slog.M( + slog.F("val", time.Date(2000, 02, 05, 4, 4, 4, 0, time.UTC)), + ), `{ + "val": "2000-02-05T04:04:04Z" + }`) + }) + + t.Run("complex", func(t *testing.T) { + t.Parallel() + + test(t, slog.M( + slog.F("val", complex(10, 10)), + ), `{ + "val": "(10+10i)" + }`) + }) + + t.Run("privateStruct", func(t *testing.T) { + t.Parallel() + + test(t, slog.M( + slog.F("val", struct { + meow string + bar int + far uint + }{ + meow: "hi", + bar: 23, + far: 600, + }), + ), `{ + "val": "{meow:hi bar:23 far:600}" + }`) + }) } type meow struct { From ece955bb9ed9239a85ec124beeacf624799a4f3f Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Sun, 15 Dec 2019 16:12:21 -0600 Subject: [PATCH 10/17] assert: add ErrorContains and move name arg to first parameter --- internal/assert/assert.go | 41 ++++------------- internal/entryhuman/entry_test.go | 4 +- internal/syncwriter/syncwriter_test.go | 12 ++--- map_test.go | 6 +-- s_test.go | 6 +-- slog_test.go | 38 ++++++++-------- sloggers/sloghuman/sloghuman_test.go | 6 +-- sloggers/slogjson/slogjson_test.go | 2 +- .../slogstackdriver/slogstackdriver_test.go | 12 ++--- sloggers/slogtest/assert/assert.go | 37 +++++++++++++--- sloggers/slogtest/assert/assert_test.go | 44 ++++++++++++------- sloggers/slogtest/t_test.go | 8 ++-- 12 files changed, 116 insertions(+), 100 deletions(-) diff --git a/internal/assert/assert.go b/internal/assert/assert.go index 3f920c8..69a92b3 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -3,28 +3,19 @@ package assert import ( "reflect" - "strings" "testing" ) // Equal asserts exp == act. -func Equal(t testing.TB, exp, act interface{}, name string) { +func Equal(t testing.TB, name string, exp, act interface{}) { t.Helper() if !reflect.DeepEqual(exp, act) { t.Fatalf("unexpected %v: exp: %q but got %q", name, exp, act) } } -// NotEqual asserts exp != act. -func NotEqual(t testing.TB, exp, act interface{}, name string) { - t.Helper() - if reflect.DeepEqual(exp, act) { - t.Fatalf("expected different %v: %q", name, act) - } -} - // Success asserts err == nil. -func Success(t testing.TB, err error, name string) { +func Success(t testing.TB, name string, err error) { t.Helper() if err != nil { t.Fatalf("unexpected error for %v: %+v", name, err) @@ -32,46 +23,30 @@ func Success(t testing.TB, err error, name string) { } // Error asserts exp != nil. -func Error(t testing.TB, err error, name string) { +func Error(t testing.TB, name string, err error) { t.Helper() if err == nil { t.Fatalf("expected error from %v", name) } } -// ErrorContains asserts the error string from err contains sub. -func ErrorContains(t testing.TB, err error, sub, name string) { - t.Helper() - Error(t, err, name) - errs := err.Error() - if !strings.Contains(errs, sub) { - t.Fatalf("error string %q from %v does not contain %q", errs, name, sub) - } -} - // True asserts true == act. -func True(t testing.TB, act bool, name string) { +func True(t testing.TB, name string, act bool) { t.Helper() - Equal(t, true, act, name) + Equal(t, name, true, act) } // False asserts false == act. -func False(t testing.TB, act bool, name string) { +func False(t testing.TB, name string, act bool) { t.Helper() - Equal(t, false, act, name) + Equal(t, name, false, act) } // Len asserts n == len(a). -func Len(t testing.TB, n int, a interface{}, name string) { +func Len(t testing.TB, name string, n int, a interface{}) { t.Helper() act := reflect.ValueOf(a).Len() if n != act { t.Fatalf("expected len(%v) == %v but got %v", name, n, act) } } - -// Nil asserts v == nil. -func Nil(t testing.TB, v interface{}, name string) { - t.Helper() - Equal(t, nil, v, name) -} diff --git a/internal/entryhuman/entry_test.go b/internal/entryhuman/entry_test.go index b476870..c668500 100644 --- a/internal/entryhuman/entry_test.go +++ b/internal/entryhuman/entry_test.go @@ -19,7 +19,7 @@ func TestEntry(t *testing.T) { test := func(t *testing.T, in slog.SinkEntry, exp string) { act := entryhuman.Fmt(ioutil.Discard, in) - assert.Equal(t, exp, act, "entry") + assert.Equal(t, "entry", exp, act) } t.Run("basic", func(t *testing.T) { @@ -89,6 +89,6 @@ line2`) slog.F("hey", "hi"), ), }) - assert.Equal(t, "0001-01-01 00:00:00.000 \x1b[91m[CRITICAL]\x1b[0m\t\x1b[36m<.:0>\x1b[0m\t\"\"\t{\x1b[34m\"hey\"\x1b[0m: \x1b[32m\"hi\"\x1b[0m}", act, "entry") + assert.Equal(t, "entry", "0001-01-01 00:00:00.000 \x1b[91m[CRITICAL]\x1b[0m\t\x1b[36m<.:0>\x1b[0m\t\"\"\t{\x1b[34m\"hey\"\x1b[0m: \x1b[32m\"hi\"\x1b[0m}", act) }) } diff --git a/internal/syncwriter/syncwriter_test.go b/internal/syncwriter/syncwriter_test.go index d6eb5e6..e001320 100644 --- a/internal/syncwriter/syncwriter_test.go +++ b/internal/syncwriter/syncwriter_test.go @@ -31,7 +31,7 @@ func TestWriter_Sync(t *testing.T) { tw := newWriter(nil) tw.w.Sync("test") - assert.Equal(t, 0, tw.errors, "errors") + assert.Equal(t, "errors", 0, tw.errors) }) t.Run("syncWriter", func(t *testing.T) { @@ -46,9 +46,9 @@ func TestWriter_Sync(t *testing.T) { }, }) tw.w.Write("hello", nil) - assert.Equal(t, 1, tw.errors, "errors") + assert.Equal(t, "errors", 1, tw.errors) tw.w.Sync("test") - assert.Equal(t, 2, tw.errors, "errors") + assert.Equal(t, "errors", 2, tw.errors) }) t.Run("stdout", func(t *testing.T) { @@ -56,7 +56,7 @@ func TestWriter_Sync(t *testing.T) { tw := newWriter(os.Stdout) tw.w.Sync("test") - assert.Equal(t, 0, tw.errors, "errors") + assert.Equal(t, "errors", 0, tw.errors) }) t.Run("errorf", func(t *testing.T) { @@ -77,8 +77,8 @@ func TestWriter_Sync(t *testing.T) { func Test_errorsIsAny(t *testing.T) { t.Parallel() - assert.True(t, errorsIsAny(io.EOF, io.ErrUnexpectedEOF, io.EOF), "err") - assert.False(t, errorsIsAny(io.EOF, io.ErrUnexpectedEOF, io.ErrClosedPipe), "err") + assert.True(t, "err", errorsIsAny(io.EOF, io.ErrUnexpectedEOF, io.EOF)) + assert.False(t, "err", errorsIsAny(io.EOF, io.ErrUnexpectedEOF, io.ErrClosedPipe)) } type syncWriter struct { diff --git a/map_test.go b/map_test.go index b02793e..3b98d7a 100644 --- a/map_test.go +++ b/map_test.go @@ -24,7 +24,7 @@ func TestMap(t *testing.T) { t.Helper() exp = indentJSON(t, exp) act := marshalJSON(t, m) - assert.Equal(t, exp, act, "JSON") + assert.Equal(t, "JSON", exp, act) } t.Run("JSON", func(t *testing.T) { @@ -253,13 +253,13 @@ func (m meow) SlogValue() interface{} { func indentJSON(t *testing.T, j string) string { b := &bytes.Buffer{} err := json.Indent(b, []byte(j), "", strings.Repeat(" ", 4)) - assert.Success(t, err, "indent JSON") + assert.Success(t, "indent JSON", err) return b.String() } func marshalJSON(t *testing.T, m slog.Map) string { actb, err := json.Marshal(m) - assert.Success(t, err, "marshal map to JSON") + assert.Success(t, "marshal map to JSON", err) return indentJSON(t, string(actb)) } diff --git a/s_test.go b/s_test.go index 2cdf78f..a589460 100644 --- a/s_test.go +++ b/s_test.go @@ -21,7 +21,7 @@ func TestStdlib(t *testing.T) { stdlibLog.Println("stdlib") et, rest, err := entryhuman.StripTimestamp(b.String()) - assert.Success(t, err, "strip timestamp") - assert.False(t, et.IsZero(), "timestamp") - assert.Equal(t, " [INFO]\t(stdlib)\t\tstdlib\t{\"hi\": \"we\"}\n", rest, "entry") + 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) } diff --git a/slog_test.go b/slog_test.go index 741bddb..a55def2 100644 --- a/slog_test.go +++ b/slog_test.go @@ -44,10 +44,10 @@ func TestLogger(t *testing.T) { l.Info(bg, "wow", slog.Error(io.EOF)) l.Error(bg, "meow", slog.Error(io.ErrUnexpectedEOF)) - assert.Equal(t, 1, s1.syncs, "syncs") - assert.Len(t, 1, s1.entries, "entries") + assert.Equal(t, "syncs", 1, s1.syncs) + assert.Len(t, "entries", 1, s1.entries) - assert.Equal(t, s1, s2, "sinks") + assert.Equal(t, "sinks", s1, s2) }) t.Run("helper", func(t *testing.T) { @@ -65,8 +65,8 @@ func TestLogger(t *testing.T) { ) h(ctx) - assert.Len(t, 1, s.entries, "entries") - assert.Equal(t, slog.SinkEntry{ + assert.Len(t, "entries", 1, s.entries) + assert.Equal(t, "entry", slog.SinkEntry{ Time: s.entries[0].Time, Level: slog.LevelInfo, @@ -79,7 +79,7 @@ func TestLogger(t *testing.T) { Fields: slog.M( slog.F("ctx", 1024), ), - }, s.entries[0], "entry") + }, s.entries[0]) }) t.Run("entry", func(t *testing.T) { @@ -96,8 +96,8 @@ func TestLogger(t *testing.T) { l.Info(ctx, "meow", slog.F("hi", "xd")) - assert.Len(t, 1, s.entries, "entries") - assert.Equal(t, slog.SinkEntry{ + assert.Len(t, "entries", 1, s.entries) + assert.Equal(t, "entry", slog.SinkEntry{ Time: s.entries[0].Time, Level: slog.LevelInfo, @@ -116,7 +116,7 @@ func TestLogger(t *testing.T) { slog.F("ctx", io.EOF), slog.F("hi", "xd"), ), - }, s.entries[0], "entry") + }, s.entries[0]) }) t.Run("levels", func(t *testing.T) { @@ -138,20 +138,20 @@ func TestLogger(t *testing.T) { l.Critical(bg, "") l.Fatal(bg, "") - assert.Len(t, 6, s.entries, "entries") - assert.Equal(t, 3, s.syncs, "syncs") - assert.Equal(t, slog.LevelDebug, s.entries[0].Level, "level") - assert.Equal(t, slog.LevelInfo, s.entries[1].Level, "level") - assert.Equal(t, slog.LevelWarn, s.entries[2].Level, "level") - assert.Equal(t, slog.LevelError, s.entries[3].Level, "level") - assert.Equal(t, slog.LevelCritical, s.entries[4].Level, "level") - assert.Equal(t, slog.LevelFatal, s.entries[5].Level, "level") - assert.Equal(t, 1, exits, "exits") + assert.Len(t, "entries", 6, s.entries) + assert.Equal(t, "syncs", 3, s.syncs) + assert.Equal(t, "level", slog.LevelDebug, s.entries[0].Level) + assert.Equal(t, "level", slog.LevelInfo, s.entries[1].Level) + assert.Equal(t, "level", slog.LevelWarn, s.entries[2].Level) + assert.Equal(t, "level", slog.LevelError, s.entries[3].Level) + assert.Equal(t, "level", slog.LevelCritical, s.entries[4].Level) + assert.Equal(t, "level", slog.LevelFatal, s.entries[5].Level) + assert.Equal(t, "exits", 1, exits) }) } func TestLevel_String(t *testing.T) { t.Parallel() - assert.Equal(t, "slog.Level(12)", slog.Level(12).String(), "level string") + assert.Equal(t, "level string", "slog.Level(12)", slog.Level(12).String()) } diff --git a/sloggers/sloghuman/sloghuman_test.go b/sloggers/sloghuman/sloghuman_test.go index 71b88ca..5c62637 100644 --- a/sloggers/sloghuman/sloghuman_test.go +++ b/sloggers/sloghuman/sloghuman_test.go @@ -22,7 +22,7 @@ func TestMake(t *testing.T) { l.Sync() et, rest, err := entryhuman.StripTimestamp(b.String()) - assert.Success(t, err, "strip timestamp") - assert.False(t, et.IsZero(), "timestamp") - assert.Equal(t, " [INFO]\t\t...\t{\"wowow\": \"me\\nyou\"}\n \"msg\": line1\n\n line2\n", rest, "entry") + 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) } diff --git a/sloggers/slogjson/slogjson_test.go b/sloggers/slogjson/slogjson_test.go index e34307d..77ee4e4 100644 --- a/sloggers/slogjson/slogjson_test.go +++ b/sloggers/slogjson/slogjson_test.go @@ -31,5 +31,5 @@ func TestMake(t *testing.T) { 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"}} `, slogjsonTestFile, s.SpanContext().TraceID, s.SpanContext().SpanID) - assert.Equal(t, exp, j, "entry") + assert.Equal(t, "entry", exp, j) } diff --git a/sloggers/slogstackdriver/slogstackdriver_test.go b/sloggers/slogstackdriver/slogstackdriver_test.go index 5e2beda..3a4a295 100644 --- a/sloggers/slogstackdriver/slogstackdriver_test.go +++ b/sloggers/slogstackdriver/slogstackdriver_test.go @@ -31,15 +31,15 @@ func TestStackdriver(t *testing.T) { 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"} `, slogstackdriverTestFile, s.SpanContext().TraceID, s.SpanContext().SpanID) - assert.Equal(t, exp, j, "entry") + assert.Equal(t, "entry", exp, j) } func TestSevMapping(t *testing.T) { t.Parallel() - assert.Equal(t, logpbtype.LogSeverity_DEBUG, slogstackdriver.Sev(slog.LevelDebug), "level") - assert.Equal(t, logpbtype.LogSeverity_INFO, slogstackdriver.Sev(slog.LevelInfo), "level") - assert.Equal(t, logpbtype.LogSeverity_WARNING, slogstackdriver.Sev(slog.LevelWarn), "level") - assert.Equal(t, logpbtype.LogSeverity_ERROR, slogstackdriver.Sev(slog.LevelError), "level") - assert.Equal(t, logpbtype.LogSeverity_CRITICAL, slogstackdriver.Sev(slog.LevelCritical), "level") + assert.Equal(t, "level", logpbtype.LogSeverity_DEBUG, slogstackdriver.Sev(slog.LevelDebug)) + assert.Equal(t, "level", logpbtype.LogSeverity_INFO, slogstackdriver.Sev(slog.LevelInfo)) + assert.Equal(t, "level", logpbtype.LogSeverity_WARNING, slogstackdriver.Sev(slog.LevelWarn)) + assert.Equal(t, "level", logpbtype.LogSeverity_ERROR, slogstackdriver.Sev(slog.LevelError)) + assert.Equal(t, "level", logpbtype.LogSeverity_CRITICAL, slogstackdriver.Sev(slog.LevelCritical)) } diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index b1ac367..1584f54 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -8,6 +8,7 @@ package assert // import "cdr.dev/slog/sloggers/slogtest/assert" import ( "errors" "reflect" + "strings" "testing" "cdr.dev/slog" @@ -20,7 +21,7 @@ import ( // two objects. // // If act or exp is an error it will be unwrapped. -func Equal(t testing.TB, exp, act interface{}, name string) { +func Equal(t testing.TB, name string, exp, act interface{}) { slog.Helper() exp = unwrapErr(exp) @@ -36,7 +37,7 @@ func Equal(t testing.TB, exp, act interface{}, name string) { } // Success asserts err == nil. -func Success(t testing.TB, err error, name string) { +func Success(t testing.TB, name string, err error) { slog.Helper() if err != nil { slogtest.Fatal(t, "unexpected error", @@ -47,13 +48,13 @@ func Success(t testing.TB, err error, name string) { } // True asserts act == true. -func True(t testing.TB, act bool, name string) { +func True(t testing.TB, name string, act bool) { slog.Helper() - Equal(t, true, act, name) + Equal(t, name, true, act) } // Error asserts err != nil. -func Error(t testing.TB, err error, name string) { +func Error(t testing.TB, name string, err error) { slog.Helper() if err == nil { slogtest.Fatal(t, "expected error", @@ -74,3 +75,29 @@ func unwrapErr(v interface{}) interface{} { } return err } + +// ErrorContains asserts err != nil and err.Error() contains sub. +// +// The match will be case insensitive. +func ErrorContains(t testing.TB, name string, err error, sub string) { + slog.Helper() + + Error(t, name, err) + + errs := err.Error() + if !stringContainsFold(errs, sub) { + slogtest.Fatal(t, "unexpected error string", + slog.F("name", name), + slog.F("error_string", errs), + slog.F("expected_contains", sub), + ) + } +} + +func stringContainsFold(errs, sub string) bool { + errs = strings.ToLower(errs) + sub = strings.ToLower(sub) + + return strings.Contains(errs, sub) + +} diff --git a/sloggers/slogtest/assert/assert_test.go b/sloggers/slogtest/assert/assert_test.go index 6008294..82d9a5d 100644 --- a/sloggers/slogtest/assert/assert_test.go +++ b/sloggers/slogtest/assert/assert_test.go @@ -13,65 +13,79 @@ func TestEqual(t *testing.T) { t.Parallel() tb := &fakeTB{} - assert.Equal(tb, 3, 3, "meow") + assert.Equal(tb, "meow", 3, 3) defer func() { recover() - simpleassert.Equal(t, 1, tb.fatals, "fatals") + simpleassert.Equal(t, "fatals", 1, tb.fatals) }() - assert.Equal(tb, 3, 4, "meow") + assert.Equal(tb, "meow", 3, 4) } func TestEqual_error(t *testing.T) { t.Parallel() tb := &fakeTB{} - assert.Equal(tb, io.EOF, fmt.Errorf("failed: %w", io.EOF), "meow") + assert.Equal(tb, "meow", io.EOF, fmt.Errorf("failed: %w", io.EOF)) defer func() { recover() - simpleassert.Equal(t, 1, tb.fatals, "fatals") + simpleassert.Equal(t, "fatals", 1, tb.fatals) }() - assert.Equal(tb, io.ErrClosedPipe, fmt.Errorf("failed: %w", io.EOF), "meow") + assert.Equal(tb, "meow", io.ErrClosedPipe, fmt.Errorf("failed: %w", io.EOF)) } +func TestErrorContains(t *testing.T) { + t.Parallel() + + tb := &fakeTB{} + assert.ErrorContains(tb, "meow", io.EOF, "eof") + + defer func() { + recover() + simpleassert.Equal(t, "fatals", 1, tb.fatals) + + }() + assert.ErrorContains(tb, "meow", io.ErrClosedPipe, "eof") + +} func TestSuccess(t *testing.T) { t.Parallel() tb := &fakeTB{} - assert.Success(tb, nil, "meow") + assert.Success(tb, "meow", nil) defer func() { recover() - simpleassert.Equal(t, 1, tb.fatals, "fatals") + simpleassert.Equal(t, "fatals", 1, tb.fatals) }() - assert.Success(tb, io.EOF, "meow") + assert.Success(tb, "meow", io.EOF) } func TestTrue(t *testing.T) { t.Parallel() tb := &fakeTB{} - assert.True(tb, true, "meow") + assert.True(tb, "meow", true) defer func() { recover() - simpleassert.Equal(t, 1, tb.fatals, "fatals") + simpleassert.Equal(t, "fatals", 1, tb.fatals) }() - assert.True(tb, false, "meow") + assert.True(tb, "meow", false) } func TestError(t *testing.T) { t.Parallel() tb := &fakeTB{} - assert.Error(tb, io.EOF, "meow") + assert.Error(tb, "meow", io.EOF) defer func() { recover() - simpleassert.Equal(t, 1, tb.fatals, "fatals") + simpleassert.Equal(t, "fatals", 1, tb.fatals) }() - assert.Error(tb, nil, "meow") + assert.Error(tb, "meow", nil) } type fakeTB struct { diff --git a/sloggers/slogtest/t_test.go b/sloggers/slogtest/t_test.go index bd6c586..9169cbe 100644 --- a/sloggers/slogtest/t_test.go +++ b/sloggers/slogtest/t_test.go @@ -17,11 +17,11 @@ func TestStateless(t *testing.T) { slogtest.Info(tb, "hello") slogtest.Error(tb, "hello") - assert.Equal(t, 1, tb.errors, "errors") + assert.Equal(t, "errors", 1, tb.errors) defer func() { recover() - assert.Equal(t, 1, tb.fatals, "fatals") + assert.Equal(t, "fatals", 1, tb.fatals) }() slogtest.Fatal(tb, "hello") @@ -36,11 +36,11 @@ func TestIgnoreErrors(t *testing.T) { })) l.Error(bg, "hello") - assert.Equal(t, 0, tb.errors, "errors") + assert.Equal(t, "errors", 0, tb.errors) defer func() { recover() - assert.Equal(t, 0, tb.fatals, "fatals") + assert.Equal(t, "fatals", 0, tb.fatals) }() l.Fatal(bg, "hello") From bf884bf189940a45175557ebb38cab60c0341369 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Sun, 15 Dec 2019 17:01:28 -0600 Subject: [PATCH 11/17] Use github.com/kylelemons/godebug for assert diffs --- go.mod | 1 + go.sum | 2 ++ internal/assert/assert.go | 5 ++++- internal/entryhuman/entry.go | 13 +++++++++++-- internal/entryhuman/entry_test.go | 4 ++-- sloggers/sloghuman/sloghuman_test.go | 2 +- sloggers/slogtest/assert/assert.go | 5 +++-- 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 74d66ae..9247148 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( cloud.google.com/go v0.43.0 github.com/alecthomas/chroma v0.6.6 github.com/fatih/color v1.7.0 + github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-isatty v0.0.9 // indirect go.opencensus.io v0.22.1 diff --git a/go.sum b/go.sum index 3ecd103..38f1ff5 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= diff --git a/internal/assert/assert.go b/internal/assert/assert.go index 69a92b3..2f8d2eb 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -4,13 +4,16 @@ package assert import ( "reflect" "testing" + + "github.com/kylelemons/godebug/pretty" ) // Equal asserts exp == act. func Equal(t testing.TB, name string, exp, act interface{}) { t.Helper() if !reflect.DeepEqual(exp, act) { - t.Fatalf("unexpected %v: exp: %q but got %q", name, exp, act) + t.Fatalf(`unexpected %v: diff: +%v`, name, pretty.Compare(exp, act)) } } diff --git a/internal/entryhuman/entry.go b/internal/entryhuman/entry.go index fcda4e9..d17f80a 100644 --- a/internal/entryhuman/entry.go +++ b/internal/entryhuman/entry.go @@ -119,11 +119,20 @@ func Fmt(w io.Writer, ent slog.SinkEntry) string { } if multilineVal != "" { - multilineVal = strings.TrimSpace(multilineVal) - multilineKey = c(w, color.FgBlue).Sprintf(`"%v"`, multilineKey) if msg != "..." { ents += " ..." } + + // Proper indentation. + lines := strings.Split(multilineVal, "\n") + for i, line := range lines[1:] { + if line != "" { + lines[i+1] = strings.Repeat(" ", len(multilineKey)+4) + line + } + } + multilineVal = strings.Join(lines, "\n") + + multilineKey = c(w, color.FgBlue).Sprintf(`"%v"`, multilineKey) ents += fmt.Sprintf("\n%v: %v", multilineKey, multilineVal) } diff --git a/internal/entryhuman/entry_test.go b/internal/entryhuman/entry_test.go index c668500..8530bd7 100644 --- a/internal/entryhuman/entry_test.go +++ b/internal/entryhuman/entry_test.go @@ -44,7 +44,7 @@ func TestEntry(t *testing.T) { Level: slog.LevelInfo, }, `0001-01-01 00:00:00.000 [INFO] <.:0> ... "msg": line1 -line2`) + line2`) }) t.Run("multilineField", func(t *testing.T) { @@ -56,7 +56,7 @@ line2`) Fields: slog.M(slog.F("field", "line1\nline2")), }, `0001-01-01 00:00:00.000 [INFO] <.:0> msg ... "field": line1 -line2`) + line2`) }) t.Run("named", func(t *testing.T) { diff --git a/sloggers/sloghuman/sloghuman_test.go b/sloggers/sloghuman/sloghuman_test.go index 5c62637..5c28afb 100644 --- a/sloggers/sloghuman/sloghuman_test.go +++ b/sloggers/sloghuman/sloghuman_test.go @@ -24,5 +24,5 @@ func TestMake(t *testing.T) { 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/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index 1584f54..0989db5 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -11,6 +11,8 @@ import ( "strings" "testing" + "github.com/kylelemons/godebug/pretty" + "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" ) @@ -30,8 +32,7 @@ func Equal(t testing.TB, name string, exp, act interface{}) { if !reflect.DeepEqual(exp, act) { slogtest.Fatal(t, "unexpected value", slog.F("name", name), - slog.F("exp", exp), - slog.F("act", act), + slog.F("diff", pretty.Compare(exp, act)), ) } } From a3fbba1b2665e488a97ab5678ea1b8cd33aba39a Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 16 Dec 2019 11:07:30 -0600 Subject: [PATCH 12/17] Remove Value interface and ForceJSON Realized we can replace ForceJSON now that we only marshal structs as JSON if they have a json struct tag and json.Marshaller can be used in place of Value as well so its not necessary. --- example_forcejson_test.go | 30 -------- example_value_test.go | 32 --------- go.mod | 24 ++++--- go.sum | 102 ++++++++++++++++++++++----- internal/assert/assert.go | 15 +++- map.go | 107 +++++++++++++---------------- map_test.go | 38 +++------- slog.go | 8 --- sloggers/slogtest/assert/assert.go | 29 ++------ 9 files changed, 173 insertions(+), 212 deletions(-) delete mode 100644 example_forcejson_test.go delete mode 100644 example_value_test.go diff --git a/example_forcejson_test.go b/example_forcejson_test.go deleted file mode 100644 index 89e2f06..0000000 --- a/example_forcejson_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package slog_test - -import ( - "context" - "fmt" - "os" - - "cdr.dev/slog" - "cdr.dev/slog/sloggers/sloghuman" -) - -type stringer struct { - X int `json:"x"` -} - -func (s *stringer) String() string { - return fmt.Sprintf("string method: %v", s.X) -} - -func (s *stringer) SlogValue() interface{} { - return slog.ForceJSON(s) -} - -func ExampleForceJSON() { - l := sloghuman.Make(os.Stdout) - - l.Info(context.Background(), "hello", slog.F("stringer", &stringer{X: 3})) - - // 2019-12-06 23:33:40.263 [INFO] hello {"stringer": {"x": 3}} -} diff --git a/example_value_test.go b/example_value_test.go deleted file mode 100644 index e889900..0000000 --- a/example_value_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package slog_test - -import ( - "context" - "os" - - "cdr.dev/slog" - "cdr.dev/slog/sloggers/sloghuman" -) - -type vals struct { - first int - second int -} - -func (s *vals) SlogValue() interface{} { - return slog.M( - slog.F("total", s.first+s.second), - slog.F("first", s.first), - slog.F("second", s.second), - ) -} - -func ExampleValue() { - l := sloghuman.Make(os.Stdout) - l.Info(context.Background(), "hello", slog.F("val", &vals{ - first: 3, - second: 6, - })) - - // 2019-12-07 21:06:14.636 [INFO] hello {"val": {"total": 9, "first": 3, "second": 6}} -} diff --git a/go.mod b/go.mod index 9247148..c965c2f 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,19 @@ module cdr.dev/slog go 1.13 require ( - cloud.google.com/go v0.43.0 - github.com/alecthomas/chroma v0.6.6 + cloud.google.com/go v0.49.0 + github.com/alecthomas/chroma v0.7.0 + github.com/dlclark/regexp2 v1.2.0 // indirect github.com/fatih/color v1.7.0 - github.com/kylelemons/godebug v1.1.0 - github.com/mattn/go-colorable v0.1.2 // indirect - github.com/mattn/go-isatty v0.0.9 // indirect - go.opencensus.io v0.22.1 - golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 - golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect - golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 - google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 + github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect + github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.11 // indirect + go.opencensus.io v0.22.2 + golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect + golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 + google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 + google.golang.org/grpc v1.25.1 // indirect ) diff --git a/go.sum b/go.sum index 38f1ff5..6bd2924 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,17 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ= +cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= @@ -10,8 +19,8 @@ github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtix github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.6.6 h1:AwWMP1sWgMNgEiptNtV/T5GWOLtZFDdrc2ZfWx1ogmg= -github.com/alecthomas/chroma v0.6.6/go.mod h1:zVlgtbRS7BJDrDY9SB238RmpoCBCYFlLmcfZ3durxTk= +github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw= +github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= @@ -19,6 +28,7 @@ github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUS github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= @@ -29,12 +39,19 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -48,10 +65,13 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e h1:4WfjkTUTsO6siF8ghDQQk6t7x/FPsv3w6MXkc47do7Q= +github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -64,16 +84,20 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= @@ -82,25 +106,36 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -108,7 +143,12 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISg golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -120,6 +160,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -142,9 +184,9 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -160,14 +202,25 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -179,14 +232,29 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/assert/assert.go b/internal/assert/assert.go index 2f8d2eb..71ae94f 100644 --- a/internal/assert/assert.go +++ b/internal/assert/assert.go @@ -5,15 +5,24 @@ import ( "reflect" "testing" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) +// Diff returns a diff between exp and act. +func Diff(exp, act interface{}, opts ...cmp.Option) string { + opts = append(opts, cmpopts.EquateErrors(), cmp.Exporter(func(r reflect.Type) bool { + return true + })) + return cmp.Diff(exp, act, opts...) +} + // Equal asserts exp == act. func Equal(t testing.TB, name string, exp, act interface{}) { t.Helper() - if !reflect.DeepEqual(exp, act) { + if diff := Diff(exp, act); diff != "" { t.Fatalf(`unexpected %v: diff: -%v`, name, pretty.Compare(exp, act)) +%v`, name, diff) } } diff --git a/map.go b/map.go index c810d60..c2e7dd6 100644 --- a/map.go +++ b/map.go @@ -13,11 +13,6 @@ import ( // Map represents an ordered map of fields. type Map []Field -// SlogValue implements Value. -func (m Map) SlogValue() interface{} { - return ForceJSON(m) -} - var _ json.Marshaler = Map(nil) // MarshalJSON implements json.Marshaler. @@ -27,23 +22,20 @@ var _ json.Marshaler = Map(nil) // // Every field value is encoded with the following process: // -// 1. slog.Value is checked to allow any type to replace its representation for logging. -// -// 2. json.Marshaller is handled. +// 1. json.Marshaller is handled. // // 2. xerrors.Formatter is handled. // -// 3. Protobufs are encoded with json.Marshal. +// 3. structs that have a field with a json tag are encoded with json.Marshal. // -// 4. error and fmt.Stringer are used if possible. +// 4. error and fmt.Stringer is handled. // // 5. slices and arrays go through the encode function for every element. // -// 6. If the value is a struct without exported fields or a type that -// cannot be encoded with json.Marshal (like channels) then -// fmt.Sprintf("%+v") is used. +// 6. If the value cannot be encoded directly with json.Marshal (like channels) +// then fmt.Sprintf("%+v") is used. // -// 8. json.Marshal(v) is used for all other values. +// 7. json.Marshal(v) is used for all other values. func (m Map) MarshalJSON() ([]byte, error) { b := &bytes.Buffer{} b.WriteByte('{') @@ -62,19 +54,6 @@ func (m Map) MarshalJSON() ([]byte, error) { return b.Bytes(), nil } -// ForceJSON ensures the value is logged via json.Marshal. -// -// Use it when implementing SlogValue to ensure a structured -// representation of a struct if you know it's capable even -// when it implements fmt.Stringer or error. -func ForceJSON(v interface{}) interface{} { - return jsonVal{v: v} -} - -type jsonVal struct { - v interface{} -} - func marshalList(rv reflect.Value) []byte { b := &bytes.Buffer{} b.WriteByte('[') @@ -93,51 +72,57 @@ func marshalList(rv reflect.Value) []byte { func encode(v interface{}) []byte { switch v := v.(type) { - case Value: - return encode(v.SlogValue()) case json.Marshaler: return encodeJSON(v) - case jsonVal: - return encodeJSON(v.v) case xerrors.Formatter: return encode(errorChain(v)) - case interface { - ProtoMessage() - }: - return encode(ForceJSON(v)) + } + + rv := reflect.Indirect(reflect.ValueOf(v)) + if !rv.IsValid() { + return encodeJSON(v) + } + + if rv.Kind() == reflect.Struct { + b, ok := encodeStruct(rv) + if ok { + return b + } + } + + switch v.(type) { case error, fmt.Stringer: return encode(fmt.Sprint(v)) - default: - rv := reflect.Indirect(reflect.ValueOf(v)) - if !rv.IsValid() { - return encodeJSON(v) - } + } - switch rv.Type().Kind() { - case reflect.Slice: - if !rv.IsNil() { - return marshalList(rv) - } - case reflect.Array: + switch rv.Type().Kind() { + case reflect.Slice: + if !rv.IsNil() { return marshalList(rv) - case reflect.Struct: - typ := rv.Type() - for i := 0; i < rv.NumField(); i++ { - // Found an exported field. - if typ.Field(i).PkgPath == "" { - return encodeJSON(v) - } - } - - return encodeJSON(fmt.Sprintf("%+v", v)) - case reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func: - // These types cannot be directly encoded with json.Marshal. - // See https://golang.org/pkg/encoding/json/#Marshal - return encodeJSON(fmt.Sprintf("%+v", v)) } + case reflect.Array: + return marshalList(rv) + case reflect.Struct, reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Func: + // These types cannot be directly encoded with json.Marshal. + // See https://golang.org/pkg/encoding/json/#Marshal + return encodeJSON(fmt.Sprintf("%+v", v)) + } - return encodeJSON(v) + return encodeJSON(v) +} + +func encodeStruct(rv reflect.Value) ([]byte, bool) { + if rv.Kind() == reflect.Struct { + for i := 0; i < rv.NumField(); i++ { + ft := rv.Type().Field(i) + // Found a field with a json tag. + if ft.Tag.Get("json") != "" { + return encodeJSON(rv.Interface()), true + } + } } + + return nil, false } func encodeJSON(v interface{}) []byte { diff --git a/map_test.go b/map_test.go index 3b98d7a..db8e5ea 100644 --- a/map_test.go +++ b/map_test.go @@ -86,18 +86,18 @@ func TestMap(t *testing.T) { mapTestFile := strings.Replace(mapTestFile, "_test", "", 1) test(t, slog.M( - slog.F("meow", slog.ForceJSON(complex(10, 10))), + slog.F("meow", complexJSON(complex(10, 10))), ), `{ "meow": { "error": [ { "msg": "failed to marshal to JSON", "fun": "cdr.dev/slog.encodeJSON", - "loc": "`+mapTestFile+`:147" + "loc": "`+mapTestFile+`:132" }, - "json: unsupported type: complex128" + "json: error calling MarshalJSON for type slog_test.complexJSON: json: unsupported type: complex128" ], - "type": "complex128", + "type": "slog_test.complexJSON", "value": "(10+10i)" } }`) @@ -163,26 +163,6 @@ func TestMap(t *testing.T) { }`) }) - t.Run("forceJSON", func(t *testing.T) { - t.Parallel() - - test(t, slog.M( - slog.F("error", slog.ForceJSON(io.EOF)), - ), `{ - "error": {} - }`) - }) - - t.Run("value", func(t *testing.T) { - t.Parallel() - - test(t, slog.M( - slog.F("error", meow{1}), - ), `{ - "error": "xdxd" - }`) - }) - t.Run("nilSlice", func(t *testing.T) { t.Parallel() @@ -246,10 +226,6 @@ type meow struct { a int } -func (m meow) SlogValue() interface{} { - return "xdxd" -} - func indentJSON(t *testing.T, j string) string { b := &bytes.Buffer{} err := json.Indent(b, []byte(j), "", strings.Repeat(" ", 4)) @@ -263,3 +239,9 @@ func marshalJSON(t *testing.T, m slog.Map) string { assert.Success(t, "marshal map to JSON", err) return indentJSON(t, string(actb)) } + +type complexJSON complex128 + +func (c complexJSON) MarshalJSON() ([]byte, error) { + return json.Marshal(complex128(c)) +} diff --git a/slog.go b/slog.go index d2cf1b3..a291f27 100644 --- a/slog.go +++ b/slog.go @@ -226,14 +226,6 @@ func M(fs ...Field) Map { return fs } -// Value represents a log value. -// -// Implement SlogValue in your own types to override -// the value encoded when logging. -type Value interface { - SlogValue() interface{} -} - // Error is the standard key used for logging a Go error value. func Error(err error) Field { return F("error", err) diff --git a/sloggers/slogtest/assert/assert.go b/sloggers/slogtest/assert/assert.go index 0989db5..e246a2f 100644 --- a/sloggers/slogtest/assert/assert.go +++ b/sloggers/slogtest/assert/assert.go @@ -6,14 +6,13 @@ package assert // import "cdr.dev/slog/sloggers/slogtest/assert" import ( - "errors" - "reflect" "strings" "testing" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" "cdr.dev/slog" + "cdr.dev/slog/internal/assert" "cdr.dev/slog/sloggers/slogtest" ) @@ -22,17 +21,14 @@ import ( // If they are not equal, it will fatal the test with a diff of the // two objects. // -// If act or exp is an error it will be unwrapped. -func Equal(t testing.TB, name string, exp, act interface{}) { +// Errors will be compared with errors.Is. +func Equal(t testing.TB, name string, exp, act interface{}, opts ...cmp.Option) { slog.Helper() - exp = unwrapErr(exp) - act = unwrapErr(act) - - if !reflect.DeepEqual(exp, act) { + if diff := assert.Diff(exp, act, opts...); diff != "" { slogtest.Fatal(t, "unexpected value", slog.F("name", name), - slog.F("diff", pretty.Compare(exp, act)), + slog.F("diff", diff), ) } } @@ -64,19 +60,6 @@ func Error(t testing.TB, name string, err error) { } } -func unwrapErr(v interface{}) interface{} { - err, ok := v.(error) - if !ok { - return v - } - uerr := errors.Unwrap(err) - for uerr != nil { - err = uerr - uerr = errors.Unwrap(uerr) - } - return err -} - // ErrorContains asserts err != nil and err.Error() contains sub. // // The match will be case insensitive. From 56c5fd9ffa13a5d08baf001ce1f89a36c9c5b979 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 16 Dec 2019 11:20:42 -0600 Subject: [PATCH 13/17] Update docs --- README.md | 8 +++----- example_test.go | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 85d77d2..9fd2562 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ log := sloghuman.Make(os.Stdout) log.Info(context.Background(), "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( - slog.F("nested_fields", "wowow"), + slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), )), slog.Error( xerrors.Errorf("wrap1: %w", @@ -52,7 +52,7 @@ log.Info(context.Background(), "my message here", ) ``` -![Example output screenshot](https://i.imgur.com/7MJM0VE.png) +![Example output screenshot](https://i.imgur.com/KGRmQFo.png) ## Why? @@ -97,9 +97,7 @@ Here is a list of reasons how we improved on zap with slog. - zap is hard and confusing to extend. There are too many structures and configuration options. 1. Structured logging of Go structures with `json.Marshal` - - All values will be logged with `json.Marshal` unless they implement `fmt.Stringer` or `error`. - - You can force JSON by using [`slog.ForceJSON`](https://godoc.org/cdr.dev/slog#ForceJSON). - - One may implement [`slog.Value`](https://godoc.org/cdr.dev/slog#Value) to override the representation completely. + - Entire encoding process is documented on [godoc](https://godoc.org/cdr.dev/slog#Map.MarshalJSON). - With zap, We found ourselves often implementing zap's [ObjectMarshaler](https://godoc.org/go.uber.org/zap/zapcore#ObjectMarshaler) to log Go structures. This was verbose and most of the time we ended up only implementing `fmt.Stringer` and using `zap.Stringer` instead. diff --git a/example_test.go b/example_test.go index e32b687..d0123bb 100644 --- a/example_test.go +++ b/example_test.go @@ -6,6 +6,7 @@ import ( "net" "os" "testing" + "time" "go.opencensus.io/trace" "golang.org/x/xerrors" @@ -22,7 +23,7 @@ func Example() { log.Info(context.Background(), "my message here", slog.F("field_name", "something or the other"), slog.F("some_map", slog.M( - slog.F("nested_fields", "wowow"), + slog.F("nested_fields", time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC)), )), slog.Error( xerrors.Errorf("wrap1: %w", @@ -33,7 +34,7 @@ func Example() { ), ) - // 2019-12-09 05:04:53.398 [INFO] my message here {"field_name": "something or the other", "some_map": {"nested_fields": "wowow"}} ... + // 2019-12-09 05:04:53.398 [INFO] my message here {"field_name": "something or the other", "some_map": {"nested_fields": "2000-02-05T04:04:04Z"}} ... // "error": wrap1: // main.main // /Users/nhooyr/src/cdr/scratch/example.go:22 From f7a90d8c4e3665558b8c161a9c69bcb2ec1edfb9 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 16 Dec 2019 11:32:40 -0600 Subject: [PATCH 14/17] Improve docs and add more examples --- example_marshaller_test.go | 34 ++++++++++++++++++++++++++++++++++ example_test.go | 25 ++++++++++++++++++++++--- map.go | 3 +-- map_test.go | 2 +- 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 example_marshaller_test.go diff --git a/example_marshaller_test.go b/example_marshaller_test.go new file mode 100644 index 0000000..e4e6992 --- /dev/null +++ b/example_marshaller_test.go @@ -0,0 +1,34 @@ +package slog_test + +import ( + "context" + "os" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" +) + +type myStruct struct { + foo int + bar int +} + +func (s myStruct) MarshalJSON() ([]byte, error) { + return slog.M( + slog.F("foo", s.foo), + slog.F("bar", s.foo), + ).MarshalJSON() +} + +func Example_marshaller() { + l := sloghuman.Make(os.Stdout) + + l.Info(context.Background(), "wow", + slog.F("myStruct", myStruct{ + foo: 1, + bar: 2, + }), + ) + + // 2019-12-16 17:31:37.120 [INFO] wow {"myStruct": {"foo": 1, "bar": 1}} +} diff --git a/example_test.go b/example_test.go index d0123bb..f3577ac 100644 --- a/example_test.go +++ b/example_test.go @@ -44,6 +44,26 @@ func Example() { // - EOF } +func Example_struct() { + l := sloghuman.Make(os.Stdout) + + type hello struct { + Meow int `json:"meow"` + Bar string `json:"bar"` + M time.Time `json:"m"` + } + + l.Info(context.Background(), "check out my structure", + slog.F("hello", hello{ + Meow: 1, + Bar: "barbar", + M: time.Date(2000, time.February, 5, 4, 4, 4, 0, time.UTC), + }), + ) + + // 2019-12-16 17:31:51.769 [INFO] check out my structure {"hello": {"meow": 1, "bar": "barbar", "m": "2000-02-05T04:04:04Z"}} +} + func Example_testing() { // Provided by the testing package in tests. var t testing.TB @@ -66,17 +86,16 @@ func Example_tracing() { } func Example_multiple() { - ctx := context.Background() l := sloghuman.Make(os.Stdout) f, err := os.OpenFile("stackdriver", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { - l.Fatal(ctx, "failed to open stackdriver log file", slog.Error(err)) + l.Fatal(context.Background(), "failed to open stackdriver log file", slog.Error(err)) } l = slog.Make(l, slogstackdriver.Make(f)) - l.Info(ctx, "log to stdout and stackdriver") + l.Info(context.Background(), "log to stdout and stackdriver") // 2019-12-07 20:59:55.790 [INFO] log to stdout and stackdriver } diff --git a/map.go b/map.go index c2e7dd6..636f4ee 100644 --- a/map.go +++ b/map.go @@ -32,8 +32,7 @@ var _ json.Marshaler = Map(nil) // // 5. slices and arrays go through the encode function for every element. // -// 6. If the value cannot be encoded directly with json.Marshal (like channels) -// then fmt.Sprintf("%+v") is used. +// 6. For values that cannot be encoded with json.Marshal, fmt.Sprintf("%+v") is used. // // 7. json.Marshal(v) is used for all other values. func (m Map) MarshalJSON() ([]byte, error) { diff --git a/map_test.go b/map_test.go index db8e5ea..e15a6ee 100644 --- a/map_test.go +++ b/map_test.go @@ -93,7 +93,7 @@ func TestMap(t *testing.T) { { "msg": "failed to marshal to JSON", "fun": "cdr.dev/slog.encodeJSON", - "loc": "`+mapTestFile+`:132" + "loc": "`+mapTestFile+`:131" }, "json: error calling MarshalJSON for type slog_test.complexJSON: json: unsupported type: complex128" ], From 2b49514fd5bbc6081a1d863ccbc3161d488e419b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 31 Mar 2020 13:25:08 -0500 Subject: [PATCH 15/17] 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 From 6a41f9d85660abd7fa70daad28a4731a59b358d0 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 31 Mar 2020 14:54:42 -0500 Subject: [PATCH 16/17] Revert "Make SinkContext a struct" It's better as an interface so users don't have to call the `Sink` method on the struct during Make. - Move SinkContext into context.go --- context.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index e3f20c1..d44a049 100644 --- a/context.go +++ b/context.go @@ -4,15 +4,21 @@ import "context" type loggerCtxKey = struct{} -// SinkContext is used by slog.Make to compose many loggers together. -type SinkContext struct { +type sinkContext struct { + context.Context + Sink +} + +// SinkContext is a context that implements Sink. +// It may be returned by log creators to allow for composition. +type SinkContext interface { Sink context.Context } func contextWithLogger(ctx context.Context, l logger) SinkContext { ctx = context.WithValue(ctx, loggerCtxKey{}, l) - return SinkContext{ + return &sinkContext{ Context: ctx, Sink: l, } From 0d999259b6559610bb00e2adadc253806c75a9d7 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 31 Mar 2020 15:11:45 -0500 Subject: [PATCH 17/17] Add /v2 to import path --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c965c2f..bfca1fb 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module cdr.dev/slog +module cdr.dev/slog/v2 go 1.13