diff -Nru golang-github-rs-zerolog-1.20.0/array.go golang-github-rs-zerolog-1.26.1/array.go --- golang-github-rs-zerolog-1.20.0/array.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/array.go 2021-12-15 23:37:18.000000000 +0000 @@ -231,3 +231,10 @@ a.buf = enc.AppendMACAddr(enc.AppendArrayDelim(a.buf), ha) return a } + +// Dict adds the dict Event to the array +func (a *Array) Dict(dict *Event) *Array { + dict.buf = enc.AppendEndMarker(dict.buf) + a.buf = append(enc.AppendArrayDelim(a.buf), dict.buf...) + return a +} diff -Nru golang-github-rs-zerolog-1.20.0/array_test.go golang-github-rs-zerolog-1.26.1/array_test.go --- golang-github-rs-zerolog-1.20.0/array_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/array_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -27,8 +27,12 @@ RawJSON([]byte(`{"some":"json"}`)). Time(time.Time{}). IPAddr(net.IP{192, 168, 0, 10}). - Dur(0) - want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},"0001-01-01T00:00:00Z","192.168.0.10",0]` + Dur(0). + Dict(Dict(). + Str("bar", "baz"). + Int("n", 1), + ) + want := `[true,1,2,3,4,5,6,7,8,9,10,11.98122,12.987654321,"a","b","1f",{"some":"json"},"0001-01-01T00:00:00Z","192.168.0.10",0,{"bar":"baz","n":1}]` if got := decodeObjectToStr(a.write([]byte{})); got != want { t.Errorf("Array.write()\ngot: %s\nwant: %s", got, want) } diff -Nru golang-github-rs-zerolog-1.20.0/benchmark_test.go golang-github-rs-zerolog-1.26.1/benchmark_test.go --- golang-github-rs-zerolog-1.20.0/benchmark_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/benchmark_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -148,16 +148,16 @@ {"a", "a", 0}, } objects := []obj{ - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, } errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")} types := map[string]func(e *Event) *Event{ @@ -272,16 +272,16 @@ {"a", "a", 0}, } objects := []obj{ - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, - obj{"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, + {"a", "a", 0}, } errs := []error{errors.New("a"), errors.New("b"), errors.New("c"), errors.New("d"), errors.New("e")} types := map[string]func(c Context) Context{ diff -Nru golang-github-rs-zerolog-1.20.0/binary_test.go golang-github-rs-zerolog-1.26.1/binary_test.go --- golang-github-rs-zerolog-1.20.0/binary_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/binary_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -364,6 +364,42 @@ // Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"} } +func ExampleEvent_Fields_map() { + fields := map[string]interface{}{ + "bar": "baz", + "n": 1, + } + + dst := bytes.Buffer{} + log := New(&dst) + + log.Log(). + Str("foo", "bar"). + Fields(fields). + Msg("hello world") + + fmt.Println(decodeIfBinaryToString(dst.Bytes())) + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} + +func ExampleEvent_Fields_slice() { + fields := []interface{}{ + "bar", "baz", + "n", 1, + } + + dst := bytes.Buffer{} + log := New(&dst) + + log.Log(). + Str("foo", "bar"). + Fields(fields). + Msg("hello world") + + fmt.Println(decodeIfBinaryToString(dst.Bytes())) + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} + func ExampleContext_Dict() { dst := bytes.Buffer{} log := New(&dst).With(). @@ -510,3 +546,39 @@ fmt.Println(decodeIfBinaryToString(dst.Bytes())) // Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"} } + +func ExampleContext_Fields_map() { + fields := map[string]interface{}{ + "bar": "baz", + "n": 1, + } + + dst := bytes.Buffer{} + log := New(&dst).With(). + Str("foo", "bar"). + Fields(fields). + Logger() + + log.Log().Msg("hello world") + + fmt.Println(decodeIfBinaryToString(dst.Bytes())) + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} + +func ExampleContext_Fields_slice() { + fields := []interface{}{ + "bar", "baz", + "n", 1, + } + + dst := bytes.Buffer{} + log := New(&dst).With(). + Str("foo", "bar"). + Fields(fields). + Logger() + + log.Log().Msg("hello world") + + fmt.Println(decodeIfBinaryToString(dst.Bytes())) + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} diff -Nru golang-github-rs-zerolog-1.20.0/console.go golang-github-rs-zerolog-1.26.1/console.go --- golang-github-rs-zerolog-1.20.0/console.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/console.go 2021-12-15 23:37:18.000000000 +0000 @@ -6,6 +6,7 @@ "fmt" "io" "os" + "path/filepath" "sort" "strconv" "strings" @@ -57,6 +58,9 @@ // PartsOrder defines the order of parts in output. PartsOrder []string + // PartsExclude defines parts to not display in output. + PartsExclude []string + FormatTimestamp Formatter FormatLevel Formatter FormatCaller Formatter @@ -208,6 +212,14 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) { var f Formatter + if w.PartsExclude != nil && len(w.PartsExclude) > 0 { + for _, exclude := range w.PartsExclude { + if exclude == p { + return + } + } + } + switch p { case LevelFieldName: if w.FormatLevel == nil { @@ -321,19 +333,19 @@ var l string if ll, ok := i.(string); ok { switch ll { - case "trace": + case LevelTraceValue: l = colorize("TRC", colorMagenta, noColor) - case "debug": + case LevelDebugValue: l = colorize("DBG", colorYellow, noColor) - case "info": + case LevelInfoValue: l = colorize("INF", colorGreen, noColor) - case "warn": + case LevelWarnValue: l = colorize("WRN", colorRed, noColor) - case "error": + case LevelErrorValue: l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor) - case "fatal": + case LevelFatalValue: l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor) - case "panic": + case LevelPanicValue: l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor) default: l = colorize("???", colorBold, noColor) @@ -356,10 +368,10 @@ c = cc } if len(c) > 0 { - cwd, err := os.Getwd() - if err == nil { - c = strings.TrimPrefix(c, cwd) - c = strings.TrimPrefix(c, "/") + if cwd, err := os.Getwd(); err == nil { + if rel, err := filepath.Rel(cwd, c); err == nil { + c = rel + } } c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor) } @@ -386,7 +398,7 @@ func consoleDefaultFormatErrFieldName(noColor bool) Formatter { return func(i interface{}) string { - return colorize(fmt.Sprintf("%s=", i), colorRed, noColor) + return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor) } } diff -Nru golang-github-rs-zerolog-1.20.0/console_test.go golang-github-rs-zerolog-1.26.1/console_test.go --- golang-github-rs-zerolog-1.20.0/console_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/console_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -336,6 +336,24 @@ t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) } }) + + t.Run("Sets PartsExclude", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsExclude: []string{"time"}} + + d := time.Unix(0, 0).UTC().Format(time.RFC3339) + evt := `{"time": "` + d + `", "level": "info", "message": "Foobar"}` + _, err := w.Write([]byte(evt)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := "INF Foobar\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) } func BenchmarkConsoleWriter(b *testing.B) { diff -Nru golang-github-rs-zerolog-1.20.0/context.go golang-github-rs-zerolog-1.26.1/context.go --- golang-github-rs-zerolog-1.20.0/context.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/context.go 2021-12-15 23:37:18.000000000 +0000 @@ -18,8 +18,10 @@ return c.l } -// Fields is a helper function to use a map to set fields using type assertion. -func (c Context) Fields(fields map[string]interface{}) Context { +// Fields is a helper function to use a map or slice to set fields using type assertion. +// Only map[string]interface{} and []interface{} are accepted. []interface{} must +// alternate string keys and arbitrary values, and extraneous ones are ignored. +func (c Context) Fields(fields interface{}) Context { c.l.context = appendFields(c.l.context, fields) return c } @@ -406,17 +408,9 @@ return c } -type stackTraceHook struct{} - -func (sh stackTraceHook) Run(e *Event, level Level, msg string) { - e.Stack() -} - -var sh = stackTraceHook{} - // Stack enables stack trace printing for the error passed to Err(). func (c Context) Stack() Context { - c.l = c.l.Hook(sh) + c.l.stack = true return c } diff -Nru golang-github-rs-zerolog-1.20.0/ctx.go golang-github-rs-zerolog-1.26.1/ctx.go --- golang-github-rs-zerolog-1.20.0/ctx.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/ctx.go 2021-12-15 23:37:18.000000000 +0000 @@ -39,10 +39,13 @@ } // Ctx returns the Logger associated with the ctx. If no logger -// is associated, a disabled logger is returned. +// is associated, DefaultContextLogger is returned, unless DefaultContextLogger +// is nil, in which case a disabled logger is returned. func Ctx(ctx context.Context) *Logger { if l, ok := ctx.Value(ctxKey{}).(*Logger); ok { return l + } else if l = DefaultContextLogger; l != nil { + return l } return disabledLogger } diff -Nru golang-github-rs-zerolog-1.20.0/ctx_test.go golang-github-rs-zerolog-1.26.1/ctx_test.go --- golang-github-rs-zerolog-1.20.0/ctx_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/ctx_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -27,6 +27,13 @@ if log2 != disabledLogger { t.Error("Ctx did not return the expected logger") } + + DefaultContextLogger = &log + t.Cleanup(func() { DefaultContextLogger = nil }) + log2 = Ctx(context.Background()) + if log2 != &log { + t.Error("Ctx did not return the expected logger") + } } func TestCtxDisabled(t *testing.T) { diff -Nru golang-github-rs-zerolog-1.20.0/debian/changelog golang-github-rs-zerolog-1.26.1/debian/changelog --- golang-github-rs-zerolog-1.20.0/debian/changelog 2020-11-25 19:00:53.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/debian/changelog 2022-01-07 00:39:35.000000000 +0000 @@ -1,3 +1,13 @@ +golang-github-rs-zerolog (1.26.1-1) unstable; urgency=medium + + * New upstream release. + * Bump debhelper from old 12 to 13. + * Update standards version to 4.6.0, no changes needed. + * reverse dependencies successfully built with ratt: + - golang-github-jackc-pgx + + -- Thorsten Alteholz Fri, 07 Jan 2022 01:39:35 +0100 + golang-github-rs-zerolog (1.20.0-1) unstable; urgency=low [ Debian Janitor ] diff -Nru golang-github-rs-zerolog-1.20.0/debian/control golang-github-rs-zerolog-1.26.1/debian/control --- golang-github-rs-zerolog-1.20.0/debian/control 2020-11-25 19:00:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/debian/control 2022-01-07 00:39:35.000000000 +0000 @@ -3,7 +3,7 @@ Priority: optional Maintainer: Debian Go Packaging Team Uploaders: Thorsten Alteholz -Build-Depends: debhelper-compat (= 12), +Build-Depends: debhelper-compat (= 13), dh-golang, golang-any, golang-github-coreos-go-systemd-dev, @@ -11,7 +11,7 @@ golang-github-rs-xid-dev, golang-github-zenazn-goji-dev, golang-golang-x-tools-dev -Standards-Version: 4.5.0 +Standards-Version: 4.6.0 Homepage: https://github.com/rs/zerolog Vcs-Browser: https://salsa.debian.org/go-team/packages/golang-github-rs-zerolog Vcs-Git: https://salsa.debian.org/go-team/packages/golang-github-rs-zerolog.git diff -Nru golang-github-rs-zerolog-1.20.0/debian/gitlab-ci.yml golang-github-rs-zerolog-1.26.1/debian/gitlab-ci.yml --- golang-github-rs-zerolog-1.20.0/debian/gitlab-ci.yml 2020-11-25 19:00:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/debian/gitlab-ci.yml 2022-01-07 00:39:32.000000000 +0000 @@ -1,4 +1,6 @@ +# auto-generated, DO NOT MODIFY. +# The authoritative copy of this file lives at: +# https://salsa.debian.org/go-team/infra/pkg-go-tools/blob/master/config/gitlabciyml.go --- include: - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml + - https://salsa.debian.org/go-team/infra/pkg-go-tools/-/raw/master/pipeline/test-archive.yml diff -Nru golang-github-rs-zerolog-1.20.0/encoder_cbor.go golang-github-rs-zerolog-1.26.1/encoder_cbor.go --- golang-github-rs-zerolog-1.20.0/encoder_cbor.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/encoder_cbor.go 2021-12-15 23:37:18.000000000 +0000 @@ -14,6 +14,13 @@ enc = cbor.Encoder{} ) +func init() { + // using closure to reflect the changes at runtime. + cbor.JSONMarshalFunc = func(v interface{}) ([]byte, error) { + return InterfaceMarshalFunc(v) + } +} + func appendJSON(dst []byte, j []byte) []byte { return cbor.AppendEmbeddedJSON(dst, j) } diff -Nru golang-github-rs-zerolog-1.20.0/encoder_json.go golang-github-rs-zerolog-1.26.1/encoder_json.go --- golang-github-rs-zerolog-1.20.0/encoder_json.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/encoder_json.go 2021-12-15 23:37:18.000000000 +0000 @@ -15,6 +15,13 @@ enc = json.Encoder{} ) +func init() { + // using closure to reflect the changes at runtime. + json.JSONMarshalFunc = func(v interface{}) ([]byte, error) { + return InterfaceMarshalFunc(v) + } +} + func appendJSON(dst []byte, j []byte) []byte { return append(dst, j...) } diff -Nru golang-github-rs-zerolog-1.20.0/event.go golang-github-rs-zerolog-1.26.1/event.go --- golang-github-rs-zerolog-1.20.0/event.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/event.go 2021-12-15 23:37:18.000000000 +0000 @@ -20,12 +20,13 @@ // Event represents a log event. It is instanced by one of the level method of // Logger and finalized by the Msg or Msgf method. type Event struct { - buf []byte - w LevelWriter - level Level - done func(msg string) - stack bool // enable error stack trace - ch []Hook // hooks from context + buf []byte + w LevelWriter + level Level + done func(msg string) + stack bool // enable error stack trace + ch []Hook // hooks from context + skipFrame int // The number of additional frames to skip when printing the caller. } func putEvent(e *Event) { @@ -62,6 +63,7 @@ e.w = w e.level = level e.stack = false + e.skipFrame = 0 return e } @@ -146,8 +148,10 @@ } } -// Fields is a helper function to use a map to set fields using type assertion. -func (e *Event) Fields(fields map[string]interface{}) *Event { +// Fields is a helper function to use a map or slice to set fields using type assertion. +// Only map[string]interface{} and []interface{} are accepted. []interface{} must +// alternate string keys and arbitrary values, and extraneous ones are ignored. +func (e *Event) Fields(fields interface{}) *Event { if e == nil { return e } @@ -205,15 +209,32 @@ return e } e.buf = enc.AppendKey(e.buf, key) + if obj == nil { + e.buf = enc.AppendNil(e.buf) + + return e + } + e.appendObject(obj) return e } +// Func allows an anonymous func to run only if the event is enabled. +func (e *Event) Func(f func(e *Event)) *Event { + if e != nil && e.Enabled() { + f(e) + } + return e +} + // EmbedObject marshals an object that implement the LogObjectMarshaler interface. func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event { if e == nil { return e } + if obj == nil { + return e + } obj.MarshalZerologObject(e) return e } @@ -236,18 +257,24 @@ return e } -// Stringer adds the field key with val.String() (or null if val is nil) to the *Event context. +// Stringer adds the field key with val.String() (or null if val is nil) +// to the *Event context. func (e *Event) Stringer(key string, val fmt.Stringer) *Event { if e == nil { return e } + e.buf = enc.AppendStringer(enc.AppendKey(e.buf, key), val) + return e +} - if val != nil { - e.buf = enc.AppendString(enc.AppendKey(e.buf, key), val.String()) +// Stringers adds the field key with vals where each individual val +// is used as val.String() (or null if val is empty) to the *Event +// context. +func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event { + if e == nil { return e } - - e.buf = enc.AppendInterface(enc.AppendKey(e.buf, key), nil) + e.buf = enc.AppendStringers(enc.AppendKey(e.buf, key), vals) return e } @@ -685,6 +712,16 @@ return e } +// CallerSkipFrame instructs any future Caller calls to skip the specified number of frames. +// This includes those added via hooks from the context. +func (e *Event) CallerSkipFrame(skip int) *Event { + if e == nil { + return e + } + e.skipFrame += skip + return e +} + // Caller adds the file:line of the caller with the zerolog.CallerFieldName key. // The argument skip is the number of stack frames to ascend // Skip If not passed, use the global variable CallerSkipFrameCount @@ -700,7 +737,7 @@ if e == nil { return e } - _, file, line, ok := runtime.Caller(skip) + _, file, line, ok := runtime.Caller(skip + e.skipFrame) if !ok { return e } diff -Nru golang-github-rs-zerolog-1.20.0/event_test.go golang-github-rs-zerolog-1.26.1/event_test.go --- golang-github-rs-zerolog-1.20.0/event_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/event_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -37,3 +37,29 @@ }) } } + +func TestEvent_ObjectWithNil(t *testing.T) { + var buf bytes.Buffer + e := newEvent(levelWriterAdapter{&buf}, DebugLevel) + _ = e.Object("obj", nil) + _ = e.write() + + want := `{"obj":null}` + got := strings.TrimSpace(buf.String()) + if got != want { + t.Errorf("Event.Object() = %q, want %q", got, want) + } +} + +func TestEvent_EmbedObjectWithNil(t *testing.T) { + var buf bytes.Buffer + e := newEvent(levelWriterAdapter{&buf}, DebugLevel) + _ = e.EmbedObject(nil) + _ = e.write() + + want := "{}" + got := strings.TrimSpace(buf.String()) + if got != want { + t.Errorf("Event.EmbedObject() = %q, want %q", got, want) + } +} diff -Nru golang-github-rs-zerolog-1.20.0/fields.go golang-github-rs-zerolog-1.26.1/fields.go --- golang-github-rs-zerolog-1.20.0/fields.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/fields.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,6 +1,7 @@ package zerolog import ( + "encoding/json" "net" "sort" "time" @@ -11,15 +12,36 @@ return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0 } -func appendFields(dst []byte, fields map[string]interface{}) []byte { - keys := make([]string, 0, len(fields)) - for key := range fields { - keys = append(keys, key) +func appendFields(dst []byte, fields interface{}) []byte { + switch fields := fields.(type) { + case []interface{}: + if n := len(fields); n&0x1 == 1 { // odd number + fields = fields[:n-1] + } + dst = appendFieldList(dst, fields) + case map[string]interface{}: + keys := make([]string, 0, len(fields)) + for key := range fields { + keys = append(keys, key) + } + sort.Strings(keys) + kv := make([]interface{}, 2) + for _, key := range keys { + kv[0], kv[1] = key, fields[key] + dst = appendFieldList(dst, kv) + } } - sort.Strings(keys) - for _, key := range keys { - dst = enc.AppendKey(dst, key) - val := fields[key] + return dst +} + +func appendFieldList(dst []byte, kvList []interface{}) []byte { + for i, n := 0, len(kvList); i < n; i += 2 { + key, val := kvList[i], kvList[i+1] + if key, ok := key.(string); ok { + dst = enc.AppendKey(dst, key) + } else { + continue + } if val, ok := val.(LogObjectMarshaler); ok { e := newEvent(nil, 0) e.buf = e.buf[:0] @@ -245,6 +267,8 @@ dst = enc.AppendIPPrefix(dst, val) case net.HardwareAddr: dst = enc.AppendMACAddr(dst, val) + case json.RawMessage: + dst = appendJSON(dst, val) default: dst = enc.AppendInterface(dst, val) } diff -Nru golang-github-rs-zerolog-1.20.0/.github/dependabot.yml golang-github-rs-zerolog-1.26.1/.github/dependabot.yml --- golang-github-rs-zerolog-1.20.0/.github/dependabot.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/.github/dependabot.yml 2021-12-15 23:37:18.000000000 +0000 @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly diff -Nru golang-github-rs-zerolog-1.20.0/.github/workflows/test.yml golang-github-rs-zerolog-1.26.1/.github/workflows/test.yml --- golang-github-rs-zerolog-1.20.0/.github/workflows/test.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/.github/workflows/test.yml 2021-12-15 23:37:18.000000000 +0000 @@ -0,0 +1,27 @@ +on: [push, pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.15.x, 1.16.x] + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Test + run: go test -race -bench . -benchmem ./... + - name: Test CBOR + run: go test -tags binary_log ./... + diff -Nru golang-github-rs-zerolog-1.20.0/globals.go golang-github-rs-zerolog-1.26.1/globals.go --- golang-github-rs-zerolog-1.20.0/globals.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/globals.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,6 +1,7 @@ package zerolog import ( + "encoding/json" "strconv" "sync/atomic" "time" @@ -27,7 +28,22 @@ // LevelFieldName is the field name used for the level field. LevelFieldName = "level" - // LevelFieldMarshalFunc allows customization of global level field marshaling + // LevelTraceValue is the value used for the trace level field. + LevelTraceValue = "trace" + // LevelDebugValue is the value used for the debug level field. + LevelDebugValue = "debug" + // LevelInfoValue is the value used for the info level field. + LevelInfoValue = "info" + // LevelWarnValue is the value used for the warn level field. + LevelWarnValue = "warn" + // LevelErrorValue is the value used for the error level field. + LevelErrorValue = "error" + // LevelFatalValue is the value used for the fatal level field. + LevelFatalValue = "fatal" + // LevelPanicValue is the value used for the panic level field. + LevelPanicValue = "panic" + + // LevelFieldMarshalFunc allows customization of global level field marshaling. LevelFieldMarshalFunc = func(l Level) string { return l.String() } @@ -60,6 +76,10 @@ return err } + // InterfaceMarshalFunc allows customization of interface marshaling. + // Default: "encoding/json.Marshal" + InterfaceMarshalFunc = json.Marshal + // TimeFieldFormat defines the time format of the Time field type. If set to // TimeFormatUnix, TimeFormatUnixMs or TimeFormatUnixMicro, the time is formatted as an UNIX // timestamp as integer. @@ -80,6 +100,10 @@ // output. If not set, an error is printed on the stderr. This handler must // be thread safe and non-blocking. ErrorHandler func(err error) + + // DefaultContextLogger is returned from Ctx() if there is no logger associated + // with the context. + DefaultContextLogger *Logger ) var ( diff -Nru golang-github-rs-zerolog-1.20.0/go.mod golang-github-rs-zerolog-1.26.1/go.mod --- golang-github-rs-zerolog-1.20.0/go.mod 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/go.mod 2021-12-15 23:37:18.000000000 +0000 @@ -1,8 +1,11 @@ module github.com/rs/zerolog +go 1.15 + require ( - github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e - github.com/pkg/errors v0.8.1 - github.com/rs/xid v1.2.1 - golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 + github.com/coreos/go-systemd/v22 v22.3.2 + github.com/pkg/errors v0.9.1 + github.com/rs/xid v1.3.0 + golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e // indirect + golang.org/x/tools v0.1.7 ) diff -Nru golang-github-rs-zerolog-1.20.0/go.sum golang-github-rs-zerolog-1.26.1/go.sum --- golang-github-rs-zerolog-1.20.0/go.sum 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/go.sum 2021-12-15 23:37:18.000000000 +0000 @@ -1,14 +1,37 @@ -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -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/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff -Nru golang-github-rs-zerolog-1.20.0/hlog/hlog.go golang-github-rs-zerolog-1.26.1/hlog/hlog.go --- golang-github-rs-zerolog-1.20.0/hlog/hlog.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/hlog/hlog.go 2021-12-15 23:37:18.000000000 +0000 @@ -3,7 +3,6 @@ import ( "context" - "net" "net/http" "time" @@ -79,10 +78,10 @@ func RemoteAddrHandler(fieldKey string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + if r.RemoteAddr != "" { log := zerolog.Ctx(r.Context()) log.UpdateContext(func(c zerolog.Context) zerolog.Context { - return c.Str(fieldKey, host) + return c.Str(fieldKey, r.RemoteAddr) }) } next.ServeHTTP(w, r) @@ -138,6 +137,11 @@ return } +// CtxWithID adds the given xid.ID to the context +func CtxWithID(ctx context.Context, id xid.ID) context.Context { + return context.WithValue(ctx, idKey{}, id) +} + // RequestIDHandler returns a handler setting a unique id to the request which can // be gathered using IDFromRequest(req). This generated id is added as a field to the // logger using the passed fieldKey as field name. The id is also added as a response @@ -154,7 +158,7 @@ id, ok := IDFromRequest(r) if !ok { id = xid.New() - ctx = context.WithValue(ctx, idKey{}, id) + ctx = CtxWithID(ctx, id) r = r.WithContext(ctx) } if fieldKey != "" { diff -Nru golang-github-rs-zerolog-1.20.0/hlog/hlog_test.go golang-github-rs-zerolog-1.26.1/hlog/hlog_test.go --- golang-github-rs-zerolog-1.20.0/hlog/hlog_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/hlog/hlog_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -4,16 +4,16 @@ import ( "bytes" + "context" "fmt" "io/ioutil" "net/http" + "net/http/httptest" "net/url" - "testing" - "reflect" + "testing" - "net/http/httptest" - + "github.com/rs/xid" "github.com/rs/zerolog" "github.com/rs/zerolog/internal/cbor" ) @@ -100,7 +100,7 @@ })) h = NewHandler(zerolog.New(out))(h) h.ServeHTTP(nil, r) - if want, got := `{"ip":"1.2.3.4"}`+"\n", decodeIfBinary(out); want != got { + if want, got := `{"ip":"1.2.3.4:1234"}`+"\n", decodeIfBinary(out); want != got { t.Errorf("Invalid log output, got: %s, want: %s", got, want) } } @@ -116,7 +116,7 @@ })) h = NewHandler(zerolog.New(out))(h) h.ServeHTTP(nil, r) - if want, got := `{"ip":"2001:db8:a0b:12f0::1"}`+"\n", decodeIfBinary(out); want != got { + if want, got := `{"ip":"[2001:db8:a0b:12f0::1]:1234"}`+"\n", decodeIfBinary(out); want != got { t.Errorf("Invalid log output, got: %s, want: %s", got, want) } } @@ -262,3 +262,16 @@ } }) } + +func TestCtxWithID(t *testing.T) { + ctx := context.Background() + + id, _ := xid.FromString(`c0umremcie6smuu506pg`) + + want := context.Background() + want = context.WithValue(want, idKey{}, id) + + if got := CtxWithID(ctx, id); !reflect.DeepEqual(got, want) { + t.Errorf("CtxWithID() = %v, want %v", got, want) + } +} diff -Nru golang-github-rs-zerolog-1.20.0/hlog/internal/mutil/writer_proxy.go golang-github-rs-zerolog-1.26.1/hlog/internal/mutil/writer_proxy.go --- golang-github-rs-zerolog-1.20.0/hlog/internal/mutil/writer_proxy.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/hlog/internal/mutil/writer_proxy.go 2021-12-15 23:37:18.000000000 +0000 @@ -124,11 +124,16 @@ func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) { if f.basicWriter.tee != nil { - return io.Copy(&f.basicWriter, r) + n, err := io.Copy(&f.basicWriter, r) + f.bytes += int(n) + return n, err } rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) f.basicWriter.maybeWriteHeader() - return rf.ReadFrom(r) + + n, err := rf.ReadFrom(r) + f.bytes += int(n) + return n, err } type flushWriter struct { diff -Nru golang-github-rs-zerolog-1.20.0/internal/cbor/base.go golang-github-rs-zerolog-1.26.1/internal/cbor/base.go --- golang-github-rs-zerolog-1.20.0/internal/cbor/base.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/internal/cbor/base.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,5 +1,13 @@ package cbor +// JSONMarshalFunc is used to marshal interface to JSON encoded byte slice. +// Making it package level instead of embedded in Encoder brings +// some extra efforts at importing, but avoids value copy when the functions +// of Encoder being invoked. +// DO REMEMBER to set this variable at importing, or +// you might get a nil pointer dereference panic at runtime. +var JSONMarshalFunc func(v interface{}) ([]byte, error) + type Encoder struct{} // AppendKey adds a key (string) to the binary encoded log message @@ -8,4 +16,4 @@ dst = e.AppendBeginMarker(dst) } return e.AppendString(dst, key) -} \ No newline at end of file +} diff -Nru golang-github-rs-zerolog-1.20.0/internal/cbor/string.go golang-github-rs-zerolog-1.26.1/internal/cbor/string.go --- golang-github-rs-zerolog-1.20.0/internal/cbor/string.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/internal/cbor/string.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,5 +1,7 @@ package cbor +import "fmt" + // AppendStrings encodes and adds an array of strings to the dst byte array. func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { major := majorTypeArray @@ -30,6 +32,31 @@ return append(dst, s...) } +// AppendStringers encodes and adds an array of Stringer values +// to the dst byte array. +func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte { + if len(vals) == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + dst = e.AppendArrayStart(dst) + dst = e.AppendStringer(dst, vals[0]) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = e.AppendStringer(dst, val) + } + } + return e.AppendArrayEnd(dst) +} + +// AppendStringer encodes and adds the Stringer value to the dst +// byte array. +func (e Encoder) AppendStringer(dst []byte, val fmt.Stringer) []byte { + if val == nil { + return e.AppendNil(dst) + } + return e.AppendString(dst, val.String()) +} + // AppendBytes encodes and adds an array of bytes to the dst byte array. func (Encoder) AppendBytes(dst, s []byte) []byte { major := majorTypeByteString diff -Nru golang-github-rs-zerolog-1.20.0/internal/cbor/types.go golang-github-rs-zerolog-1.26.1/internal/cbor/types.go --- golang-github-rs-zerolog-1.20.0/internal/cbor/types.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/internal/cbor/types.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,7 +1,6 @@ package cbor import ( - "encoding/json" "fmt" "math" "net" @@ -432,7 +431,7 @@ // AppendInterface takes an arbitrary object and converts it to JSON and embeds it dst. func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { - marshaled, err := json.Marshal(i) + marshaled, err := JSONMarshalFunc(i) if err != nil { return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) } diff -Nru golang-github-rs-zerolog-1.20.0/internal/json/base.go golang-github-rs-zerolog-1.26.1/internal/json/base.go --- golang-github-rs-zerolog-1.20.0/internal/json/base.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/internal/json/base.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,5 +1,13 @@ package json +// JSONMarshalFunc is used to marshal interface to JSON encoded byte slice. +// Making it package level instead of embedded in Encoder brings +// some extra efforts at importing, but avoids value copy when the functions +// of Encoder being invoked. +// DO REMEMBER to set this variable at importing, or +// you might get a nil pointer dereference panic at runtime. +var JSONMarshalFunc func(v interface{}) ([]byte, error) + type Encoder struct{} // AppendKey appends a new key to the output JSON. diff -Nru golang-github-rs-zerolog-1.20.0/internal/json/string.go golang-github-rs-zerolog-1.26.1/internal/json/string.go --- golang-github-rs-zerolog-1.20.0/internal/json/string.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/internal/json/string.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,6 +1,9 @@ package json -import "unicode/utf8" +import ( + "fmt" + "unicode/utf8" +) const hex = "0123456789abcdef" @@ -60,7 +63,32 @@ return append(dst, '"') } -// appendStringComplex is used by appendString to take over an in +// AppendStringers encodes the provided Stringer list to json and +// appends the encoded Stringer list to the input byte slice. +func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = e.AppendStringer(dst, vals[0]) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = e.AppendStringer(append(dst, ','), val) + } + } + return append(dst, ']') +} + +// AppendStringer encodes the input Stringer to json and appends the +// encoded Stringer value to the input byte slice. +func (e Encoder) AppendStringer(dst []byte, val fmt.Stringer) []byte { + if val == nil { + return e.AppendInterface(dst, nil) + } + return e.AppendString(dst, val.String()) +} + +//// appendStringComplex is used by appendString to take over an in // progress JSON string encoding that encountered a character that needs // to be encoded. func appendStringComplex(dst []byte, s string, i int) []byte { diff -Nru golang-github-rs-zerolog-1.20.0/internal/json/types.go golang-github-rs-zerolog-1.26.1/internal/json/types.go --- golang-github-rs-zerolog-1.20.0/internal/json/types.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/internal/json/types.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,7 +1,6 @@ package json import ( - "encoding/json" "fmt" "math" "net" @@ -350,7 +349,7 @@ return append(dst, '[', ']') } dst = append(dst, '[') - dst = appendFloat(dst, vals[0], 32) + dst = appendFloat(dst, vals[0], 64) if len(vals) > 1 { for _, val := range vals[1:] { dst = appendFloat(append(dst, ','), val, 64) @@ -363,7 +362,7 @@ // AppendInterface marshals the input interface to a string and // appends the encoded string to the input byte slice. func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { - marshaled, err := json.Marshal(i) + marshaled, err := JSONMarshalFunc(i) if err != nil { return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) } diff -Nru golang-github-rs-zerolog-1.20.0/journald/journald.go golang-github-rs-zerolog-1.26.1/journald/journald.go --- golang-github-rs-zerolog-1.20.0/journald/journald.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/journald/journald.go 2021-12-15 23:37:18.000000000 +0000 @@ -23,7 +23,7 @@ "io" "strings" - "github.com/coreos/go-systemd/journal" + "github.com/coreos/go-systemd/v22/journal" "github.com/rs/zerolog" "github.com/rs/zerolog/internal/cbor" ) diff -Nru golang-github-rs-zerolog-1.20.0/log/log.go golang-github-rs-zerolog-1.26.1/log/log.go --- golang-github-rs-zerolog-1.20.0/log/log.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/log/log.go 2021-12-15 23:37:18.000000000 +0000 @@ -3,6 +3,7 @@ import ( "context" + "fmt" "io" "os" @@ -114,13 +115,13 @@ // Print sends a log event using debug level and no extra field. // Arguments are handled in the manner of fmt.Print. func Print(v ...interface{}) { - Logger.Print(v...) + Logger.Debug().CallerSkipFrame(1).Msg(fmt.Sprint(v...)) } // Printf sends a log event using debug level and no extra field. // Arguments are handled in the manner of fmt.Printf. func Printf(format string, v ...interface{}) { - Logger.Printf(format, v...) + Logger.Debug().CallerSkipFrame(1).Msgf(format, v...) } // Ctx returns the Logger associated with the ctx. If no logger diff -Nru golang-github-rs-zerolog-1.20.0/log_example_test.go golang-github-rs-zerolog-1.26.1/log_example_test.go --- golang-github-rs-zerolog-1.20.0/log_example_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/log_example_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -238,11 +238,15 @@ Str("foo", "bar"). Array("array", zerolog.Arr(). Str("baz"). - Int(1), + Int(1). + Dict(zerolog.Dict(). + Str("bar", "baz"). + Int("n", 1), + ), ). Msg("hello world") - // Output: {"foo":"bar","array":["baz",1],"message":"hello world"} + // Output: {"foo":"bar","array":["baz",1,{"bar":"baz","n":1}],"message":"hello world"} } func ExampleEvent_Array_object() { @@ -335,6 +339,38 @@ // Output: {"foo":"bar","durs":[10000,20000],"message":"hello world"} } +func ExampleEvent_Fields_map() { + fields := map[string]interface{}{ + "bar": "baz", + "n": 1, + } + + log := zerolog.New(os.Stdout) + + log.Log(). + Str("foo", "bar"). + Fields(fields). + Msg("hello world") + + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} + +func ExampleEvent_Fields_slice() { + fields := []interface{}{ + "bar", "baz", + "n", 1, + } + + log := zerolog.New(os.Stdout) + + log.Log(). + Str("foo", "bar"). + Fields(fields). + Msg("hello world") + + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} + func ExampleContext_Dict() { log := zerolog.New(os.Stdout).With(). Str("foo", "bar"). @@ -484,3 +520,35 @@ // Output: {"hostMAC":"00:14:22:01:23:45","message":"hello world"} } + +func ExampleContext_Fields_map() { + fields := map[string]interface{}{ + "bar": "baz", + "n": 1, + } + + log := zerolog.New(os.Stdout).With(). + Str("foo", "bar"). + Fields(fields). + Logger() + + log.Log().Msg("hello world") + + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} + +func ExampleContext_Fields_slice() { + fields := []interface{}{ + "bar", "baz", + "n", 1, + } + + log := zerolog.New(os.Stdout).With(). + Str("foo", "bar"). + Fields(fields). + Logger() + + log.Log().Msg("hello world") + + // Output: {"foo":"bar","bar":"baz","n":1,"message":"hello world"} +} diff -Nru golang-github-rs-zerolog-1.20.0/log.go golang-github-rs-zerolog-1.26.1/log.go --- golang-github-rs-zerolog-1.20.0/log.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/log.go 2021-12-15 23:37:18.000000000 +0000 @@ -129,28 +129,31 @@ // TraceLevel defines trace log level. TraceLevel Level = -1 + // Values less than TraceLevel are handled as numbers. ) func (l Level) String() string { switch l { case TraceLevel: - return "trace" + return LevelTraceValue case DebugLevel: - return "debug" + return LevelDebugValue case InfoLevel: - return "info" + return LevelInfoValue case WarnLevel: - return "warn" + return LevelWarnValue case ErrorLevel: - return "error" + return LevelErrorValue case FatalLevel: - return "fatal" + return LevelFatalValue case PanicLevel: - return "panic" + return LevelPanicValue + case Disabled: + return "disabled" case NoLevel: return "" } - return "" + return strconv.Itoa(int(l)) } // ParseLevel converts a level string into a zerolog Level value. @@ -171,10 +174,19 @@ return FatalLevel, nil case LevelFieldMarshalFunc(PanicLevel): return PanicLevel, nil + case LevelFieldMarshalFunc(Disabled): + return Disabled, nil case LevelFieldMarshalFunc(NoLevel): return NoLevel, nil } - return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) + i, err := strconv.Atoi(levelStr) + if err != nil { + return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) + } + if i > 127 || i < -128 { + return NoLevel, fmt.Errorf("Out-Of-Bounds Level: '%d', defaulting to NoLevel", i) + } + return Level(i), nil } // A Logger represents an active logging object that generates lines @@ -188,6 +200,7 @@ sampler Sampler context []byte hooks []Hook + stack bool } // New creates a root logger with given output writer. If the output writer implements @@ -218,6 +231,7 @@ l2 := New(w) l2.level = l.level l2.sampler = l.sampler + l2.stack = l.stack if len(l.hooks) > 0 { l2.hooks = append(l2.hooks, l.hooks...) } @@ -371,7 +385,7 @@ case Disabled: return nil default: - panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level))) + return l.newEvent(level, nil) } } @@ -387,7 +401,7 @@ // Arguments are handled in the manner of fmt.Print. func (l *Logger) Print(v ...interface{}) { if e := l.Debug(); e.Enabled() { - e.Msg(fmt.Sprint(v...)) + e.CallerSkipFrame(1).Msg(fmt.Sprint(v...)) } } @@ -395,7 +409,7 @@ // Arguments are handled in the manner of fmt.Printf. func (l *Logger) Printf(format string, v ...interface{}) { if e := l.Debug(); e.Enabled() { - e.Msg(fmt.Sprintf(format, v...)) + e.CallerSkipFrame(1).Msg(fmt.Sprintf(format, v...)) } } @@ -407,7 +421,7 @@ // Trim CR added by stdlog. p = p[0 : n-1] } - l.Log().Msg(string(p)) + l.Log().CallerSkipFrame(1).Msg(string(p)) return } @@ -419,12 +433,15 @@ e := newEvent(l.w, level) e.done = done e.ch = l.hooks - if level != NoLevel { + if level != NoLevel && LevelFieldName != "" { e.Str(LevelFieldName, LevelFieldMarshalFunc(level)) } if l.context != nil && len(l.context) > 1 { e.buf = enc.AppendObjectData(e.buf, l.context) } + if l.stack { + e.Stack() + } return e } diff -Nru golang-github-rs-zerolog-1.20.0/log_test.go golang-github-rs-zerolog-1.26.1/log_test.go --- golang-github-rs-zerolog-1.20.0/log_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/log_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -77,6 +77,24 @@ }) } +func TestEmptyLevelFieldName(t *testing.T) { + fieldName := LevelFieldName + LevelFieldName = "" + + t.Run("empty setting", func(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + log.Info(). + Str("foo", "bar"). + Int("n", 123). + Msg("") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","n":123}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } + }) + LevelFieldName = fieldName +} + func TestWith(t *testing.T) { out := &bytes.Buffer{} ctx := New(out).With(). @@ -228,6 +246,66 @@ } } +func TestFieldsSlice(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + log.Log().Fields([]interface{}{ + "nil", nil, + "string", "foo", + "bytes", []byte("bar"), + "error", errors.New("some error"), + "bool", true, + "int", int(1), + "int8", int8(2), + "int16", int16(3), + "int32", int32(4), + "int64", int64(5), + "uint", uint(6), + "uint8", uint8(7), + "uint16", uint16(8), + "uint32", uint32(9), + "uint64", uint64(10), + "float32", float32(11), + "float64", float64(12), + "ipv6", net.IP{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}, + "dur", 1 * time.Second, + "time", time.Time{}, + "obj", obj{"a", "b", 1}, + }).Msg("") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"nil":null,"string":"foo","bytes":"bar","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11,"float64":12,"ipv6":"2001:db8:85a3::8a2e:370:7334","dur":1000,"time":"0001-01-01T00:00:00Z","obj":{"Pub":"a","Tag":"b","priv":1}}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + +func TestFieldsSliceExtraneous(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + log.Log().Fields([]interface{}{ + "string", "foo", + "error", errors.New("some error"), + 32, "valueForNonStringKey", + "bool", true, + "int", int(1), + "keyWithoutValue", + }).Msg("") + if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","error":"some error","bool":true,"int":1}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + +func TestFieldsNotMapSlice(t *testing.T) { + out := &bytes.Buffer{} + log := New(out) + log.Log(). + Fields(obj{"a", "b", 1}). + Fields("string"). + Fields(1). + Msg("") + if got, want := decodeIfBinaryToString(out.Bytes()), `{}`+"\n"; got != want { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + func TestFields(t *testing.T) { out := &bytes.Buffer{} log := New(out) @@ -242,6 +320,7 @@ Bytes("bytes", []byte("bar")). Hex("hex", []byte{0x12, 0xef}). RawJSON("json", []byte(`{"some":"json"}`)). + Func(func(e *Event) { e.Str("func", "func_output") }). AnErr("some_err", nil). Err(errors.New("some error")). Bool("bool", true). @@ -265,7 +344,7 @@ Time("time", time.Time{}). TimeDiff("diff", now, now.Add(-10*time.Second)). Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"`+caller+`","string":"foo","stringer":"127.0.0.1","stringer_nil":null,"bytes":"bar","hex":"12ef","json":{"some":"json"},"func":"func_output","error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"IPv4":"192.168.0.100","IPv6":"2001:db8:85a3::8a2e:370:7334","Mac":"00:14:22:01:23:45","Prefix":"192.168.0.100/24","float32":11.1234,"float64":12.321321321,"dur":1000,"time":"0001-01-01T00:00:00Z","diff":10000}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -275,6 +354,7 @@ log := New(out) log.Log(). Strs("string", []string{}). + Stringers("stringer", []fmt.Stringer{}). Errs("err", []error{}). Bools("bool", []bool{}). Ints("int", []int{}). @@ -292,7 +372,7 @@ Durs("dur", []time.Duration{}). Times("time", []time.Time{}). Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":[],"stringer":[],"err":[],"bool":[],"int":[],"int8":[],"int16":[],"int32":[],"int64":[],"uint":[],"uint8":[],"uint16":[],"uint32":[],"uint64":[],"float32":[],"float64":[],"dur":[],"time":[]}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -302,6 +382,7 @@ log := New(out) log.Log(). Strs("string", []string{"foo"}). + Stringers("stringer", []fmt.Stringer{net.IP{127, 0, 0, 1}}). Errs("err", []error{errors.New("some error")}). Bools("bool", []bool{true}). Ints("int", []int{1}). @@ -317,9 +398,9 @@ Floats32("float32", []float32{11}). Floats64("float64", []float64{12}). Durs("dur", []time.Duration{1 * time.Second}). - Times("time", []time.Time{time.Time{}}). + Times("time", []time.Time{{}}). Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo"],"err":["some error"],"bool":[true],"int":[1],"int8":[2],"int16":[3],"int32":[4],"int64":[5],"uint":[6],"uint8":[7],"uint16":[8],"uint32":[9],"uint64":[10],"float32":[11],"float64":[12],"dur":[1000],"time":["0001-01-01T00:00:00Z"]}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo"],"stringer":["127.0.0.1"],"err":["some error"],"bool":[true],"int":[1],"int8":[2],"int16":[3],"int32":[4],"int64":[5],"uint":[6],"uint8":[7],"uint16":[8],"uint32":[9],"uint64":[10],"float32":[11],"float64":[12],"dur":[1000],"time":["0001-01-01T00:00:00Z"]}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -329,6 +410,7 @@ log := New(out) log.Log(). Strs("string", []string{"foo", "bar"}). + Stringers("stringer", []fmt.Stringer{nil, net.IP{127, 0, 0, 1}}). Errs("err", []error{errors.New("some error"), nil}). Bools("bool", []bool{true, false}). Ints("int", []int{1, 0}). @@ -344,9 +426,9 @@ Floats32("float32", []float32{11, 0}). Floats64("float64", []float64{12, 0}). Durs("dur", []time.Duration{1 * time.Second, 0}). - Times("time", []time.Time{time.Time{}, time.Time{}}). + Times("time", []time.Time{{}, {}}). Msg("") - if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo","bar"],"err":["some error",null],"bool":[true,false],"int":[1,0],"int8":[2,0],"int16":[3,0],"int32":[4,0],"int64":[5,0],"uint":[6,0],"uint8":[7,0],"uint16":[8,0],"uint32":[9,0],"uint64":[10,0],"float32":[11,0],"float64":[12,0],"dur":[1000,0],"time":["0001-01-01T00:00:00Z","0001-01-01T00:00:00Z"]}`+"\n"; got != want { + if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":["foo","bar"],"stringer":[null,"127.0.0.1"],"err":["some error",null],"bool":[true,false],"int":[1,0],"int8":[2,0],"int16":[3,0],"int32":[4,0],"int64":[5,0],"uint":[6,0],"uint8":[7,0],"uint16":[8,0],"uint32":[9,0],"uint64":[10,0],"float32":[11,0],"float64":[12,0],"dur":[1000,0],"time":["0001-01-01T00:00:00Z","0001-01-01T00:00:00Z"]}`+"\n"; got != want { t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) } } @@ -357,10 +439,12 @@ now := time.Now() log.Debug(). Str("string", "foo"). + Stringer("stringer", net.IP{127, 0, 0, 1}). Bytes("bytes", []byte("bar")). Hex("hex", []byte{0x12, 0xef}). AnErr("some_err", nil). Err(errors.New("some error")). + Func(func(e *Event) { e.Str("func", "func_output") }). Bool("bool", true). Int("int", 1). Int8("int8", 2). @@ -529,7 +613,11 @@ p string }{}, } - log := New(lw) + + // Allow extra-verbose logs. + SetGlobalLevel(TraceLevel - 1) + log := New(lw).Level(TraceLevel - 1) + log.Trace().Msg("0") log.Debug().Msg("1") log.Info().Msg("2") @@ -542,6 +630,9 @@ log.WithLevel(WarnLevel).Msg("8") log.WithLevel(ErrorLevel).Msg("9") log.WithLevel(NoLevel).Msg("nolevel-2") + log.WithLevel(-1).Msg("-1") // Same as TraceLevel + log.WithLevel(-2).Msg("-2") // Will log + log.WithLevel(-3).Msg("-3") // Will not log want := []struct { l Level @@ -559,6 +650,8 @@ {WarnLevel, `{"level":"warn","message":"8"}` + "\n"}, {ErrorLevel, `{"level":"error","message":"9"}` + "\n"}, {NoLevel, `{"message":"nolevel-2"}` + "\n"}, + {Level(-1), `{"level":"trace","message":"-1"}` + "\n"}, + {Level(-2), `{"level":"-2","message":"-2"}` + "\n"}, } if got := lw.ops; !reflect.DeepEqual(got, want) { t.Errorf("invalid ops:\ngot:\n%v\nwant:\n%v", got, want) @@ -788,3 +881,65 @@ t.Errorf("invalid log output:\ngot: %q\nwant: %q", got, want) } } + +func TestLevel_String(t *testing.T) { + tests := []struct { + name string + l Level + want string + }{ + {"trace", TraceLevel, "trace"}, + {"debug", DebugLevel, "debug"}, + {"info", InfoLevel, "info"}, + {"warn", WarnLevel, "warn"}, + {"error", ErrorLevel, "error"}, + {"fatal", FatalLevel, "fatal"}, + {"panic", PanicLevel, "panic"}, + {"disabled", Disabled, "disabled"}, + {"nolevel", NoLevel, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.l.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseLevel(t *testing.T) { + type args struct { + levelStr string + } + tests := []struct { + name string + args args + want Level + wantErr bool + }{ + {"trace", args{"trace"}, TraceLevel, false}, + {"debug", args{"debug"}, DebugLevel, false}, + {"info", args{"info"}, InfoLevel, false}, + {"warn", args{"warn"}, WarnLevel, false}, + {"error", args{"error"}, ErrorLevel, false}, + {"fatal", args{"fatal"}, FatalLevel, false}, + {"panic", args{"panic"}, PanicLevel, false}, + {"disabled", args{"disabled"}, Disabled, false}, + {"nolevel", args{""}, NoLevel, false}, + {"-1", args{"-1"}, TraceLevel, false}, + {"-2", args{"-2"}, Level(-2), false}, + {"-3", args{"-3"}, Level(-3), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseLevel(tt.args.levelStr) + if (err != nil) != tt.wantErr { + t.Errorf("ParseLevel() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseLevel() got = %v, want %v", got, tt.want) + } + }) + } +} diff -Nru golang-github-rs-zerolog-1.20.0/pkgerrors/stacktrace_test.go golang-github-rs-zerolog-1.26.1/pkgerrors/stacktrace_test.go --- golang-github-rs-zerolog-1.20.0/pkgerrors/stacktrace_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/pkgerrors/stacktrace_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -27,6 +27,22 @@ } } +func TestLogStackFromContext(t *testing.T) { + zerolog.ErrorStackMarshaler = MarshalStack + + out := &bytes.Buffer{} + log := zerolog.New(out).With().Stack().Logger() // calling Stack() on log context instead of event + + err := errors.Wrap(errors.New("error message"), "from error") + log.Log().Err(err).Msg("") // not explicitly calling Stack() + + got := out.String() + want := `\{"stack":\[\{"func":"TestLogStackFromContext","line":"36","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n` + if ok, _ := regexp.MatchString(want, got); !ok { + t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want) + } +} + func BenchmarkLogStack(b *testing.B) { zerolog.ErrorStackMarshaler = MarshalStack out := &bytes.Buffer{} Binary files /tmp/tmp7h5vpn8l/wDUXvQ3Ezf/golang-github-rs-zerolog-1.20.0/pretty.png and /tmp/tmp7h5vpn8l/sU1bgb725Y/golang-github-rs-zerolog-1.26.1/pretty.png differ diff -Nru golang-github-rs-zerolog-1.20.0/README.md golang-github-rs-zerolog-1.26.1/README.md --- golang-github-rs-zerolog-1.20.0/README.md 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/README.md 2021-12-15 23:37:18.000000000 +0000 @@ -18,16 +18,17 @@ ## Features -* Blazing fast -* Low to zero allocation -* Level logging -* Sampling -* Hooks -* Contextual fields +* [Blazing fast](#benchmarks) +* [Low to zero allocation](#benchmarks) +* [Leveled logging](#leveled-logging) +* [Sampling](#log-sampling) +* [Hooks](#hooks) +* [Contextual fields](#contextual-logging) * `context.Context` integration -* `net/http` helpers -* JSON and CBOR encoding formats -* Pretty logging for development +* [Integration with `net/http`](#integration-with-nethttp) +* [JSON and CBOR encoding formats](#binary-encoding) +* [Pretty logging for development](#pretty-logging) +* [Error Logging (with optional Stacktrace)](#error-logging) ## Installation @@ -51,8 +52,6 @@ func main() { // UNIX Time is faster and smaller than most timestamps - // If you set zerolog.TimeFieldFormat to an empty string, - // logs will write with UNIX time zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Print("hello world") @@ -205,6 +204,80 @@ // Output: {"time":1494567715,"foo":"bar"} ``` +### Error Logging + +You can log errors using the `Err` method + +```go +package main + +import ( + "errors" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + err := errors.New("seems we have an error here") + log.Error().Err(err).Msg("") +} + +// Output: {"level":"error","error":"seems we have an error here","time":1609085256} +``` + +> The default field name for errors is `error`, you can change this by setting `zerolog.ErrorFieldName` to meet your needs. + +#### Error Logging with Stacktrace + +Using `github.com/pkg/errors`, you can add a formatted stacktrace to your errors. + +```go +package main + +import ( + "github.com/pkg/errors" + "github.com/rs/zerolog/pkgerrors" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack + + err := outer() + log.Error().Stack().Err(err).Msg("") +} + +func inner() error { + return errors.New("seems we have an error here") +} + +func middle() error { + err := inner() + if err != nil { + return err + } + return nil +} + +func outer() error { + err := middle() + if err != nil { + return err + } + return nil +} + +// Output: {"level":"error","stack":[{"func":"inner","line":"20","source":"errors.go"},{"func":"middle","line":"24","source":"errors.go"},{"func":"outer","line":"32","source":"errors.go"},{"func":"main","line":"15","source":"errors.go"},{"func":"main","line":"204","source":"proc.go"},{"func":"goexit","line":"1374","source":"asm_amd64.s"}],"error":"seems we have an error here","time":1609086683} +``` + +> zerolog.ErrorStackMarshaler must be set in order for the stack to output anything. + #### Logging Fatal Messages ```go @@ -235,6 +308,7 @@ > NOTE: Using `Msgf` generates one allocation even when the logger is disabled. + ### Create logger instance to manage different outputs ```go @@ -517,6 +591,7 @@ ### Advanced Fields * `Err`: Takes an `error` and renders it as a string using the `zerolog.ErrorFieldName` field name. +* `Func`: Run a `func` only if the level is enabled. * `Timestamp`: Inserts a timestamp field with `zerolog.TimestampFieldName` field name, formatted using `zerolog.TimeFieldFormat`. * `Time`: Adds a field with time formatted with `zerolog.TimeFieldFormat`. * `Dur`: Adds a field with `time.Duration`. @@ -541,6 +616,8 @@ ## Related Projects * [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog` +* [overlog](https://github.com/Trendyol/overlog): Implementation of `Mapped Diagnostic Context` interface using `zerolog` +* [zerologr](https://github.com/go-logr/zerologr): Implementation of `logr.LogSink` interface using `zerolog` ## Benchmarks diff -Nru golang-github-rs-zerolog-1.20.0/sampler.go golang-github-rs-zerolog-1.26.1/sampler.go --- golang-github-rs-zerolog-1.20.0/sampler.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/sampler.go 2021-12-15 23:37:18.000000000 +0000 @@ -38,7 +38,7 @@ } // BasicSampler is a sampler that will send every Nth events, regardless of -// there level. +// their level. type BasicSampler struct { N uint32 counter uint32 diff -Nru golang-github-rs-zerolog-1.20.0/syslog.go golang-github-rs-zerolog-1.26.1/syslog.go --- golang-github-rs-zerolog-1.20.0/syslog.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/syslog.go 2021-12-15 23:37:18.000000000 +0000 @@ -7,6 +7,10 @@ "io" ) +// See http://cee.mitre.org/language/1.0-beta1/clt.html#syslog +// or https://www.rsyslog.com/json-elasticsearch/ +const ceePrefix = "@cee:" + // SyslogWriter is an interface matching a syslog.Writer struct. type SyslogWriter interface { io.Writer @@ -19,17 +23,34 @@ } type syslogWriter struct { - w SyslogWriter + w SyslogWriter + prefix string } // SyslogLevelWriter wraps a SyslogWriter and call the right syslog level // method matching the zerolog level. func SyslogLevelWriter(w SyslogWriter) LevelWriter { - return syslogWriter{w} + return syslogWriter{w, ""} +} + +// SyslogCEEWriter wraps a SyslogWriter with a SyslogLevelWriter that adds a +// MITRE CEE prefix for JSON syslog entries, compatible with rsyslog +// and syslog-ng JSON logging support. +// See https://www.rsyslog.com/json-elasticsearch/ +func SyslogCEEWriter(w SyslogWriter) LevelWriter { + return syslogWriter{w, ceePrefix} } func (sw syslogWriter) Write(p []byte) (n int, err error) { - return sw.w.Write(p) + var pn int + if sw.prefix != "" { + pn, err = sw.w.Write([]byte(sw.prefix)) + if err != nil { + return pn, err + } + } + n, err = sw.w.Write(p) + return pn + n, err } // WriteLevel implements LevelWriter interface. @@ -37,22 +58,23 @@ switch level { case TraceLevel: case DebugLevel: - err = sw.w.Debug(string(p)) + err = sw.w.Debug(sw.prefix + string(p)) case InfoLevel: - err = sw.w.Info(string(p)) + err = sw.w.Info(sw.prefix + string(p)) case WarnLevel: - err = sw.w.Warning(string(p)) + err = sw.w.Warning(sw.prefix + string(p)) case ErrorLevel: - err = sw.w.Err(string(p)) + err = sw.w.Err(sw.prefix + string(p)) case FatalLevel: - err = sw.w.Emerg(string(p)) + err = sw.w.Emerg(sw.prefix + string(p)) case PanicLevel: - err = sw.w.Crit(string(p)) + err = sw.w.Crit(sw.prefix + string(p)) case NoLevel: - err = sw.w.Info(string(p)) + err = sw.w.Info(sw.prefix + string(p)) default: panic("invalid level") } + // Any CEE prefix is not part of the message, so we don't include its length n = len(p) return } diff -Nru golang-github-rs-zerolog-1.20.0/syslog_test.go golang-github-rs-zerolog-1.26.1/syslog_test.go --- golang-github-rs-zerolog-1.20.0/syslog_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/syslog_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -4,7 +4,9 @@ package zerolog import ( + "bytes" "reflect" + "strings" "testing" ) @@ -68,3 +70,39 @@ t.Errorf("Invalid syslog message routing: want %v, got %v", want, got) } } + +type testCEEwriter struct { + buf *bytes.Buffer +} + +// Only implement one method as we're just testing the prefixing +func (c testCEEwriter) Debug(m string) error { return nil } + +func (c testCEEwriter) Info(m string) error { + _, err := c.buf.Write([]byte(m)) + return err +} + +func (c testCEEwriter) Warning(m string) error { return nil } + +func (c testCEEwriter) Err(m string) error { return nil } + +func (c testCEEwriter) Emerg(m string) error { return nil } + +func (c testCEEwriter) Crit(m string) error { return nil } + +func (c testCEEwriter) Write(b []byte) (int, error) { + return c.buf.Write(b) +} + +func TestSyslogWriter_WithCEE(t *testing.T) { + var buf bytes.Buffer + sw := testCEEwriter{&buf} + log := New(SyslogCEEWriter(sw)) + log.Info().Str("key", "value").Msg("message string") + got := string(buf.Bytes()) + want := "@cee:{" + if !strings.HasPrefix(got, want) { + t.Errorf("Bad CEE message start: want %v, got %v", want, got) + } +} diff -Nru golang-github-rs-zerolog-1.20.0/.travis.yml golang-github-rs-zerolog-1.26.1/.travis.yml --- golang-github-rs-zerolog-1.20.0/.travis.yml 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -language: go -go: -- "1.7" -- "1.8" -- "1.9" -- "1.10" -- "1.11" -- "1.12" -- "master" -matrix: - allow_failures: - - go: "master" -script: - - go test -v -race -cpu=1,2,4 -bench . -benchmem ./... - - go test -v -tags binary_log -race -cpu=1,2,4 -bench . -benchmem ./... diff -Nru golang-github-rs-zerolog-1.20.0/writer.go golang-github-rs-zerolog-1.26.1/writer.go --- golang-github-rs-zerolog-1.20.0/writer.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/writer.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,7 +1,12 @@ package zerolog import ( + "bytes" "io" + "path" + "runtime" + "strconv" + "strings" "sync" ) @@ -56,30 +61,30 @@ func (t multiLevelWriter) Write(p []byte) (n int, err error) { for _, w := range t.writers { - n, err = w.Write(p) - if err != nil { - return - } - if n != len(p) { - err = io.ErrShortWrite - return + if _n, _err := w.Write(p); err == nil { + n = _n + if _err != nil { + err = _err + } else if _n != len(p) { + err = io.ErrShortWrite + } } } - return len(p), nil + return n, err } func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) { for _, w := range t.writers { - n, err = w.WriteLevel(l, p) - if err != nil { - return - } - if n != len(p) { - err = io.ErrShortWrite - return + if _n, _err := w.WriteLevel(l, p); err == nil { + n = _n + if _err != nil { + err = _err + } else if _n != len(p) { + err = io.ErrShortWrite + } } } - return len(p), nil + return n, err } // MultiLevelWriter creates a writer that duplicates its writes to all the @@ -96,3 +101,54 @@ } return multiLevelWriter{lwriters} } + +// TestingLog is the logging interface of testing.TB. +type TestingLog interface { + Log(args ...interface{}) + Logf(format string, args ...interface{}) + Helper() +} + +// TestWriter is a writer that writes to testing.TB. +type TestWriter struct { + T TestingLog + + // Frame skips caller frames to capture the original file and line numbers. + Frame int +} + +// NewTestWriter creates a writer that logs to the testing.TB. +func NewTestWriter(t TestingLog) TestWriter { + return TestWriter{T: t} +} + +// Write to testing.TB. +func (t TestWriter) Write(p []byte) (n int, err error) { + t.T.Helper() + + n = len(p) + + // Strip trailing newline because t.Log always adds one. + p = bytes.TrimRight(p, "\n") + + // Try to correct the log file and line number to the caller. + if t.Frame > 0 { + _, origFile, origLine, _ := runtime.Caller(1) + _, frameFile, frameLine, ok := runtime.Caller(1 + t.Frame) + if ok { + erase := strings.Repeat("\b", len(path.Base(origFile))+len(strconv.Itoa(origLine))+3) + t.T.Logf("%s%s:%d: %s", erase, path.Base(frameFile), frameLine, p) + return n, err + } + } + t.T.Log(string(p)) + + return n, err +} + +// ConsoleTestWriter creates an option that correctly sets the file frame depth for testing.TB log. +func ConsoleTestWriter(t TestingLog) func(w *ConsoleWriter) { + return func(w *ConsoleWriter) { + w.Out = TestWriter{T: t, Frame: 6} + } +} diff -Nru golang-github-rs-zerolog-1.20.0/writer_test.go golang-github-rs-zerolog-1.26.1/writer_test.go --- golang-github-rs-zerolog-1.20.0/writer_test.go 2020-08-06 10:19:27.000000000 +0000 +++ golang-github-rs-zerolog-1.26.1/writer_test.go 2021-12-15 23:37:18.000000000 +0000 @@ -1,9 +1,13 @@ -// +build !binary_log -// +build !windows +//go:build !binary_log && !windows +// +build !binary_log,!windows package zerolog import ( + "bytes" + "errors" + "fmt" + "io" "reflect" "testing" ) @@ -27,3 +31,147 @@ t.Errorf("Invalid syslog message routing: want %v, got %v", want, got) } } + +var writeCalls int + +type mockedWriter struct { + wantErr bool +} + +func (c mockedWriter) Write(p []byte) (int, error) { + writeCalls++ + + if c.wantErr { + return -1, errors.New("Expected error") + } + + return len(p), nil +} + +// Tests that a new writer is only used if it actually works. +func TestResilientMultiWriter(t *testing.T) { + tests := []struct { + name string + writers []io.Writer + }{ + { + name: "All valid writers", + writers: []io.Writer{ + mockedWriter{ + wantErr: false, + }, + mockedWriter{ + wantErr: false, + }, + }, + }, + { + name: "All invalid writers", + writers: []io.Writer{ + mockedWriter{ + wantErr: true, + }, + mockedWriter{ + wantErr: true, + }, + }, + }, + { + name: "First invalid writer", + writers: []io.Writer{ + mockedWriter{ + wantErr: true, + }, + mockedWriter{ + wantErr: false, + }, + }, + }, + { + name: "First valid writer", + writers: []io.Writer{ + mockedWriter{ + wantErr: false, + }, + mockedWriter{ + wantErr: true, + }, + }, + }, + } + + for _, tt := range tests { + writers := tt.writers + multiWriter := MultiLevelWriter(writers...) + + logger := New(multiWriter).With().Timestamp().Logger().Level(InfoLevel) + logger.Info().Msg("Test msg") + + if len(writers) != writeCalls { + t.Errorf("Expected %d writers to have been called but only %d were.", len(writers), writeCalls) + } + writeCalls = 0 + } +} + +type testingLog struct { + testing.TB + buf bytes.Buffer +} + +func (t *testingLog) Log(args ...interface{}) { + if _, err := t.buf.WriteString(fmt.Sprint(args...)); err != nil { + t.Error(err) + } +} + +func (t *testingLog) Logf(format string, args ...interface{}) { + if _, err := t.buf.WriteString(fmt.Sprintf(format, args...)); err != nil { + t.Error(err) + } +} + +func TestTestWriter(t *testing.T) { + tests := []struct { + name string + write []byte + want []byte + }{{ + name: "newline", + write: []byte("newline\n"), + want: []byte("newline"), + }, { + name: "oneline", + write: []byte("oneline"), + want: []byte("oneline"), + }, { + name: "twoline", + write: []byte("twoline\n\n"), + want: []byte("twoline"), + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tb := &testingLog{TB: t} // Capture TB log buffer. + w := TestWriter{T: tb} + + n, err := w.Write(tt.write) + if err != nil { + t.Error(err) + } + if n != len(tt.write) { + t.Errorf("Expected %d write length but got %d", len(tt.write), n) + } + p := tb.buf.Bytes() + if !bytes.Equal(tt.want, p) { + t.Errorf("Expected %q, got %q.", tt.want, p) + } + + log := New(NewConsoleWriter(ConsoleTestWriter(t))) + log.Info().Str("name", tt.name).Msg("Success!") + + tb.buf.Reset() + }) + } + +}