diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:36:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:36:04 +0000 |
commit | b09c6d56832eb1718c07d74abf3bc6ae3fe4e030 (patch) | |
tree | d2caec2610d4ea887803ec9e9c3cd77136c448ba /dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore | |
parent | Initial commit. (diff) | |
download | icingadb-b09c6d56832eb1718c07d74abf3bc6ae3fe4e030.tar.xz icingadb-b09c6d56832eb1718c07d74abf3bc6ae3fe4e030.zip |
Adding upstream version 1.1.0.upstream/1.1.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore')
45 files changed, 8329 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer.go new file mode 100644 index 0000000..a40e93b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer.go @@ -0,0 +1,219 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bufio" + "sync" + "time" + + "go.uber.org/multierr" +) + +const ( + // _defaultBufferSize specifies the default size used by Buffer. + _defaultBufferSize = 256 * 1024 // 256 kB + + // _defaultFlushInterval specifies the default flush interval for + // Buffer. + _defaultFlushInterval = 30 * time.Second +) + +// A BufferedWriteSyncer is a WriteSyncer that buffers writes in-memory before +// flushing them to a wrapped WriteSyncer after reaching some limit, or at some +// fixed interval--whichever comes first. +// +// BufferedWriteSyncer is safe for concurrent use. You don't need to use +// zapcore.Lock for WriteSyncers with BufferedWriteSyncer. +// +// To set up a BufferedWriteSyncer, construct a WriteSyncer for your log +// destination (*os.File is a valid WriteSyncer), wrap it with +// BufferedWriteSyncer, and defer a Stop() call for when you no longer need the +// object. +// +// func main() { +// ws := ... // your log destination +// bws := &zapcore.BufferedWriteSyncer{WS: ws} +// defer bws.Stop() +// +// // ... +// core := zapcore.NewCore(enc, bws, lvl) +// logger := zap.New(core) +// +// // ... +// } +// +// By default, a BufferedWriteSyncer will buffer up to 256 kilobytes of logs, +// waiting at most 30 seconds between flushes. +// You can customize these parameters by setting the Size or FlushInterval +// fields. +// For example, the following buffers up to 512 kB of logs before flushing them +// to Stderr, with a maximum of one minute between each flush. +// +// ws := &BufferedWriteSyncer{ +// WS: os.Stderr, +// Size: 512 * 1024, // 512 kB +// FlushInterval: time.Minute, +// } +// defer ws.Stop() +type BufferedWriteSyncer struct { + // WS is the WriteSyncer around which BufferedWriteSyncer will buffer + // writes. + // + // This field is required. + WS WriteSyncer + + // Size specifies the maximum amount of data the writer will buffered + // before flushing. + // + // Defaults to 256 kB if unspecified. + Size int + + // FlushInterval specifies how often the writer should flush data if + // there have been no writes. + // + // Defaults to 30 seconds if unspecified. + FlushInterval time.Duration + + // Clock, if specified, provides control of the source of time for the + // writer. + // + // Defaults to the system clock. + Clock Clock + + // unexported fields for state + mu sync.Mutex + initialized bool // whether initialize() has run + stopped bool // whether Stop() has run + writer *bufio.Writer + ticker *time.Ticker + stop chan struct{} // closed when flushLoop should stop + done chan struct{} // closed when flushLoop has stopped +} + +func (s *BufferedWriteSyncer) initialize() { + size := s.Size + if size == 0 { + size = _defaultBufferSize + } + + flushInterval := s.FlushInterval + if flushInterval == 0 { + flushInterval = _defaultFlushInterval + } + + if s.Clock == nil { + s.Clock = DefaultClock + } + + s.ticker = s.Clock.NewTicker(flushInterval) + s.writer = bufio.NewWriterSize(s.WS, size) + s.stop = make(chan struct{}) + s.done = make(chan struct{}) + s.initialized = true + go s.flushLoop() +} + +// Write writes log data into buffer syncer directly, multiple Write calls will be batched, +// and log data will be flushed to disk when the buffer is full or periodically. +func (s *BufferedWriteSyncer) Write(bs []byte) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.initialized { + s.initialize() + } + + // To avoid partial writes from being flushed, we manually flush the existing buffer if: + // * The current write doesn't fit into the buffer fully, and + // * The buffer is not empty (since bufio will not split large writes when the buffer is empty) + if len(bs) > s.writer.Available() && s.writer.Buffered() > 0 { + if err := s.writer.Flush(); err != nil { + return 0, err + } + } + + return s.writer.Write(bs) +} + +// Sync flushes buffered log data into disk directly. +func (s *BufferedWriteSyncer) Sync() error { + s.mu.Lock() + defer s.mu.Unlock() + + var err error + if s.initialized { + err = s.writer.Flush() + } + + return multierr.Append(err, s.WS.Sync()) +} + +// flushLoop flushes the buffer at the configured interval until Stop is +// called. +func (s *BufferedWriteSyncer) flushLoop() { + defer close(s.done) + + for { + select { + case <-s.ticker.C: + // we just simply ignore error here + // because the underlying bufio writer stores any errors + // and we return any error from Sync() as part of the close + _ = s.Sync() + case <-s.stop: + return + } + } +} + +// Stop closes the buffer, cleans up background goroutines, and flushes +// remaining unwritten data. +func (s *BufferedWriteSyncer) Stop() (err error) { + var stopped bool + + // Critical section. + func() { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.initialized { + return + } + + stopped = s.stopped + if stopped { + return + } + s.stopped = true + + s.ticker.Stop() + close(s.stop) // tell flushLoop to stop + <-s.done // and wait until it has + }() + + // Don't call Sync on consecutive Stops. + if !stopped { + err = s.Sync() + } + + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer_bench_test.go new file mode 100644 index 0000000..1e3db59 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer_bench_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func BenchmarkBufferedWriteSyncer(b *testing.B) { + b.Run("write file with buffer", func(b *testing.B) { + file, err := os.CreateTemp(b.TempDir(), "test.log") + require.NoError(b, err) + + defer func() { + assert.NoError(b, file.Close()) + }() + + w := &BufferedWriteSyncer{ + WS: AddSync(file), + } + defer w.Stop() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + w.Write([]byte("foobarbazbabble")) + } + }) + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer_test.go new file mode 100644 index 0000000..8a36ad6 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/buffered_write_syncer_test.go @@ -0,0 +1,139 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +func TestBufferWriter(t *testing.T) { + // If we pass a plain io.Writer, make sure that we still get a WriteSyncer + // with a no-op Sync. + t.Run("sync", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf)} + + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.") + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("stop", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf)} + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + assert.NoError(t, ws.Stop()) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + }) + + t.Run("stop twice", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: &ztest.FailWriter{}} + _, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + assert.Error(t, ws.Stop(), "Expected stop to fail.") + assert.NoError(t, ws.Stop(), "Expected stop to not fail.") + }) + + t.Run("wrap twice", func(t *testing.T) { + buf := &bytes.Buffer{} + bufsync := &BufferedWriteSyncer{WS: AddSync(buf)} + ws := &BufferedWriteSyncer{WS: bufsync} + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + require.NoError(t, ws.Sync()) + assert.Equal(t, "foo", buf.String()) + assert.NoError(t, ws.Stop()) + assert.NoError(t, bufsync.Stop()) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + }) + + t.Run("small buffer", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf), Size: 5} + + requireWriteWorks(t, ws) + assert.Equal(t, "", buf.String(), "Unexpected log calling a no-op Write method.") + requireWriteWorks(t, ws) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("with lockedWriteSyncer", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: Lock(AddSync(buf)), Size: 5} + + requireWriteWorks(t, ws) + assert.Equal(t, "", buf.String(), "Unexpected log calling a no-op Write method.") + requireWriteWorks(t, ws) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("flush error", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: &ztest.FailWriter{}, Size: 4} + n, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + require.Equal(t, 3, n, "Wrote an unexpected number of bytes.") + ws.Write([]byte("foo")) + assert.Error(t, ws.Stop(), "Expected stop to fail.") + }) + + t.Run("flush timer", func(t *testing.T) { + buf := &bytes.Buffer{} + clock := ztest.NewMockClock() + ws := &BufferedWriteSyncer{ + WS: AddSync(buf), + Size: 6, + FlushInterval: time.Microsecond, + Clock: clock, + } + requireWriteWorks(t, ws) + clock.Add(10 * time.Microsecond) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + + // flush twice to validate loop logic + requireWriteWorks(t, ws) + clock.Add(10 * time.Microsecond) + assert.Equal(t, "foofoo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) +} + +func TestBufferWriterWithoutStart(t *testing.T) { + t.Run("stop", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: AddSync(new(bytes.Buffer))} + assert.NoError(t, ws.Stop(), "Stop must not fail") + }) + + t.Run("Sync", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: AddSync(new(bytes.Buffer))} + assert.NoError(t, ws.Sync(), "Sync must not fail") + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/clock.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/clock.go new file mode 100644 index 0000000..422fd82 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/clock.go @@ -0,0 +1,48 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "time" + +// DefaultClock is the default clock used by Zap in operations that require +// time. This clock uses the system clock for all operations. +var DefaultClock = systemClock{} + +// Clock is a source of time for logged entries. +type Clock interface { + // Now returns the current local time. + Now() time.Time + + // NewTicker returns *time.Ticker that holds a channel + // that delivers "ticks" of a clock. + NewTicker(time.Duration) *time.Ticker +} + +// systemClock implements default Clock that uses system time. +type systemClock struct{} + +func (systemClock) Now() time.Time { + return time.Now() +} + +func (systemClock) NewTicker(duration time.Duration) *time.Ticker { + return time.NewTicker(duration) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/clock_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/clock_test.go new file mode 100644 index 0000000..0dff349 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/clock_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + "time" + + "go.uber.org/zap/internal/ztest" +) + +// Verify that the mock clock satisfies the Clock interface. +var _ Clock = (*ztest.MockClock)(nil) + +func TestSystemClock_NewTicker(t *testing.T) { + want := 3 + + var n int + timer := DefaultClock.NewTicker(time.Millisecond) + for range timer.C { + n++ + if n == want { + return + } + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder.go new file mode 100644 index 0000000..1aa5dc3 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder.go @@ -0,0 +1,157 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "sync" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" +) + +var _sliceEncoderPool = sync.Pool{ + New: func() interface{} { + return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)} + }, +} + +func getSliceEncoder() *sliceArrayEncoder { + return _sliceEncoderPool.Get().(*sliceArrayEncoder) +} + +func putSliceEncoder(e *sliceArrayEncoder) { + e.elems = e.elems[:0] + _sliceEncoderPool.Put(e) +} + +type consoleEncoder struct { + *jsonEncoder +} + +// NewConsoleEncoder creates an encoder whose output is designed for human - +// rather than machine - consumption. It serializes the core log entry data +// (message, level, timestamp, etc.) in a plain-text format and leaves the +// structured context as JSON. +// +// Note that although the console encoder doesn't use the keys specified in the +// encoder configuration, it will omit any element whose key is set to the empty +// string. +func NewConsoleEncoder(cfg EncoderConfig) Encoder { + if cfg.ConsoleSeparator == "" { + // Use a default delimiter of '\t' for backwards compatibility + cfg.ConsoleSeparator = "\t" + } + return consoleEncoder{newJSONEncoder(cfg, true)} +} + +func (c consoleEncoder) Clone() Encoder { + return consoleEncoder{c.jsonEncoder.Clone().(*jsonEncoder)} +} + +func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + line := bufferpool.Get() + + // We don't want the entry's metadata to be quoted and escaped (if it's + // encoded as strings), which means that we can't use the JSON encoder. The + // simplest option is to use the memory encoder and fmt.Fprint. + // + // If this ever becomes a performance bottleneck, we can implement + // ArrayEncoder for our plain-text format. + arr := getSliceEncoder() + if c.TimeKey != "" && c.EncodeTime != nil { + c.EncodeTime(ent.Time, arr) + } + if c.LevelKey != "" && c.EncodeLevel != nil { + c.EncodeLevel(ent.Level, arr) + } + if ent.LoggerName != "" && c.NameKey != "" { + nameEncoder := c.EncodeName + + if nameEncoder == nil { + // Fall back to FullNameEncoder for backward compatibility. + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, arr) + } + if ent.Caller.Defined { + if c.CallerKey != "" && c.EncodeCaller != nil { + c.EncodeCaller(ent.Caller, arr) + } + if c.FunctionKey != "" { + arr.AppendString(ent.Caller.Function) + } + } + for i := range arr.elems { + if i > 0 { + line.AppendString(c.ConsoleSeparator) + } + fmt.Fprint(line, arr.elems[i]) + } + putSliceEncoder(arr) + + // Add the message itself. + if c.MessageKey != "" { + c.addSeparatorIfNecessary(line) + line.AppendString(ent.Message) + } + + // Add any structured context. + c.writeContext(line, fields) + + // If there's no stacktrace key, honor that; this allows users to force + // single-line output. + if ent.Stack != "" && c.StacktraceKey != "" { + line.AppendByte('\n') + line.AppendString(ent.Stack) + } + + line.AppendString(c.LineEnding) + return line, nil +} + +func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) { + context := c.jsonEncoder.Clone().(*jsonEncoder) + defer func() { + // putJSONEncoder assumes the buffer is still used, but we write out the buffer so + // we can free it. + context.buf.Free() + putJSONEncoder(context) + }() + + addFields(context, extra) + context.closeOpenNamespaces() + if context.buf.Len() == 0 { + return + } + + c.addSeparatorIfNecessary(line) + line.AppendByte('{') + line.Write(context.buf.Bytes()) + line.AppendByte('}') +} + +func (c consoleEncoder) addSeparatorIfNecessary(line *buffer.Buffer) { + if line.Len() > 0 { + line.AppendString(c.ConsoleSeparator) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder_bench_test.go new file mode 100644 index 0000000..62feaea --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder_bench_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "testing" + + . "go.uber.org/zap/zapcore" +) + +func BenchmarkZapConsole(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewConsoleEncoder(humanEncoderConfig()) + enc.AddString("str", "foo") + enc.AddInt64("int64-1", 1) + enc.AddInt64("int64-2", 2) + enc.AddFloat64("float64", 1.0) + enc.AddString("string1", "\n") + enc.AddString("string2", "💩") + enc.AddString("string3", "🤔") + enc.AddString("string4", "🙊") + enc.AddBool("bool", true) + buf, _ := enc.EncodeEntry(Entry{ + Message: "fake", + Level: DebugLevel, + }, nil) + buf.Free() + } + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder_test.go new file mode 100644 index 0000000..b03f1a7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/console_encoder_test.go @@ -0,0 +1,91 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package zapcore_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + . "go.uber.org/zap/zapcore" +) + +var ( + testEntry = Entry{ + LoggerName: "main", + Level: InfoLevel, + Message: `hello`, + Time: _epoch, + Stack: "fake-stack", + Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"}, + } +) + +func TestConsoleSeparator(t *testing.T) { + tests := []struct { + desc string + separator string + wantConsole string + }{ + { + desc: "space console separator", + separator: " ", + wantConsole: "0 info main foo.go:42 foo.Foo hello\nfake-stack\n", + }, + { + desc: "default console separator", + separator: "", + wantConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "tag console separator", + separator: "\t", + wantConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "dash console separator", + separator: "--", + wantConsole: "0--info--main--foo.go:42--foo.Foo--hello\nfake-stack\n", + }, + } + + for _, tt := range tests { + console := NewConsoleEncoder(encoderTestEncoderConfig(tt.separator)) + t.Run(tt.desc, func(t *testing.T) { + entry := testEntry + consoleOut, err := console.EncodeEntry(entry, nil) + if !assert.NoError(t, err) { + return + } + assert.Equal( + t, + tt.wantConsole, + consoleOut.String(), + "Unexpected console output", + ) + }) + + } +} + +func encoderTestEncoderConfig(separator string) EncoderConfig { + testEncoder := testEncoderConfig() + testEncoder.ConsoleSeparator = separator + return testEncoder +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/core.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/core.go new file mode 100644 index 0000000..9dfd640 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/core.go @@ -0,0 +1,122 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// Core is a minimal, fast logger interface. It's designed for library authors +// to wrap in a more user-friendly API. +type Core interface { + LevelEnabler + + // With adds structured context to the Core. + With([]Field) Core + // Check determines whether the supplied Entry should be logged (using the + // embedded LevelEnabler and possibly some extra logic). If the entry + // should be logged, the Core adds itself to the CheckedEntry and returns + // the result. + // + // Callers must use Check before calling Write. + Check(Entry, *CheckedEntry) *CheckedEntry + // Write serializes the Entry and any Fields supplied at the log site and + // writes them to their destination. + // + // If called, Write should always log the Entry and Fields; it should not + // replicate the logic of Check. + Write(Entry, []Field) error + // Sync flushes buffered logs (if any). + Sync() error +} + +type nopCore struct{} + +// NewNopCore returns a no-op Core. +func NewNopCore() Core { return nopCore{} } +func (nopCore) Enabled(Level) bool { return false } +func (n nopCore) With([]Field) Core { return n } +func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce } +func (nopCore) Write(Entry, []Field) error { return nil } +func (nopCore) Sync() error { return nil } + +// NewCore creates a Core that writes logs to a WriteSyncer. +func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core { + return &ioCore{ + LevelEnabler: enab, + enc: enc, + out: ws, + } +} + +type ioCore struct { + LevelEnabler + enc Encoder + out WriteSyncer +} + +var ( + _ Core = (*ioCore)(nil) + _ leveledEnabler = (*ioCore)(nil) +) + +func (c *ioCore) Level() Level { + return LevelOf(c.LevelEnabler) +} + +func (c *ioCore) With(fields []Field) Core { + clone := c.clone() + addFields(clone.enc, fields) + return clone +} + +func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if c.Enabled(ent.Level) { + return ce.AddCore(ent, c) + } + return ce +} + +func (c *ioCore) Write(ent Entry, fields []Field) error { + buf, err := c.enc.EncodeEntry(ent, fields) + if err != nil { + return err + } + _, err = c.out.Write(buf.Bytes()) + buf.Free() + if err != nil { + return err + } + if ent.Level > ErrorLevel { + // Since we may be crashing the program, sync the output. Ignore Sync + // errors, pending a clean solution to issue #370. + c.Sync() + } + return nil +} + +func (c *ioCore) Sync() error { + return c.out.Sync() +} + +func (c *ioCore) clone() *ioCore { + return &ioCore{ + LevelEnabler: c.LevelEnabler, + enc: c.enc.Clone(), + out: c.out, + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/core_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/core_test.go new file mode 100644 index 0000000..1311097 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/core_test.go @@ -0,0 +1,165 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "os" + "testing" + "time" + + "go.uber.org/zap/internal/ztest" + . "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeInt64Field(key string, val int) Field { + return Field{Type: Int64Type, Integer: int64(val), Key: key} +} + +func TestNopCore(t *testing.T) { + entry := Entry{ + Message: "test", + Level: InfoLevel, + Time: time.Now(), + LoggerName: "main", + Stack: "fake-stack", + } + ce := &CheckedEntry{} + + allLevels := []Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + PanicLevel, + FatalLevel, + } + core := NewNopCore() + assert.Equal(t, core, core.With([]Field{makeInt64Field("k", 42)}), "Expected no-op With.") + for _, level := range allLevels { + assert.False(t, core.Enabled(level), "Expected all levels to be disabled in no-op core.") + assert.Equal(t, ce, core.Check(entry, ce), "Expected no-op Check to return checked entry unchanged.") + assert.NoError(t, core.Write(entry, nil), "Expected no-op Writes to always succeed.") + assert.NoError(t, core.Sync(), "Expected no-op Syncs to always succeed.") + } +} + +func TestIOCore(t *testing.T) { + temp, err := os.CreateTemp(t.TempDir(), "test.log") + require.NoError(t, err) + + // Drop timestamps for simpler assertions (timestamp encoding is tested + // elsewhere). + cfg := testEncoderConfig() + cfg.TimeKey = "" + + core := NewCore( + NewJSONEncoder(cfg), + temp, + InfoLevel, + ).With([]Field{makeInt64Field("k", 1)}) + defer assert.NoError(t, core.Sync(), "Expected Syncing a temp file to succeed.") + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, InfoLevel, LevelOf(core), "Incorrect Core Level") + }) + + if ce := core.Check(Entry{Level: DebugLevel, Message: "debug"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 2)) + } + if ce := core.Check(Entry{Level: InfoLevel, Message: "info"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 3)) + } + if ce := core.Check(Entry{Level: WarnLevel, Message: "warn"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 4)) + } + + logged, err := os.ReadFile(temp.Name()) + require.NoError(t, err, "Failed to read from temp file.") + require.Equal( + t, + `{"level":"info","msg":"info","k":1,"k":3}`+"\n"+ + `{"level":"warn","msg":"warn","k":1,"k":4}`+"\n", + string(logged), + "Unexpected log output.", + ) +} + +func TestIOCoreSyncFail(t *testing.T) { + sink := &ztest.Discarder{} + err := errors.New("failed") + sink.SetError(err) + + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + + assert.Equal( + t, + err, + core.Sync(), + "Expected core.Sync to return errors from underlying WriteSyncer.", + ) +} + +func TestIOCoreSyncsOutput(t *testing.T) { + tests := []struct { + entry Entry + shouldSync bool + }{ + {Entry{Level: DebugLevel}, false}, + {Entry{Level: InfoLevel}, false}, + {Entry{Level: WarnLevel}, false}, + {Entry{Level: ErrorLevel}, false}, + {Entry{Level: DPanicLevel}, true}, + {Entry{Level: PanicLevel}, true}, + {Entry{Level: FatalLevel}, true}, + } + + for _, tt := range tests { + sink := &ztest.Discarder{} + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + + core.Write(tt.entry, nil) + assert.Equal(t, tt.shouldSync, sink.Called(), "Incorrect Sync behavior.") + } +} + +func TestIOCoreWriteFailure(t *testing.T) { + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + Lock(&ztest.FailWriter{}), + DebugLevel, + ) + err := core.Write(Entry{}, nil) + // Should log the error. + assert.Error(t, err, "Expected writing Entry to fail.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/doc.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/doc.go new file mode 100644 index 0000000..31000e9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapcore defines and implements the low-level interfaces upon which +// zap is built. By providing alternate implementations of these interfaces, +// external packages can extend zap's capabilities. +package zapcore // import "go.uber.org/zap/zapcore" diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/encoder.go new file mode 100644 index 0000000..5769ff3 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/encoder.go @@ -0,0 +1,451 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "io" + "time" + + "go.uber.org/zap/buffer" +) + +// DefaultLineEnding defines the default line ending when writing logs. +// Alternate line endings specified in EncoderConfig can override this +// behavior. +const DefaultLineEnding = "\n" + +// OmitKey defines the key to use when callers want to remove a key from log output. +const OmitKey = "" + +// A LevelEncoder serializes a Level to a primitive type. +type LevelEncoder func(Level, PrimitiveArrayEncoder) + +// LowercaseLevelEncoder serializes a Level to a lowercase string. For example, +// InfoLevel is serialized to "info". +func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.String()) +} + +// LowercaseColorLevelEncoder serializes a Level to a lowercase string and adds coloring. +// For example, InfoLevel is serialized to "info" and colored blue. +func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToLowercaseColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.String()) + } + enc.AppendString(s) +} + +// CapitalLevelEncoder serializes a Level to an all-caps string. For example, +// InfoLevel is serialized to "INFO". +func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.CapitalString()) +} + +// CapitalColorLevelEncoder serializes a Level to an all-caps string and adds color. +// For example, InfoLevel is serialized to "INFO" and colored blue. +func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToCapitalColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.CapitalString()) + } + enc.AppendString(s) +} + +// UnmarshalText unmarshals text to a LevelEncoder. "capital" is unmarshaled to +// CapitalLevelEncoder, "coloredCapital" is unmarshaled to CapitalColorLevelEncoder, +// "colored" is unmarshaled to LowercaseColorLevelEncoder, and anything else +// is unmarshaled to LowercaseLevelEncoder. +func (e *LevelEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "capital": + *e = CapitalLevelEncoder + case "capitalColor": + *e = CapitalColorLevelEncoder + case "color": + *e = LowercaseColorLevelEncoder + default: + *e = LowercaseLevelEncoder + } + return nil +} + +// A TimeEncoder serializes a time.Time to a primitive type. +type TimeEncoder func(time.Time, PrimitiveArrayEncoder) + +// EpochTimeEncoder serializes a time.Time to a floating-point number of seconds +// since the Unix epoch. +func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + sec := float64(nanos) / float64(time.Second) + enc.AppendFloat64(sec) +} + +// EpochMillisTimeEncoder serializes a time.Time to a floating-point number of +// milliseconds since the Unix epoch. +func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + millis := float64(nanos) / float64(time.Millisecond) + enc.AppendFloat64(millis) +} + +// EpochNanosTimeEncoder serializes a time.Time to an integer number of +// nanoseconds since the Unix epoch. +func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + enc.AppendInt64(t.UnixNano()) +} + +func encodeTimeLayout(t time.Time, layout string, enc PrimitiveArrayEncoder) { + type appendTimeEncoder interface { + AppendTimeLayout(time.Time, string) + } + + if enc, ok := enc.(appendTimeEncoder); ok { + enc.AppendTimeLayout(t, layout) + return + } + + enc.AppendString(t.Format(layout)) +} + +// ISO8601TimeEncoder serializes a time.Time to an ISO8601-formatted string +// with millisecond precision. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, "2006-01-02T15:04:05.000Z0700", enc) +} + +// RFC3339TimeEncoder serializes a time.Time to an RFC3339-formatted string. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func RFC3339TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, time.RFC3339, enc) +} + +// RFC3339NanoTimeEncoder serializes a time.Time to an RFC3339-formatted string +// with nanosecond precision. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func RFC3339NanoTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, time.RFC3339Nano, enc) +} + +// TimeEncoderOfLayout returns TimeEncoder which serializes a time.Time using +// given layout. +func TimeEncoderOfLayout(layout string) TimeEncoder { + return func(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, layout, enc) + } +} + +// UnmarshalText unmarshals text to a TimeEncoder. +// "rfc3339nano" and "RFC3339Nano" are unmarshaled to RFC3339NanoTimeEncoder. +// "rfc3339" and "RFC3339" are unmarshaled to RFC3339TimeEncoder. +// "iso8601" and "ISO8601" are unmarshaled to ISO8601TimeEncoder. +// "millis" is unmarshaled to EpochMillisTimeEncoder. +// "nanos" is unmarshaled to EpochNanosEncoder. +// Anything else is unmarshaled to EpochTimeEncoder. +func (e *TimeEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "rfc3339nano", "RFC3339Nano": + *e = RFC3339NanoTimeEncoder + case "rfc3339", "RFC3339": + *e = RFC3339TimeEncoder + case "iso8601", "ISO8601": + *e = ISO8601TimeEncoder + case "millis": + *e = EpochMillisTimeEncoder + case "nanos": + *e = EpochNanosTimeEncoder + default: + *e = EpochTimeEncoder + } + return nil +} + +// UnmarshalYAML unmarshals YAML to a TimeEncoder. +// If value is an object with a "layout" field, it will be unmarshaled to TimeEncoder with given layout. +// +// timeEncoder: +// layout: 06/01/02 03:04pm +// +// If value is string, it uses UnmarshalText. +// +// timeEncoder: iso8601 +func (e *TimeEncoder) UnmarshalYAML(unmarshal func(interface{}) error) error { + var o struct { + Layout string `json:"layout" yaml:"layout"` + } + if err := unmarshal(&o); err == nil { + *e = TimeEncoderOfLayout(o.Layout) + return nil + } + + var s string + if err := unmarshal(&s); err != nil { + return err + } + return e.UnmarshalText([]byte(s)) +} + +// UnmarshalJSON unmarshals JSON to a TimeEncoder as same way UnmarshalYAML does. +func (e *TimeEncoder) UnmarshalJSON(data []byte) error { + return e.UnmarshalYAML(func(v interface{}) error { + return json.Unmarshal(data, v) + }) +} + +// A DurationEncoder serializes a time.Duration to a primitive type. +type DurationEncoder func(time.Duration, PrimitiveArrayEncoder) + +// SecondsDurationEncoder serializes a time.Duration to a floating-point number of seconds elapsed. +func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendFloat64(float64(d) / float64(time.Second)) +} + +// NanosDurationEncoder serializes a time.Duration to an integer number of +// nanoseconds elapsed. +func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendInt64(int64(d)) +} + +// MillisDurationEncoder serializes a time.Duration to an integer number of +// milliseconds elapsed. +func MillisDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendInt64(d.Nanoseconds() / 1e6) +} + +// StringDurationEncoder serializes a time.Duration using its built-in String +// method. +func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendString(d.String()) +} + +// UnmarshalText unmarshals text to a DurationEncoder. "string" is unmarshaled +// to StringDurationEncoder, and anything else is unmarshaled to +// NanosDurationEncoder. +func (e *DurationEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "string": + *e = StringDurationEncoder + case "nanos": + *e = NanosDurationEncoder + case "ms": + *e = MillisDurationEncoder + default: + *e = SecondsDurationEncoder + } + return nil +} + +// A CallerEncoder serializes an EntryCaller to a primitive type. +type CallerEncoder func(EntryCaller, PrimitiveArrayEncoder) + +// FullCallerEncoder serializes a caller in /full/path/to/package/file:line +// format. +func FullCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.String()) +} + +// ShortCallerEncoder serializes a caller in package/file:line format, trimming +// all but the final directory from the full path. +func ShortCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.TrimmedPath()) +} + +// UnmarshalText unmarshals text to a CallerEncoder. "full" is unmarshaled to +// FullCallerEncoder and anything else is unmarshaled to ShortCallerEncoder. +func (e *CallerEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullCallerEncoder + default: + *e = ShortCallerEncoder + } + return nil +} + +// A NameEncoder serializes a period-separated logger name to a primitive +// type. +type NameEncoder func(string, PrimitiveArrayEncoder) + +// FullNameEncoder serializes the logger name as-is. +func FullNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { + enc.AppendString(loggerName) +} + +// UnmarshalText unmarshals text to a NameEncoder. Currently, everything is +// unmarshaled to FullNameEncoder. +func (e *NameEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullNameEncoder + default: + *e = FullNameEncoder + } + return nil +} + +// An EncoderConfig allows users to configure the concrete encoders supplied by +// zapcore. +type EncoderConfig struct { + // Set the keys used for each log entry. If any key is empty, that portion + // of the entry is omitted. + MessageKey string `json:"messageKey" yaml:"messageKey"` + LevelKey string `json:"levelKey" yaml:"levelKey"` + TimeKey string `json:"timeKey" yaml:"timeKey"` + NameKey string `json:"nameKey" yaml:"nameKey"` + CallerKey string `json:"callerKey" yaml:"callerKey"` + FunctionKey string `json:"functionKey" yaml:"functionKey"` + StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` + SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"` + LineEnding string `json:"lineEnding" yaml:"lineEnding"` + // Configure the primitive representations of common complex types. For + // example, some users may want all time.Times serialized as floating-point + // seconds since epoch, while others may prefer ISO8601 strings. + EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` + EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` + EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` + EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` + // Unlike the other primitive type encoders, EncodeName is optional. The + // zero value falls back to FullNameEncoder. + EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` + // Configure the encoder for interface{} type objects. + // If not provided, objects are encoded using json.Encoder + NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"` + // Configures the field separator used by the console encoder. Defaults + // to tab. + ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` +} + +// ObjectEncoder is a strongly-typed, encoding-agnostic interface for adding a +// map- or struct-like object to the logging context. Like maps, ObjectEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ObjectEncoder interface { + // Logging-specific marshalers. + AddArray(key string, marshaler ArrayMarshaler) error + AddObject(key string, marshaler ObjectMarshaler) error + + // Built-in types. + AddBinary(key string, value []byte) // for arbitrary bytes + AddByteString(key string, value []byte) // for UTF-8 encoded bytes + AddBool(key string, value bool) + AddComplex128(key string, value complex128) + AddComplex64(key string, value complex64) + AddDuration(key string, value time.Duration) + AddFloat64(key string, value float64) + AddFloat32(key string, value float32) + AddInt(key string, value int) + AddInt64(key string, value int64) + AddInt32(key string, value int32) + AddInt16(key string, value int16) + AddInt8(key string, value int8) + AddString(key, value string) + AddTime(key string, value time.Time) + AddUint(key string, value uint) + AddUint64(key string, value uint64) + AddUint32(key string, value uint32) + AddUint16(key string, value uint16) + AddUint8(key string, value uint8) + AddUintptr(key string, value uintptr) + + // AddReflected uses reflection to serialize arbitrary objects, so it can be + // slow and allocation-heavy. + AddReflected(key string, value interface{}) error + // OpenNamespace opens an isolated namespace where all subsequent fields will + // be added. Applications can use namespaces to prevent key collisions when + // injecting loggers into sub-components or third-party libraries. + OpenNamespace(key string) +} + +// ArrayEncoder is a strongly-typed, encoding-agnostic interface for adding +// array-like objects to the logging context. Of note, it supports mixed-type +// arrays even though they aren't typical in Go. Like slices, ArrayEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ArrayEncoder interface { + // Built-in types. + PrimitiveArrayEncoder + + // Time-related types. + AppendDuration(time.Duration) + AppendTime(time.Time) + + // Logging-specific marshalers. + AppendArray(ArrayMarshaler) error + AppendObject(ObjectMarshaler) error + + // AppendReflected uses reflection to serialize arbitrary objects, so it's + // slow and allocation-heavy. + AppendReflected(value interface{}) error +} + +// PrimitiveArrayEncoder is the subset of the ArrayEncoder interface that deals +// only in Go's built-in types. It's included only so that Duration- and +// TimeEncoders cannot trigger infinite recursion. +type PrimitiveArrayEncoder interface { + // Built-in types. + AppendBool(bool) + AppendByteString([]byte) // for UTF-8 encoded bytes + AppendComplex128(complex128) + AppendComplex64(complex64) + AppendFloat64(float64) + AppendFloat32(float32) + AppendInt(int) + AppendInt64(int64) + AppendInt32(int32) + AppendInt16(int16) + AppendInt8(int8) + AppendString(string) + AppendUint(uint) + AppendUint64(uint64) + AppendUint32(uint32) + AppendUint16(uint16) + AppendUint8(uint8) + AppendUintptr(uintptr) +} + +// Encoder is a format-agnostic interface for all log entry marshalers. Since +// log encoders don't need to support the same wide range of use cases as +// general-purpose marshalers, it's possible to make them faster and +// lower-allocation. +// +// Implementations of the ObjectEncoder interface's methods can, of course, +// freely modify the receiver. However, the Clone and EncodeEntry methods will +// be called concurrently and shouldn't modify the receiver. +type Encoder interface { + ObjectEncoder + + // Clone copies the encoder, ensuring that adding fields to the copy doesn't + // affect the original. + Clone() Encoder + + // EncodeEntry encodes an entry and fields, along with any accumulated + // context, into a byte buffer and returns it. Any fields that are empty, + // including fields on the `Entry` type, should be omitted. + EncodeEntry(Entry, []Field) (*buffer.Buffer, error) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/encoder_test.go new file mode 100644 index 0000000..c0dbc5b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/encoder_test.go @@ -0,0 +1,730 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "encoding/json" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + + . "go.uber.org/zap/zapcore" +) + +var ( + _epoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) + _testEntry = Entry{ + LoggerName: "main", + Level: InfoLevel, + Message: `hello`, + Time: _epoch, + Stack: "fake-stack", + Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"}, + } +) + +func testEncoderConfig() EncoderConfig { + return EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "name", + TimeKey: "ts", + CallerKey: "caller", + FunctionKey: "func", + StacktraceKey: "stacktrace", + LineEnding: "\n", + EncodeTime: EpochTimeEncoder, + EncodeLevel: LowercaseLevelEncoder, + EncodeDuration: SecondsDurationEncoder, + EncodeCaller: ShortCallerEncoder, + } +} + +func humanEncoderConfig() EncoderConfig { + cfg := testEncoderConfig() + cfg.EncodeTime = ISO8601TimeEncoder + cfg.EncodeLevel = CapitalLevelEncoder + cfg.EncodeDuration = StringDurationEncoder + return cfg +} + +func capitalNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { + enc.AppendString(strings.ToUpper(loggerName)) +} + +func TestEncoderConfiguration(t *testing.T) { + base := testEncoderConfig() + + tests := []struct { + desc string + cfg EncoderConfig + amendEntry func(Entry) Entry + extra func(Encoder) + expectedJSON string + expectedConsole string + }{ + { + desc: "messages to be escaped", + cfg: base, + amendEntry: func(ent Entry) Entry { + ent.Message = `hello\` + return ent + }, + expectedJSON: `{"level":"info","ts":0,"name":"main","caller":"foo.go:42","func":"foo.Foo","msg":"hello\\","stacktrace":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\\\nfake-stack\n", + }, + { + desc: "use custom entry keys in JSON output and ignore them in console output", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip line ending if SkipLineEnding is 'true'", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + SkipLineEnding: true, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}`, + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack", + }, + { + desc: "skip level if LevelKey is omitted", + cfg: EncoderConfig{ + LevelKey: OmitKey, + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip timestamp if TimeKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: OmitKey, + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip message if MessageKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: OmitKey, + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\nfake-stack\n", + }, + { + desc: "skip name if NameKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: OmitKey, + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip caller if CallerKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: OmitKey, + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip function if FunctionKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: OmitKey, + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\n", + }, + { + desc: "skip stacktrace if StacktraceKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: OmitKey, + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\n", + }, + { + desc: "use the supplied EncodeTime, for both the entry and any times added", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) }, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.AddTime("extra", _epoch) + enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendTime(_epoch) + return nil + })) + }, + expectedJSON: `{"L":"info","T":"1970-01-01 00:00:00 +0000 UTC","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1970-01-01 00:00:00 +0000 UTC","extras":["1970-01-01 00:00:00 +0000 UTC"],"S":"fake-stack"}` + "\n", + expectedConsole: "1970-01-01 00:00:00 +0000 UTC\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // plain-text preamble + `{"extra": "1970-01-01 00:00:00 +0000 UTC", "extras": ["1970-01-01 00:00:00 +0000 UTC"]}` + // JSON context + "\nfake-stack\n", // stacktrace after newline + }, + { + desc: "use the supplied EncodeDuration for any durations added", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: StringDurationEncoder, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.AddDuration("extra", time.Second) + enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendDuration(time.Minute) + return nil + })) + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1s","extras":["1m0s"],"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // preamble + `{"extra": "1s", "extras": ["1m0s"]}` + // context + "\nfake-stack\n", // stacktrace + }, + { + desc: "use the supplied EncodeLevel", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: CapitalLevelEncoder, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tINFO\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "use the supplied EncodeName", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeName: capitalNameEncoder, + }, + expectedJSON: `{"L":"info","T":0,"N":"MAIN","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tMAIN\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "close all open namespaces", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.OpenNamespace("outer") + enc.OpenNamespace("inner") + enc.AddString("foo", "bar") + enc.OpenNamespace("innermost") + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","outer":{"inner":{"foo":"bar","innermost":{}}},"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + + `{"outer": {"inner": {"foo": "bar", "innermost": {}}}}` + + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeTime", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { enc.AddTime("sometime", time.Unix(0, 100)) }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","sometime":100,"S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"sometime": 100}` + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeDuration", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {}, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { enc.AddDuration("someduration", time.Microsecond) }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","someduration":1000,"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"someduration": 1000}` + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeLevel", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "handle no-op EncodeCaller", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {}, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "handle no-op EncodeName", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeName: func(string, PrimitiveArrayEncoder) {}, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "use custom line separator", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: "\r\n", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\r\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\r\n", + }, + { + desc: "omit line separator definition - fall back to default", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + DefaultLineEnding, + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack" + DefaultLineEnding, + }, + } + + for i, tt := range tests { + json := NewJSONEncoder(tt.cfg) + console := NewConsoleEncoder(tt.cfg) + if tt.extra != nil { + tt.extra(json) + tt.extra(console) + } + entry := _testEntry + if tt.amendEntry != nil { + entry = tt.amendEntry(_testEntry) + } + jsonOut, jsonErr := json.EncodeEntry(entry, nil) + if assert.NoError(t, jsonErr, "Unexpected error JSON-encoding entry in case #%d.", i) { + assert.Equal( + t, + tt.expectedJSON, + jsonOut.String(), + "Unexpected JSON output: expected to %v.", tt.desc, + ) + } + consoleOut, consoleErr := console.EncodeEntry(entry, nil) + if assert.NoError(t, consoleErr, "Unexpected error console-encoding entry in case #%d.", i) { + assert.Equal( + t, + tt.expectedConsole, + consoleOut.String(), + "Unexpected console output: expected to %v.", tt.desc, + ) + } + } +} + +func TestLevelEncoders(t *testing.T) { + tests := []struct { + name string + expected interface{} // output of encoding InfoLevel + }{ + {"capital", "INFO"}, + {"lower", "info"}, + {"", "info"}, + {"something-random", "info"}, + } + + for _, tt := range tests { + var le LevelEncoder + require.NoError(t, le.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { le(InfoLevel, arr) }, + "Unexpected output serializing InfoLevel with %q.", tt.name, + ) + } +} + +func TestTimeEncoders(t *testing.T) { + moment := time.Unix(100, 50005000).UTC() + tests := []struct { + yamlDoc string + expected interface{} // output of serializing moment + }{ + {"timeEncoder: iso8601", "1970-01-01T00:01:40.050Z"}, + {"timeEncoder: ISO8601", "1970-01-01T00:01:40.050Z"}, + {"timeEncoder: millis", 100050.005}, + {"timeEncoder: nanos", int64(100050005000)}, + {"timeEncoder: {layout: 06/01/02 03:04pm}", "70/01/01 12:01am"}, + {"timeEncoder: ''", 100.050005}, + {"timeEncoder: something-random", 100.050005}, + {"timeEncoder: rfc3339", "1970-01-01T00:01:40Z"}, + {"timeEncoder: RFC3339", "1970-01-01T00:01:40Z"}, + {"timeEncoder: rfc3339nano", "1970-01-01T00:01:40.050005Z"}, + {"timeEncoder: RFC3339Nano", "1970-01-01T00:01:40.050005Z"}, + } + + for _, tt := range tests { + cfg := EncoderConfig{} + require.NoError(t, yaml.Unmarshal([]byte(tt.yamlDoc), &cfg), "Unexpected error unmarshaling %q.", tt.yamlDoc) + require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.yamlDoc) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, + "Unexpected output serializing %v with %q.", moment, tt.yamlDoc, + ) + } +} + +func TestTimeEncodersWrongYAML(t *testing.T) { + tests := []string{ + "timeEncoder: [1, 2, 3]", // wrong type + "timeEncoder: {foo:bar", // broken yaml + } + for _, tt := range tests { + cfg := EncoderConfig{} + assert.Error(t, yaml.Unmarshal([]byte(tt), &cfg), "Expected unmarshaling %q to become error, but not.", tt) + } +} + +func TestTimeEncodersParseFromJSON(t *testing.T) { + moment := time.Unix(100, 50005000).UTC() + tests := []struct { + jsonDoc string + expected interface{} // output of serializing moment + }{ + {`{"timeEncoder": "iso8601"}`, "1970-01-01T00:01:40.050Z"}, + {`{"timeEncoder": {"layout": "06/01/02 03:04pm"}}`, "70/01/01 12:01am"}, + } + + for _, tt := range tests { + cfg := EncoderConfig{} + require.NoError(t, json.Unmarshal([]byte(tt.jsonDoc), &cfg), "Unexpected error unmarshaling %q.", tt.jsonDoc) + require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.jsonDoc) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, + "Unexpected output serializing %v with %q.", moment, tt.jsonDoc, + ) + } +} + +func TestDurationEncoders(t *testing.T) { + elapsed := time.Second + 500*time.Nanosecond + tests := []struct { + name string + expected interface{} // output of serializing elapsed + }{ + {"string", "1.0000005s"}, + {"nanos", int64(1000000500)}, + {"ms", int64(1000)}, + {"", 1.0000005}, + {"something-random", 1.0000005}, + } + + for _, tt := range tests { + var de DurationEncoder + require.NoError(t, de.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { de(elapsed, arr) }, + "Unexpected output serializing %v with %q.", elapsed, tt.name, + ) + } +} + +func TestCallerEncoders(t *testing.T) { + caller := EntryCaller{Defined: true, File: "/home/jack/src/github.com/foo/foo.go", Line: 42} + tests := []struct { + name string + expected interface{} // output of serializing caller + }{ + {"", "foo/foo.go:42"}, + {"something-random", "foo/foo.go:42"}, + {"short", "foo/foo.go:42"}, + {"full", "/home/jack/src/github.com/foo/foo.go:42"}, + } + + for _, tt := range tests { + var ce CallerEncoder + require.NoError(t, ce.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { ce(caller, arr) }, + "Unexpected output serializing file name as %v with %q.", tt.expected, tt.name, + ) + } +} + +func TestNameEncoders(t *testing.T) { + tests := []struct { + name string + expected interface{} // output of encoding InfoLevel + }{ + {"", "main"}, + {"full", "main"}, + {"something-random", "main"}, + } + + for _, tt := range tests { + var ne NameEncoder + require.NoError(t, ne.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { ne("main", arr) }, + "Unexpected output serializing logger name with %q.", tt.name, + ) + } +} + +func assertAppended(t testing.TB, expected interface{}, f func(ArrayEncoder), msgAndArgs ...interface{}) { + mem := NewMapObjectEncoder() + mem.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + f(arr) + return nil + })) + arr := mem.Fields["k"].([]interface{}) + require.Equal(t, 1, len(arr), "Expected to append exactly one element to array.") + assert.Equal(t, expected, arr[0], msgAndArgs...) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/entry.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/entry.go new file mode 100644 index 0000000..ea0431e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/entry.go @@ -0,0 +1,299 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "runtime" + "strings" + "sync" + "time" + + "go.uber.org/multierr" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/exit" +) + +var ( + _cePool = sync.Pool{New: func() interface{} { + // Pre-allocate some space for cores. + return &CheckedEntry{ + cores: make([]Core, 4), + } + }} +) + +func getCheckedEntry() *CheckedEntry { + ce := _cePool.Get().(*CheckedEntry) + ce.reset() + return ce +} + +func putCheckedEntry(ce *CheckedEntry) { + if ce == nil { + return + } + _cePool.Put(ce) +} + +// NewEntryCaller makes an EntryCaller from the return signature of +// runtime.Caller. +func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller { + if !ok { + return EntryCaller{} + } + return EntryCaller{ + PC: pc, + File: file, + Line: line, + Defined: true, + } +} + +// EntryCaller represents the caller of a logging function. +type EntryCaller struct { + Defined bool + PC uintptr + File string + Line int + Function string +} + +// String returns the full path and line number of the caller. +func (ec EntryCaller) String() string { + return ec.FullPath() +} + +// FullPath returns a /full/path/to/package/file:line description of the +// caller. +func (ec EntryCaller) FullPath() string { + if !ec.Defined { + return "undefined" + } + buf := bufferpool.Get() + buf.AppendString(ec.File) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// TrimmedPath returns a package/file:line description of the caller, +// preserving only the leaf directory name and file name. +func (ec EntryCaller) TrimmedPath() string { + if !ec.Defined { + return "undefined" + } + // nb. To make sure we trim the path correctly on Windows too, we + // counter-intuitively need to use '/' and *not* os.PathSeparator here, + // because the path given originates from Go stdlib, specifically + // runtime.Caller() which (as of Mar/17) returns forward slashes even on + // Windows. + // + // See https://github.com/golang/go/issues/3335 + // and https://github.com/golang/go/issues/18151 + // + // for discussion on the issue on Go side. + // + // Find the last separator. + // + idx := strings.LastIndexByte(ec.File, '/') + if idx == -1 { + return ec.FullPath() + } + // Find the penultimate separator. + idx = strings.LastIndexByte(ec.File[:idx], '/') + if idx == -1 { + return ec.FullPath() + } + buf := bufferpool.Get() + // Keep everything after the penultimate separator. + buf.AppendString(ec.File[idx+1:]) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// An Entry represents a complete log message. The entry's structured context +// is already serialized, but the log level, time, message, and call site +// information are available for inspection and modification. Any fields left +// empty will be omitted when encoding. +// +// Entries are pooled, so any functions that accept them MUST be careful not to +// retain references to them. +type Entry struct { + Level Level + Time time.Time + LoggerName string + Message string + Caller EntryCaller + Stack string +} + +// CheckWriteHook is a custom action that may be executed after an entry is +// written. +// +// Register one on a CheckedEntry with the After method. +// +// if ce := logger.Check(...); ce != nil { +// ce = ce.After(hook) +// ce.Write(...) +// } +// +// You can configure the hook for Fatal log statements at the logger level with +// the zap.WithFatalHook option. +type CheckWriteHook interface { + // OnWrite is invoked with the CheckedEntry that was written and a list + // of fields added with that entry. + // + // The list of fields DOES NOT include fields that were already added + // to the logger with the With method. + OnWrite(*CheckedEntry, []Field) +} + +// CheckWriteAction indicates what action to take after a log entry is +// processed. Actions are ordered in increasing severity. +type CheckWriteAction uint8 + +const ( + // WriteThenNoop indicates that nothing special needs to be done. It's the + // default behavior. + WriteThenNoop CheckWriteAction = iota + // WriteThenGoexit runs runtime.Goexit after Write. + WriteThenGoexit + // WriteThenPanic causes a panic after Write. + WriteThenPanic + // WriteThenFatal causes an os.Exit(1) after Write. + WriteThenFatal +) + +// OnWrite implements the OnWrite method to keep CheckWriteAction compatible +// with the new CheckWriteHook interface which deprecates CheckWriteAction. +func (a CheckWriteAction) OnWrite(ce *CheckedEntry, _ []Field) { + switch a { + case WriteThenGoexit: + runtime.Goexit() + case WriteThenPanic: + panic(ce.Message) + case WriteThenFatal: + exit.With(1) + } +} + +var _ CheckWriteHook = CheckWriteAction(0) + +// CheckedEntry is an Entry together with a collection of Cores that have +// already agreed to log it. +// +// CheckedEntry references should be created by calling AddCore or After on a +// nil *CheckedEntry. References are returned to a pool after Write, and MUST +// NOT be retained after calling their Write method. +type CheckedEntry struct { + Entry + ErrorOutput WriteSyncer + dirty bool // best-effort detection of pool misuse + after CheckWriteHook + cores []Core +} + +func (ce *CheckedEntry) reset() { + ce.Entry = Entry{} + ce.ErrorOutput = nil + ce.dirty = false + ce.after = nil + for i := range ce.cores { + // don't keep references to cores + ce.cores[i] = nil + } + ce.cores = ce.cores[:0] +} + +// Write writes the entry to the stored Cores, returns any errors, and returns +// the CheckedEntry reference to a pool for immediate re-use. Finally, it +// executes any required CheckWriteAction. +func (ce *CheckedEntry) Write(fields ...Field) { + if ce == nil { + return + } + + if ce.dirty { + if ce.ErrorOutput != nil { + // Make a best effort to detect unsafe re-use of this CheckedEntry. + // If the entry is dirty, log an internal error; because the + // CheckedEntry is being used after it was returned to the pool, + // the message may be an amalgamation from multiple call sites. + fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", ce.Time, ce.Entry) + ce.ErrorOutput.Sync() + } + return + } + ce.dirty = true + + var err error + for i := range ce.cores { + err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields)) + } + if err != nil && ce.ErrorOutput != nil { + fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", ce.Time, err) + ce.ErrorOutput.Sync() + } + + hook := ce.after + if hook != nil { + hook.OnWrite(ce, fields) + } + putCheckedEntry(ce) +} + +// AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be +// used by Core.Check implementations, and is safe to call on nil CheckedEntry +// references. +func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.cores = append(ce.cores, core) + return ce +} + +// Should sets this CheckedEntry's CheckWriteAction, which controls whether a +// Core will panic or fatal after writing this log entry. Like AddCore, it's +// safe to call on nil CheckedEntry references. +// Deprecated: Use After(ent Entry, after CheckWriteHook) instead. +func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry { + return ce.After(ent, should) +} + +// After sets this CheckEntry's CheckWriteHook, which will be called after this +// log entry has been written. It's safe to call this on nil CheckedEntry +// references. +func (ce *CheckedEntry) After(ent Entry, hook CheckWriteHook) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.after = hook + return ce +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/entry_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/entry_test.go new file mode 100644 index 0000000..6555ab6 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/entry_test.go @@ -0,0 +1,149 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "sync" + "testing" + + "go.uber.org/zap/internal/exit" + + "github.com/stretchr/testify/assert" +) + +func assertGoexit(t *testing.T, f func()) { + var finished bool + recovered := make(chan interface{}) + go func() { + defer func() { + recovered <- recover() + }() + + f() + finished = true + }() + + assert.Nil(t, <-recovered, "Goexit should cause recover to return nil") + assert.False(t, finished, "Goroutine should not finish after Goexit") +} + +func TestPutNilEntry(t *testing.T) { + // Pooling nil entries defeats the purpose. + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + putCheckedEntry(nil) + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + ce := getCheckedEntry() + assert.NotNil(t, ce, "Expected only non-nil CheckedEntries in pool.") + assert.False(t, ce.dirty, "Unexpected dirty bit set.") + assert.Nil(t, ce.ErrorOutput, "Non-nil ErrorOutput.") + assert.Nil(t, ce.after, "Unexpected terminal behavior.") + assert.Equal(t, 0, len(ce.cores), "Expected empty slice of cores.") + assert.True(t, cap(ce.cores) > 0, "Expected pooled CheckedEntries to pre-allocate slice of Cores.") + } + }() + + wg.Wait() +} + +func TestEntryCaller(t *testing.T) { + tests := []struct { + caller EntryCaller + full string + short string + }{ + { + caller: NewEntryCaller(100, "/path/to/foo.go", 42, false), + full: "undefined", + short: "undefined", + }, + { + caller: NewEntryCaller(100, "/path/to/foo.go", 42, true), + full: "/path/to/foo.go:42", + short: "to/foo.go:42", + }, + { + caller: NewEntryCaller(100, "to/foo.go", 42, true), + full: "to/foo.go:42", + short: "to/foo.go:42", + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.full, tt.caller.String(), "Unexpected string from EntryCaller.") + assert.Equal(t, tt.full, tt.caller.FullPath(), "Unexpected FullPath from EntryCaller.") + assert.Equal(t, tt.short, tt.caller.TrimmedPath(), "Unexpected TrimmedPath from EntryCaller.") + } +} + +func TestCheckedEntryWrite(t *testing.T) { + t.Run("nil is safe", func(t *testing.T) { + var ce *CheckedEntry + assert.NotPanics(t, func() { ce.Write() }, "Unexpected panic writing nil CheckedEntry.") + }) + + t.Run("WriteThenPanic", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenPanic) + assert.Panics(t, func() { ce.Write() }, "Expected to panic when WriteThenPanic is set.") + }) + + t.Run("WriteThenGoexit", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenGoexit) + assertGoexit(t, func() { ce.Write() }) + }) + + t.Run("WriteThenFatal", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenFatal) + stub := exit.WithStub(func() { + ce.Write() + }) + assert.True(t, stub.Exited, "Expected to exit when WriteThenFatal is set.") + assert.Equal(t, 1, stub.Code, "Expected to exit when WriteThenFatal is set.") + }) + + t.Run("After", func(t *testing.T) { + var ce *CheckedEntry + hook := &customHook{} + ce = ce.After(Entry{}, hook) + ce.Write() + assert.True(t, hook.called, "Expected to call custom action after Write.") + }) +} + +type customHook struct { + called bool +} + +func (c *customHook) OnWrite(_ *CheckedEntry, _ []Field) { + c.called = true +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/error.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/error.go new file mode 100644 index 0000000..0635990 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/error.go @@ -0,0 +1,132 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "reflect" + "sync" +) + +// Encodes the given error into fields of an object. A field with the given +// name is added for the error message. +// +// If the error implements fmt.Formatter, a field with the name ${key}Verbose +// is also added with the full verbose error message. +// +// Finally, if the error implements errorGroup (from go.uber.org/multierr) or +// causer (from github.com/pkg/errors), a ${key}Causes field is added with an +// array of objects containing the errors this error was comprised of. +// +// { +// "error": err.Error(), +// "errorVerbose": fmt.Sprintf("%+v", err), +// "errorCauses": [ +// ... +// ], +// } +func encodeError(key string, err error, enc ObjectEncoder) (retErr error) { + // Try to capture panics (from nil references or otherwise) when calling + // the Error() method + defer func() { + if rerr := recover(); rerr != nil { + // If it's a nil pointer, just say "<nil>". The likeliest causes are a + // error that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "<nil>" is a nice result. + if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { + enc.AddString(key, "<nil>") + return + } + + retErr = fmt.Errorf("PANIC=%v", rerr) + } + }() + + basic := err.Error() + enc.AddString(key, basic) + + switch e := err.(type) { + case errorGroup: + return enc.AddArray(key+"Causes", errArray(e.Errors())) + case fmt.Formatter: + verbose := fmt.Sprintf("%+v", e) + if verbose != basic { + // This is a rich error type, like those produced by + // github.com/pkg/errors. + enc.AddString(key+"Verbose", verbose) + } + } + return nil +} + +type errorGroup interface { + // Provides read-only access to the underlying list of errors, preferably + // without causing any allocs. + Errors() []error +} + +// Note that errArray and errArrayElem are very similar to the version +// implemented in the top-level error.go file. We can't re-use this because +// that would require exporting errArray as part of the zapcore API. + +// Encodes a list of errors using the standard error encoding logic. +type errArray []error + +func (errs errArray) MarshalLogArray(arr ArrayEncoder) error { + for i := range errs { + if errs[i] == nil { + continue + } + + el := newErrArrayElem(errs[i]) + arr.AppendObject(el) + el.Free() + } + return nil +} + +var _errArrayElemPool = sync.Pool{New: func() interface{} { + return &errArrayElem{} +}} + +// Encodes any error into a {"error": ...} re-using the same errors logic. +// +// May be passed in place of an array to build a single-element array. +type errArrayElem struct{ err error } + +func newErrArrayElem(err error) *errArrayElem { + e := _errArrayElemPool.Get().(*errArrayElem) + e.err = err + return e +} + +func (e *errArrayElem) MarshalLogArray(arr ArrayEncoder) error { + return arr.AppendObject(e) +} + +func (e *errArrayElem) MarshalLogObject(enc ObjectEncoder) error { + return encodeError("error", e.err, enc) +} + +func (e *errArrayElem) Free() { + e.err = nil + _errArrayElemPool.Put(e) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/error_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/error_test.go new file mode 100644 index 0000000..900f520 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/error_test.go @@ -0,0 +1,177 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "fmt" + "io" + "testing" + + richErrors "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + + "go.uber.org/multierr" + . "go.uber.org/zap/zapcore" +) + +type errTooManyUsers int + +func (e errTooManyUsers) Error() string { + return fmt.Sprintf("%d too many users", int(e)) +} + +func (e errTooManyUsers) Format(s fmt.State, verb rune) { + // Implement fmt.Formatter, but don't add any information beyond the basic + // Error method. + if verb == 'v' && s.Flag('+') { + io.WriteString(s, e.Error()) + } +} + +type customMultierr struct{} + +func (e customMultierr) Error() string { + return "great sadness" +} + +func (e customMultierr) Errors() []error { + return []error{ + errors.New("foo"), + nil, + multierr.Append( + errors.New("bar"), + errors.New("baz"), + ), + } +} + +func TestErrorEncoding(t *testing.T) { + tests := []struct { + k string + t FieldType // defaults to ErrorType + iface interface{} + want map[string]interface{} + }{ + { + k: "k", + iface: errTooManyUsers(2), + want: map[string]interface{}{ + "k": "2 too many users", + }, + }, + { + k: "err", + iface: multierr.Combine( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + want: map[string]interface{}{ + "err": "foo; bar; baz", + "errCauses": []interface{}{ + map[string]interface{}{"error": "foo"}, + map[string]interface{}{"error": "bar"}, + map[string]interface{}{"error": "baz"}, + }, + }, + }, + { + k: "e", + iface: customMultierr{}, + want: map[string]interface{}{ + "e": "great sadness", + "eCauses": []interface{}{ + map[string]interface{}{"error": "foo"}, + map[string]interface{}{ + "error": "bar; baz", + "errorCauses": []interface{}{ + map[string]interface{}{"error": "bar"}, + map[string]interface{}{"error": "baz"}, + }, + }, + }, + }, + }, + { + k: "k", + iface: richErrors.WithMessage(errors.New("egad"), "failed"), + want: map[string]interface{}{ + "k": "failed: egad", + "kVerbose": "egad\nfailed", + }, + }, + { + k: "error", + iface: multierr.Combine( + richErrors.WithMessage( + multierr.Combine(errors.New("foo"), errors.New("bar")), + "hello", + ), + errors.New("baz"), + richErrors.WithMessage(errors.New("qux"), "world"), + ), + want: map[string]interface{}{ + "error": "hello: foo; bar; baz; world: qux", + "errorCauses": []interface{}{ + map[string]interface{}{ + "error": "hello: foo; bar", + "errorVerbose": "the following errors occurred:\n" + + " - foo\n" + + " - bar\n" + + "hello", + }, + map[string]interface{}{"error": "baz"}, + map[string]interface{}{"error": "world: qux", "errorVerbose": "qux\nworld"}, + }, + }, + }, + } + + for _, tt := range tests { + if tt.t == UnknownType { + tt.t = ErrorType + } + + enc := NewMapObjectEncoder() + f := Field{Key: tt.k, Type: tt.t, Interface: tt.iface} + f.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields, "Unexpected output from field %+v.", f) + } +} + +func TestRichErrorSupport(t *testing.T) { + f := Field{ + Type: ErrorType, + Interface: richErrors.WithMessage(richErrors.New("egad"), "failed"), + Key: "k", + } + enc := NewMapObjectEncoder() + f.AddTo(enc) + assert.Equal(t, "failed: egad", enc.Fields["k"], "Unexpected basic error message.") + + serialized := enc.Fields["kVerbose"] + // Don't assert the exact format used by a third-party package, but ensure + // that some critical elements are present. + assert.Regexp(t, `egad`, serialized, "Expected original error message to be present.") + assert.Regexp(t, `failed`, serialized, "Expected error annotation to be present.") + assert.Regexp(t, `TestRichErrorSupport`, serialized, "Expected calling function to be present in stacktrace.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/field.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/field.go new file mode 100644 index 0000000..95bdb0a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/field.go @@ -0,0 +1,233 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "fmt" + "math" + "reflect" + "time" +) + +// A FieldType indicates which member of the Field union struct should be used +// and how it should be serialized. +type FieldType uint8 + +const ( + // UnknownType is the default field type. Attempting to add it to an encoder will panic. + UnknownType FieldType = iota + // ArrayMarshalerType indicates that the field carries an ArrayMarshaler. + ArrayMarshalerType + // ObjectMarshalerType indicates that the field carries an ObjectMarshaler. + ObjectMarshalerType + // BinaryType indicates that the field carries an opaque binary blob. + BinaryType + // BoolType indicates that the field carries a bool. + BoolType + // ByteStringType indicates that the field carries UTF-8 encoded bytes. + ByteStringType + // Complex128Type indicates that the field carries a complex128. + Complex128Type + // Complex64Type indicates that the field carries a complex128. + Complex64Type + // DurationType indicates that the field carries a time.Duration. + DurationType + // Float64Type indicates that the field carries a float64. + Float64Type + // Float32Type indicates that the field carries a float32. + Float32Type + // Int64Type indicates that the field carries an int64. + Int64Type + // Int32Type indicates that the field carries an int32. + Int32Type + // Int16Type indicates that the field carries an int16. + Int16Type + // Int8Type indicates that the field carries an int8. + Int8Type + // StringType indicates that the field carries a string. + StringType + // TimeType indicates that the field carries a time.Time that is + // representable by a UnixNano() stored as an int64. + TimeType + // TimeFullType indicates that the field carries a time.Time stored as-is. + TimeFullType + // Uint64Type indicates that the field carries a uint64. + Uint64Type + // Uint32Type indicates that the field carries a uint32. + Uint32Type + // Uint16Type indicates that the field carries a uint16. + Uint16Type + // Uint8Type indicates that the field carries a uint8. + Uint8Type + // UintptrType indicates that the field carries a uintptr. + UintptrType + // ReflectType indicates that the field carries an interface{}, which should + // be serialized using reflection. + ReflectType + // NamespaceType signals the beginning of an isolated namespace. All + // subsequent fields should be added to the new namespace. + NamespaceType + // StringerType indicates that the field carries a fmt.Stringer. + StringerType + // ErrorType indicates that the field carries an error. + ErrorType + // SkipType indicates that the field is a no-op. + SkipType + + // InlineMarshalerType indicates that the field carries an ObjectMarshaler + // that should be inlined. + InlineMarshalerType +) + +// A Field is a marshaling operation used to add a key-value pair to a logger's +// context. Most fields are lazily marshaled, so it's inexpensive to add fields +// to disabled debug-level log statements. +type Field struct { + Key string + Type FieldType + Integer int64 + String string + Interface interface{} +} + +// AddTo exports a field through the ObjectEncoder interface. It's primarily +// useful to library authors, and shouldn't be necessary in most applications. +func (f Field) AddTo(enc ObjectEncoder) { + var err error + + switch f.Type { + case ArrayMarshalerType: + err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler)) + case ObjectMarshalerType: + err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler)) + case InlineMarshalerType: + err = f.Interface.(ObjectMarshaler).MarshalLogObject(enc) + case BinaryType: + enc.AddBinary(f.Key, f.Interface.([]byte)) + case BoolType: + enc.AddBool(f.Key, f.Integer == 1) + case ByteStringType: + enc.AddByteString(f.Key, f.Interface.([]byte)) + case Complex128Type: + enc.AddComplex128(f.Key, f.Interface.(complex128)) + case Complex64Type: + enc.AddComplex64(f.Key, f.Interface.(complex64)) + case DurationType: + enc.AddDuration(f.Key, time.Duration(f.Integer)) + case Float64Type: + enc.AddFloat64(f.Key, math.Float64frombits(uint64(f.Integer))) + case Float32Type: + enc.AddFloat32(f.Key, math.Float32frombits(uint32(f.Integer))) + case Int64Type: + enc.AddInt64(f.Key, f.Integer) + case Int32Type: + enc.AddInt32(f.Key, int32(f.Integer)) + case Int16Type: + enc.AddInt16(f.Key, int16(f.Integer)) + case Int8Type: + enc.AddInt8(f.Key, int8(f.Integer)) + case StringType: + enc.AddString(f.Key, f.String) + case TimeType: + if f.Interface != nil { + enc.AddTime(f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location))) + } else { + // Fall back to UTC if location is nil. + enc.AddTime(f.Key, time.Unix(0, f.Integer)) + } + case TimeFullType: + enc.AddTime(f.Key, f.Interface.(time.Time)) + case Uint64Type: + enc.AddUint64(f.Key, uint64(f.Integer)) + case Uint32Type: + enc.AddUint32(f.Key, uint32(f.Integer)) + case Uint16Type: + enc.AddUint16(f.Key, uint16(f.Integer)) + case Uint8Type: + enc.AddUint8(f.Key, uint8(f.Integer)) + case UintptrType: + enc.AddUintptr(f.Key, uintptr(f.Integer)) + case ReflectType: + err = enc.AddReflected(f.Key, f.Interface) + case NamespaceType: + enc.OpenNamespace(f.Key) + case StringerType: + err = encodeStringer(f.Key, f.Interface, enc) + case ErrorType: + err = encodeError(f.Key, f.Interface.(error), enc) + case SkipType: + break + default: + panic(fmt.Sprintf("unknown field type: %v", f)) + } + + if err != nil { + enc.AddString(fmt.Sprintf("%sError", f.Key), err.Error()) + } +} + +// Equals returns whether two fields are equal. For non-primitive types such as +// errors, marshalers, or reflect types, it uses reflect.DeepEqual. +func (f Field) Equals(other Field) bool { + if f.Type != other.Type { + return false + } + if f.Key != other.Key { + return false + } + + switch f.Type { + case BinaryType, ByteStringType: + return bytes.Equal(f.Interface.([]byte), other.Interface.([]byte)) + case ArrayMarshalerType, ObjectMarshalerType, ErrorType, ReflectType: + return reflect.DeepEqual(f.Interface, other.Interface) + default: + return f == other + } +} + +func addFields(enc ObjectEncoder, fields []Field) { + for i := range fields { + fields[i].AddTo(enc) + } +} + +func encodeStringer(key string, stringer interface{}, enc ObjectEncoder) (retErr error) { + // Try to capture panics (from nil references or otherwise) when calling + // the String() method, similar to https://golang.org/src/fmt/print.go#L540 + defer func() { + if err := recover(); err != nil { + // If it's a nil pointer, just say "<nil>". The likeliest causes are a + // Stringer that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "<nil>" is a nice result. + if v := reflect.ValueOf(stringer); v.Kind() == reflect.Ptr && v.IsNil() { + enc.AddString(key, "<nil>") + return + } + + retErr = fmt.Errorf("PANIC=%v", err) + } + }() + + enc.AddString(key, stringer.(fmt.Stringer).String()) + return nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/field_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/field_test.go new file mode 100644 index 0000000..c436329 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/field_test.go @@ -0,0 +1,318 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "fmt" + "math" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + . "go.uber.org/zap/zapcore" +) + +type users int + +func (u users) String() string { + return fmt.Sprintf("%d users", int(u)) +} + +func (u users) MarshalLogObject(enc ObjectEncoder) error { + if int(u) < 0 { + return errors.New("too few users") + } + enc.AddInt("users", int(u)) + return nil +} + +func (u users) MarshalLogArray(enc ArrayEncoder) error { + if int(u) < 0 { + return errors.New("too few users") + } + for i := 0; i < int(u); i++ { + enc.AppendString("user") + } + return nil +} + +type obj struct { + kind int +} + +func (o *obj) String() string { + if o == nil { + return "nil obj" + } + + if o.kind == 1 { + panic("panic with string") + } else if o.kind == 2 { + panic(errors.New("panic with error")) + } else if o.kind == 3 { + // panic with an arbitrary object that causes a panic itself + // when being converted to a string + panic((*url.URL)(nil)) + } + + return "obj" +} + +type errObj struct { + kind int + errMsg string +} + +func (eobj *errObj) Error() string { + if eobj.kind == 1 { + panic("panic in Error() method") + } else { + return eobj.errMsg + } +} + +func TestUnknownFieldType(t *testing.T) { + unknown := Field{Key: "k", String: "foo"} + assert.Equal(t, UnknownType, unknown.Type, "Expected zero value of FieldType to be UnknownType.") + assert.Panics(t, func() { + unknown.AddTo(NewMapObjectEncoder()) + }, "Expected using a field with unknown type to panic.") +} + +func TestFieldAddingError(t *testing.T) { + var empty interface{} + tests := []struct { + t FieldType + iface interface{} + want interface{} + err string + }{ + {t: ArrayMarshalerType, iface: users(-1), want: []interface{}{}, err: "too few users"}, + {t: ObjectMarshalerType, iface: users(-1), want: map[string]interface{}{}, err: "too few users"}, + {t: InlineMarshalerType, iface: users(-1), want: nil, err: "too few users"}, + {t: StringerType, iface: obj{}, want: empty, err: "PANIC=interface conversion: zapcore_test.obj is not fmt.Stringer: missing method String"}, + {t: StringerType, iface: &obj{1}, want: empty, err: "PANIC=panic with string"}, + {t: StringerType, iface: &obj{2}, want: empty, err: "PANIC=panic with error"}, + {t: StringerType, iface: &obj{3}, want: empty, err: "PANIC=<nil>"}, + {t: ErrorType, iface: &errObj{kind: 1}, want: empty, err: "PANIC=panic in Error() method"}, + } + for _, tt := range tests { + f := Field{Key: "k", Interface: tt.iface, Type: tt.t} + enc := NewMapObjectEncoder() + assert.NotPanics(t, func() { f.AddTo(enc) }, "Unexpected panic when adding fields returns an error.") + assert.Equal(t, tt.want, enc.Fields["k"], "On error, expected zero value in field.Key.") + assert.Equal(t, tt.err, enc.Fields["kError"], "Expected error message in log context.") + } +} + +func TestFields(t *testing.T) { + tests := []struct { + t FieldType + i int64 + s string + iface interface{} + want interface{} + }{ + {t: ArrayMarshalerType, iface: users(2), want: []interface{}{"user", "user"}}, + {t: ObjectMarshalerType, iface: users(2), want: map[string]interface{}{"users": 2}}, + {t: BoolType, i: 0, want: false}, + {t: ByteStringType, iface: []byte("foo"), want: "foo"}, + {t: Complex128Type, iface: 1 + 2i, want: 1 + 2i}, + {t: Complex64Type, iface: complex64(1 + 2i), want: complex64(1 + 2i)}, + {t: DurationType, i: 1000, want: time.Microsecond}, + {t: Float64Type, i: int64(math.Float64bits(3.14)), want: 3.14}, + {t: Float32Type, i: int64(math.Float32bits(3.14)), want: float32(3.14)}, + {t: Int64Type, i: 42, want: int64(42)}, + {t: Int32Type, i: 42, want: int32(42)}, + {t: Int16Type, i: 42, want: int16(42)}, + {t: Int8Type, i: 42, want: int8(42)}, + {t: StringType, s: "foo", want: "foo"}, + {t: TimeType, i: 1000, iface: time.UTC, want: time.Unix(0, 1000).In(time.UTC)}, + {t: TimeType, i: 1000, want: time.Unix(0, 1000)}, + {t: Uint64Type, i: 42, want: uint64(42)}, + {t: Uint32Type, i: 42, want: uint32(42)}, + {t: Uint16Type, i: 42, want: uint16(42)}, + {t: Uint8Type, i: 42, want: uint8(42)}, + {t: UintptrType, i: 42, want: uintptr(42)}, + {t: ReflectType, iface: users(2), want: users(2)}, + {t: NamespaceType, want: map[string]interface{}{}}, + {t: StringerType, iface: users(2), want: "2 users"}, + {t: StringerType, iface: &obj{}, want: "obj"}, + {t: StringerType, iface: (*obj)(nil), want: "nil obj"}, + {t: SkipType, want: interface{}(nil)}, + {t: StringerType, iface: (*url.URL)(nil), want: "<nil>"}, + {t: StringerType, iface: (*users)(nil), want: "<nil>"}, + {t: ErrorType, iface: (*errObj)(nil), want: "<nil>"}, + } + + for _, tt := range tests { + enc := NewMapObjectEncoder() + f := Field{Key: "k", Type: tt.t, Integer: tt.i, Interface: tt.iface, String: tt.s} + f.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields["k"], "Unexpected output from field %+v.", f) + + delete(enc.Fields, "k") + assert.Equal(t, 0, len(enc.Fields), "Unexpected extra fields present.") + + assert.True(t, f.Equals(f), "Field does not equal itself") + } +} + +func TestInlineMarshaler(t *testing.T) { + enc := NewMapObjectEncoder() + + topLevelStr := Field{Key: "k", Type: StringType, String: "s"} + topLevelStr.AddTo(enc) + + inlineObj := Field{Key: "ignored", Type: InlineMarshalerType, Interface: users(10)} + inlineObj.AddTo(enc) + + nestedObj := Field{Key: "nested", Type: ObjectMarshalerType, Interface: users(11)} + nestedObj.AddTo(enc) + + assert.Equal(t, map[string]interface{}{ + "k": "s", + "users": 10, + "nested": map[string]interface{}{ + "users": 11, + }, + }, enc.Fields) +} + +func TestEquals(t *testing.T) { + // Values outside the UnixNano range were encoded incorrectly (#737, #803). + timeOutOfRangeHigh := time.Unix(0, math.MaxInt64).Add(time.Nanosecond) + timeOutOfRangeLow := time.Unix(0, math.MinInt64).Add(-time.Nanosecond) + timeOutOfRangeHighNano := time.Unix(0, timeOutOfRangeHigh.UnixNano()) + timeOutOfRangeLowNano := time.Unix(0, timeOutOfRangeLow.UnixNano()) + require.False(t, timeOutOfRangeHigh.Equal(timeOutOfRangeHighNano), "should be different as value is > UnixNano range") + require.False(t, timeOutOfRangeHigh.Equal(timeOutOfRangeHighNano), "should be different as value is < UnixNano range") + + tests := []struct { + a, b Field + want bool + }{ + { + a: zap.Int16("a", 1), + b: zap.Int32("a", 1), + want: false, + }, + { + a: zap.String("k", "a"), + b: zap.String("k", "a"), + want: true, + }, + { + a: zap.String("k", "a"), + b: zap.String("k2", "a"), + want: false, + }, + { + a: zap.String("k", "a"), + b: zap.String("k", "b"), + want: false, + }, + { + a: zap.Time("k", time.Unix(1000, 1000)), + b: zap.Time("k", time.Unix(1000, 1000)), + want: true, + }, + { + a: zap.Time("k", time.Unix(1000, 1000).In(time.UTC)), + b: zap.Time("k", time.Unix(1000, 1000).In(time.FixedZone("TEST", -8))), + want: false, + }, + { + a: zap.Time("k", timeOutOfRangeLow), + b: zap.Time("k", timeOutOfRangeLowNano), + want: false, + }, + { + a: zap.Time("k", timeOutOfRangeHigh), + b: zap.Time("k", timeOutOfRangeHighNano), + want: false, + }, + { + a: zap.Time("k", time.Unix(1000, 1000)), + b: zap.Time("k", time.Unix(1000, 2000)), + want: false, + }, + { + a: zap.Binary("k", []byte{1, 2}), + b: zap.Binary("k", []byte{1, 2}), + want: true, + }, + { + a: zap.Binary("k", []byte{1, 2}), + b: zap.Binary("k", []byte{1, 3}), + want: false, + }, + { + a: zap.ByteString("k", []byte("abc")), + b: zap.ByteString("k", []byte("abc")), + want: true, + }, + { + a: zap.ByteString("k", []byte("abc")), + b: zap.ByteString("k", []byte("abd")), + want: false, + }, + { + a: zap.Ints("k", []int{1, 2}), + b: zap.Ints("k", []int{1, 2}), + want: true, + }, + { + a: zap.Ints("k", []int{1, 2}), + b: zap.Ints("k", []int{1, 3}), + want: false, + }, + { + a: zap.Object("k", users(10)), + b: zap.Object("k", users(10)), + want: true, + }, + { + a: zap.Object("k", users(10)), + b: zap.Object("k", users(20)), + want: false, + }, + { + a: zap.Any("k", map[string]string{"a": "b"}), + b: zap.Any("k", map[string]string{"a": "b"}), + want: true, + }, + { + a: zap.Any("k", map[string]string{"a": "b"}), + b: zap.Any("k", map[string]string{"a": "d"}), + want: false, + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.want, tt.a.Equals(tt.b), "a.Equals(b) a: %#v b: %#v", tt.a, tt.b) + assert.Equal(t, tt.want, tt.b.Equals(tt.a), "b.Equals(a) a: %#v b: %#v", tt.a, tt.b) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/hook.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/hook.go new file mode 100644 index 0000000..198def9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/hook.go @@ -0,0 +1,77 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type hooked struct { + Core + funcs []func(Entry) error +} + +var ( + _ Core = (*hooked)(nil) + _ leveledEnabler = (*hooked)(nil) +) + +// RegisterHooks wraps a Core and runs a collection of user-defined callback +// hooks each time a message is logged. Execution of the callbacks is blocking. +// +// This offers users an easy way to register simple callbacks (e.g., metrics +// collection) without implementing the full Core interface. +func RegisterHooks(core Core, hooks ...func(Entry) error) Core { + funcs := append([]func(Entry) error{}, hooks...) + return &hooked{ + Core: core, + funcs: funcs, + } +} + +func (h *hooked) Level() Level { + return LevelOf(h.Core) +} + +func (h *hooked) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + // Let the wrapped Core decide whether to log this message or not. This + // also gives the downstream a chance to register itself directly with the + // CheckedEntry. + if downstream := h.Core.Check(ent, ce); downstream != nil { + return downstream.AddCore(ent, h) + } + return ce +} + +func (h *hooked) With(fields []Field) Core { + return &hooked{ + Core: h.Core.With(fields), + funcs: h.funcs, + } +} + +func (h *hooked) Write(ent Entry, _ []Field) error { + // Since our downstream had a chance to register itself directly with the + // CheckedMessage, we don't need to call it here. + var err error + for i := range h.funcs { + err = multierr.Append(err, h.funcs[i](ent)) + } + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/hook_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/hook_test.go new file mode 100644 index 0000000..46e3c35 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/hook_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "testing" + + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHooks(t *testing.T) { + tests := []struct { + entryLevel Level + coreLevel Level + expectCall bool + }{ + {DebugLevel, InfoLevel, false}, + {InfoLevel, InfoLevel, true}, + {WarnLevel, InfoLevel, true}, + } + + for _, tt := range tests { + fac, logs := observer.New(tt.coreLevel) + + // sanity check + require.Equal(t, tt.coreLevel, LevelOf(fac), "Original logger has the wrong level") + + intField := makeInt64Field("foo", 42) + ent := Entry{Message: "bar", Level: tt.entryLevel} + + var called int + f := func(e Entry) error { + called++ + assert.Equal(t, ent, e, "Hook called with unexpected Entry.") + return nil + } + + h := RegisterHooks(fac, f) + if ce := h.With([]Field{intField}).Check(ent, nil); ce != nil { + ce.Write() + } + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, tt.coreLevel, LevelOf(h), "Wrapped logger has the wrong log level") + }) + + if tt.expectCall { + assert.Equal(t, 1, called, "Expected to call hook once.") + assert.Equal( + t, + []observer.LoggedEntry{{Entry: ent, Context: []Field{intField}}}, + logs.AllUntimed(), + "Unexpected logs written out.", + ) + } else { + assert.Equal(t, 0, called, "Didn't expect to call hook.") + assert.Equal(t, 0, logs.Len(), "Unexpected logs written out.") + } + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/increase_level.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/increase_level.go new file mode 100644 index 0000000..7a11237 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/increase_level.go @@ -0,0 +1,75 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "fmt" + +type levelFilterCore struct { + core Core + level LevelEnabler +} + +var ( + _ Core = (*levelFilterCore)(nil) + _ leveledEnabler = (*levelFilterCore)(nil) +) + +// NewIncreaseLevelCore creates a core that can be used to increase the level of +// an existing Core. It cannot be used to decrease the logging level, as it acts +// as a filter before calling the underlying core. If level decreases the log level, +// an error is returned. +func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error) { + for l := _maxLevel; l >= _minLevel; l-- { + if !core.Enabled(l) && level.Enabled(l) { + return nil, fmt.Errorf("invalid increase level, as level %q is allowed by increased level, but not by existing core", l) + } + } + + return &levelFilterCore{core, level}, nil +} + +func (c *levelFilterCore) Enabled(lvl Level) bool { + return c.level.Enabled(lvl) +} + +func (c *levelFilterCore) Level() Level { + return LevelOf(c.level) +} + +func (c *levelFilterCore) With(fields []Field) Core { + return &levelFilterCore{c.core.With(fields), c.level} +} + +func (c *levelFilterCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !c.Enabled(ent.Level) { + return ce + } + + return c.core.Check(ent, ce) +} + +func (c *levelFilterCore) Write(ent Entry, fields []Field) error { + return c.core.Write(ent, fields) +} + +func (c *levelFilterCore) Sync() error { + return c.core.Sync() +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/increase_level_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/increase_level_test.go new file mode 100644 index 0000000..547794f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/increase_level_test.go @@ -0,0 +1,129 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestIncreaseLevel(t *testing.T) { + tests := []struct { + coreLevel Level + increaseLevel Level + wantErr bool + with []Field + }{ + { + coreLevel: InfoLevel, + increaseLevel: DebugLevel, + wantErr: true, + }, + { + coreLevel: InfoLevel, + increaseLevel: InfoLevel, + }, + { + coreLevel: InfoLevel, + increaseLevel: ErrorLevel, + }, + { + coreLevel: InfoLevel, + increaseLevel: ErrorLevel, + with: []Field{zap.String("k", "v")}, + }, + { + coreLevel: ErrorLevel, + increaseLevel: DebugLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: InfoLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: WarnLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: PanicLevel, + }, + } + + for _, tt := range tests { + msg := fmt.Sprintf("increase %v to %v", tt.coreLevel, tt.increaseLevel) + t.Run(msg, func(t *testing.T) { + logger, logs := observer.New(tt.coreLevel) + + // sanity check + require.Equal(t, tt.coreLevel, LevelOf(logger), "Original logger has the wrong level") + + filteredLogger, err := NewIncreaseLevelCore(logger, tt.increaseLevel) + if tt.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid increase level") + return + } + + if len(tt.with) > 0 { + filteredLogger = filteredLogger.With(tt.with) + } + + require.NoError(t, err) + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, tt.increaseLevel, LevelOf(filteredLogger), "Filtered logger has the wrong level") + }) + + for l := DebugLevel; l <= FatalLevel; l++ { + enabled := filteredLogger.Enabled(l) + entry := Entry{Level: l} + ce := filteredLogger.Check(entry, nil) + ce.Write() + entries := logs.TakeAll() + + if l >= tt.increaseLevel { + assert.True(t, enabled, "expect %v to be enabled", l) + assert.NotNil(t, ce, "expect non-nil Check") + assert.NotEmpty(t, entries, "Expect log to be written") + } else { + assert.False(t, enabled, "expect %v to be disabled", l) + assert.Nil(t, ce, "expect nil Check") + assert.Empty(t, entries, "No logs should have been written") + } + + // Write should always log the entry as per the Core interface + require.NoError(t, filteredLogger.Write(entry, nil), "Write failed") + require.NoError(t, filteredLogger.Sync(), "Sync failed") + assert.NotEmpty(t, logs.TakeAll(), "Write should always log") + } + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder.go new file mode 100644 index 0000000..3921c5c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder.go @@ -0,0 +1,562 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/base64" + "math" + "sync" + "time" + "unicode/utf8" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" +) + +// For JSON-escaping; see jsonEncoder.safeAddString below. +const _hex = "0123456789abcdef" + +var _jsonPool = sync.Pool{New: func() interface{} { + return &jsonEncoder{} +}} + +func getJSONEncoder() *jsonEncoder { + return _jsonPool.Get().(*jsonEncoder) +} + +func putJSONEncoder(enc *jsonEncoder) { + if enc.reflectBuf != nil { + enc.reflectBuf.Free() + } + enc.EncoderConfig = nil + enc.buf = nil + enc.spaced = false + enc.openNamespaces = 0 + enc.reflectBuf = nil + enc.reflectEnc = nil + _jsonPool.Put(enc) +} + +type jsonEncoder struct { + *EncoderConfig + buf *buffer.Buffer + spaced bool // include spaces after colons and commas + openNamespaces int + + // for encoding generic values by reflection + reflectBuf *buffer.Buffer + reflectEnc ReflectedEncoder +} + +// NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder +// appropriately escapes all field keys and values. +// +// Note that the encoder doesn't deduplicate keys, so it's possible to produce +// a message like +// +// {"foo":"bar","foo":"baz"} +// +// This is permitted by the JSON specification, but not encouraged. Many +// libraries will ignore duplicate key-value pairs (typically keeping the last +// pair) when unmarshaling, but users should attempt to avoid adding duplicate +// keys. +func NewJSONEncoder(cfg EncoderConfig) Encoder { + return newJSONEncoder(cfg, false) +} + +func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { + if cfg.SkipLineEnding { + cfg.LineEnding = "" + } else if cfg.LineEnding == "" { + cfg.LineEnding = DefaultLineEnding + } + + // If no EncoderConfig.NewReflectedEncoder is provided by the user, then use default + if cfg.NewReflectedEncoder == nil { + cfg.NewReflectedEncoder = defaultReflectedEncoder + } + + return &jsonEncoder{ + EncoderConfig: &cfg, + buf: bufferpool.Get(), + spaced: spaced, + } +} + +func (enc *jsonEncoder) AddArray(key string, arr ArrayMarshaler) error { + enc.addKey(key) + return enc.AppendArray(arr) +} + +func (enc *jsonEncoder) AddObject(key string, obj ObjectMarshaler) error { + enc.addKey(key) + return enc.AppendObject(obj) +} + +func (enc *jsonEncoder) AddBinary(key string, val []byte) { + enc.AddString(key, base64.StdEncoding.EncodeToString(val)) +} + +func (enc *jsonEncoder) AddByteString(key string, val []byte) { + enc.addKey(key) + enc.AppendByteString(val) +} + +func (enc *jsonEncoder) AddBool(key string, val bool) { + enc.addKey(key) + enc.AppendBool(val) +} + +func (enc *jsonEncoder) AddComplex128(key string, val complex128) { + enc.addKey(key) + enc.AppendComplex128(val) +} + +func (enc *jsonEncoder) AddComplex64(key string, val complex64) { + enc.addKey(key) + enc.AppendComplex64(val) +} + +func (enc *jsonEncoder) AddDuration(key string, val time.Duration) { + enc.addKey(key) + enc.AppendDuration(val) +} + +func (enc *jsonEncoder) AddFloat64(key string, val float64) { + enc.addKey(key) + enc.AppendFloat64(val) +} + +func (enc *jsonEncoder) AddFloat32(key string, val float32) { + enc.addKey(key) + enc.AppendFloat32(val) +} + +func (enc *jsonEncoder) AddInt64(key string, val int64) { + enc.addKey(key) + enc.AppendInt64(val) +} + +func (enc *jsonEncoder) resetReflectBuf() { + if enc.reflectBuf == nil { + enc.reflectBuf = bufferpool.Get() + enc.reflectEnc = enc.NewReflectedEncoder(enc.reflectBuf) + } else { + enc.reflectBuf.Reset() + } +} + +var nullLiteralBytes = []byte("null") + +// Only invoke the standard JSON encoder if there is actually something to +// encode; otherwise write JSON null literal directly. +func (enc *jsonEncoder) encodeReflected(obj interface{}) ([]byte, error) { + if obj == nil { + return nullLiteralBytes, nil + } + enc.resetReflectBuf() + if err := enc.reflectEnc.Encode(obj); err != nil { + return nil, err + } + enc.reflectBuf.TrimNewline() + return enc.reflectBuf.Bytes(), nil +} + +func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error { + valueBytes, err := enc.encodeReflected(obj) + if err != nil { + return err + } + enc.addKey(key) + _, err = enc.buf.Write(valueBytes) + return err +} + +func (enc *jsonEncoder) OpenNamespace(key string) { + enc.addKey(key) + enc.buf.AppendByte('{') + enc.openNamespaces++ +} + +func (enc *jsonEncoder) AddString(key, val string) { + enc.addKey(key) + enc.AppendString(val) +} + +func (enc *jsonEncoder) AddTime(key string, val time.Time) { + enc.addKey(key) + enc.AppendTime(val) +} + +func (enc *jsonEncoder) AddUint64(key string, val uint64) { + enc.addKey(key) + enc.AppendUint64(val) +} + +func (enc *jsonEncoder) AppendArray(arr ArrayMarshaler) error { + enc.addElementSeparator() + enc.buf.AppendByte('[') + err := arr.MarshalLogArray(enc) + enc.buf.AppendByte(']') + return err +} + +func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error { + // Close ONLY new openNamespaces that are created during + // AppendObject(). + old := enc.openNamespaces + enc.openNamespaces = 0 + enc.addElementSeparator() + enc.buf.AppendByte('{') + err := obj.MarshalLogObject(enc) + enc.buf.AppendByte('}') + enc.closeOpenNamespaces() + enc.openNamespaces = old + return err +} + +func (enc *jsonEncoder) AppendBool(val bool) { + enc.addElementSeparator() + enc.buf.AppendBool(val) +} + +func (enc *jsonEncoder) AppendByteString(val []byte) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddByteString(val) + enc.buf.AppendByte('"') +} + +// appendComplex appends the encoded form of the provided complex128 value. +// precision specifies the encoding precision for the real and imaginary +// components of the complex number. +func (enc *jsonEncoder) appendComplex(val complex128, precision int) { + enc.addElementSeparator() + // Cast to a platform-independent, fixed-size type. + r, i := float64(real(val)), float64(imag(val)) + enc.buf.AppendByte('"') + // Because we're always in a quoted string, we can use strconv without + // special-casing NaN and +/-Inf. + enc.buf.AppendFloat(r, precision) + // If imaginary part is less than 0, minus (-) sign is added by default + // by AppendFloat. + if i >= 0 { + enc.buf.AppendByte('+') + } + enc.buf.AppendFloat(i, precision) + enc.buf.AppendByte('i') + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendDuration(val time.Duration) { + cur := enc.buf.Len() + if e := enc.EncodeDuration; e != nil { + e(val, enc) + } + if cur == enc.buf.Len() { + // User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep + // JSON valid. + enc.AppendInt64(int64(val)) + } +} + +func (enc *jsonEncoder) AppendInt64(val int64) { + enc.addElementSeparator() + enc.buf.AppendInt(val) +} + +func (enc *jsonEncoder) AppendReflected(val interface{}) error { + valueBytes, err := enc.encodeReflected(val) + if err != nil { + return err + } + enc.addElementSeparator() + _, err = enc.buf.Write(valueBytes) + return err +} + +func (enc *jsonEncoder) AppendString(val string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(val) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendTimeLayout(time time.Time, layout string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.buf.AppendTime(time, layout) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendTime(val time.Time) { + cur := enc.buf.Len() + if e := enc.EncodeTime; e != nil { + e(val, enc) + } + if cur == enc.buf.Len() { + // User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep + // output JSON valid. + enc.AppendInt64(val.UnixNano()) + } +} + +func (enc *jsonEncoder) AppendUint64(val uint64) { + enc.addElementSeparator() + enc.buf.AppendUint(val) +} + +func (enc *jsonEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AppendComplex64(v complex64) { enc.appendComplex(complex128(v), 32) } +func (enc *jsonEncoder) AppendComplex128(v complex128) { enc.appendComplex(complex128(v), 64) } +func (enc *jsonEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) } +func (enc *jsonEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) } +func (enc *jsonEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) } + +func (enc *jsonEncoder) Clone() Encoder { + clone := enc.clone() + clone.buf.Write(enc.buf.Bytes()) + return clone +} + +func (enc *jsonEncoder) clone() *jsonEncoder { + clone := getJSONEncoder() + clone.EncoderConfig = enc.EncoderConfig + clone.spaced = enc.spaced + clone.openNamespaces = enc.openNamespaces + clone.buf = bufferpool.Get() + return clone +} + +func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + final := enc.clone() + final.buf.AppendByte('{') + + if final.LevelKey != "" && final.EncodeLevel != nil { + final.addKey(final.LevelKey) + cur := final.buf.Len() + final.EncodeLevel(ent.Level, final) + if cur == final.buf.Len() { + // User-supplied EncodeLevel was a no-op. Fall back to strings to keep + // output JSON valid. + final.AppendString(ent.Level.String()) + } + } + if final.TimeKey != "" { + final.AddTime(final.TimeKey, ent.Time) + } + if ent.LoggerName != "" && final.NameKey != "" { + final.addKey(final.NameKey) + cur := final.buf.Len() + nameEncoder := final.EncodeName + + // if no name encoder provided, fall back to FullNameEncoder for backwards + // compatibility + if nameEncoder == nil { + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, final) + if cur == final.buf.Len() { + // User-supplied EncodeName was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.LoggerName) + } + } + if ent.Caller.Defined { + if final.CallerKey != "" { + final.addKey(final.CallerKey) + cur := final.buf.Len() + final.EncodeCaller(ent.Caller, final) + if cur == final.buf.Len() { + // User-supplied EncodeCaller was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.Caller.String()) + } + } + if final.FunctionKey != "" { + final.addKey(final.FunctionKey) + final.AppendString(ent.Caller.Function) + } + } + if final.MessageKey != "" { + final.addKey(enc.MessageKey) + final.AppendString(ent.Message) + } + if enc.buf.Len() > 0 { + final.addElementSeparator() + final.buf.Write(enc.buf.Bytes()) + } + addFields(final, fields) + final.closeOpenNamespaces() + if ent.Stack != "" && final.StacktraceKey != "" { + final.AddString(final.StacktraceKey, ent.Stack) + } + final.buf.AppendByte('}') + final.buf.AppendString(final.LineEnding) + + ret := final.buf + putJSONEncoder(final) + return ret, nil +} + +func (enc *jsonEncoder) truncate() { + enc.buf.Reset() +} + +func (enc *jsonEncoder) closeOpenNamespaces() { + for i := 0; i < enc.openNamespaces; i++ { + enc.buf.AppendByte('}') + } + enc.openNamespaces = 0 +} + +func (enc *jsonEncoder) addKey(key string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(key) + enc.buf.AppendByte('"') + enc.buf.AppendByte(':') + if enc.spaced { + enc.buf.AppendByte(' ') + } +} + +func (enc *jsonEncoder) addElementSeparator() { + last := enc.buf.Len() - 1 + if last < 0 { + return + } + switch enc.buf.Bytes()[last] { + case '{', '[', ':', ',', ' ': + return + default: + enc.buf.AppendByte(',') + if enc.spaced { + enc.buf.AppendByte(' ') + } + } +} + +func (enc *jsonEncoder) appendFloat(val float64, bitSize int) { + enc.addElementSeparator() + switch { + case math.IsNaN(val): + enc.buf.AppendString(`"NaN"`) + case math.IsInf(val, 1): + enc.buf.AppendString(`"+Inf"`) + case math.IsInf(val, -1): + enc.buf.AppendString(`"-Inf"`) + default: + enc.buf.AppendFloat(val, bitSize) + } +} + +// safeAddString JSON-escapes a string and appends it to the internal buffer. +// Unlike the standard library's encoder, it doesn't attempt to protect the +// user from browser vulnerabilities or JSONP-related problems. +func (enc *jsonEncoder) safeAddString(s string) { + for i := 0; i < len(s); { + if enc.tryAddRuneSelf(s[i]) { + i++ + continue + } + r, size := utf8.DecodeRuneInString(s[i:]) + if enc.tryAddRuneError(r, size) { + i++ + continue + } + enc.buf.AppendString(s[i : i+size]) + i += size + } +} + +// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte. +func (enc *jsonEncoder) safeAddByteString(s []byte) { + for i := 0; i < len(s); { + if enc.tryAddRuneSelf(s[i]) { + i++ + continue + } + r, size := utf8.DecodeRune(s[i:]) + if enc.tryAddRuneError(r, size) { + i++ + continue + } + enc.buf.Write(s[i : i+size]) + i += size + } +} + +// tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte. +func (enc *jsonEncoder) tryAddRuneSelf(b byte) bool { + if b >= utf8.RuneSelf { + return false + } + if 0x20 <= b && b != '\\' && b != '"' { + enc.buf.AppendByte(b) + return true + } + switch b { + case '\\', '"': + enc.buf.AppendByte('\\') + enc.buf.AppendByte(b) + case '\n': + enc.buf.AppendByte('\\') + enc.buf.AppendByte('n') + case '\r': + enc.buf.AppendByte('\\') + enc.buf.AppendByte('r') + case '\t': + enc.buf.AppendByte('\\') + enc.buf.AppendByte('t') + default: + // Encode bytes < 0x20, except for the escape sequences above. + enc.buf.AppendString(`\u00`) + enc.buf.AppendByte(_hex[b>>4]) + enc.buf.AppendByte(_hex[b&0xF]) + } + return true +} + +func (enc *jsonEncoder) tryAddRuneError(r rune, size int) bool { + if r == utf8.RuneError && size == 1 { + enc.buf.AppendString(`\ufffd`) + return true + } + return false +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_bench_test.go new file mode 100644 index 0000000..bcb5a01 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_bench_test.go @@ -0,0 +1,101 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "encoding/json" + "testing" + "time" + + . "go.uber.org/zap/zapcore" +) + +func BenchmarkJSONLogMarshalerFunc(b *testing.B) { + for i := 0; i < b.N; i++ { + enc := NewJSONEncoder(testEncoderConfig()) + enc.AddObject("nested", ObjectMarshalerFunc(func(enc ObjectEncoder) error { + enc.AddInt64("i", int64(i)) + return nil + })) + } +} + +func BenchmarkZapJSONFloat32AndComplex64(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewJSONEncoder(testEncoderConfig()) + enc.AddFloat32("float32", 3.14) + enc.AddComplex64("complex64", 2.71+3.14i) + } + }) +} + +func BenchmarkZapJSON(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewJSONEncoder(testEncoderConfig()) + enc.AddString("str", "foo") + enc.AddInt64("int64-1", 1) + enc.AddInt64("int64-2", 2) + enc.AddFloat64("float64", 1.0) + enc.AddString("string1", "\n") + enc.AddString("string2", "💩") + enc.AddString("string3", "🤔") + enc.AddString("string4", "🙊") + enc.AddBool("bool", true) + buf, _ := enc.EncodeEntry(Entry{ + Message: "fake", + Level: DebugLevel, + }, nil) + buf.Free() + } + }) +} + +func BenchmarkStandardJSON(b *testing.B) { + record := struct { + Level string `json:"level"` + Message string `json:"msg"` + Time time.Time `json:"ts"` + Fields map[string]interface{} `json:"fields"` + }{ + Level: "debug", + Message: "fake", + Time: time.Unix(0, 0), + Fields: map[string]interface{}{ + "str": "foo", + "int64-1": int64(1), + "int64-2": int64(1), + "float64": float64(1.0), + "string1": "\n", + "string2": "💩", + "string3": "🤔", + "string4": "🙊", + "bool": true, + }, + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + json.Marshal(record) + } + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_impl_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_impl_test.go new file mode 100644 index 0000000..fde241f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_impl_test.go @@ -0,0 +1,659 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "errors" + "math" + "math/rand" + "reflect" + "testing" + "testing/quick" + "time" + + "go.uber.org/zap/internal/bufferpool" + + "github.com/stretchr/testify/assert" + "go.uber.org/multierr" +) + +var _defaultEncoderConfig = EncoderConfig{ + EncodeTime: EpochTimeEncoder, + EncodeDuration: SecondsDurationEncoder, +} + +func TestJSONClone(t *testing.T) { + // The parent encoder is created with plenty of excess capacity. + parent := &jsonEncoder{buf: bufferpool.Get()} + clone := parent.Clone() + + // Adding to the parent shouldn't affect the clone, and vice versa. + parent.AddString("foo", "bar") + clone.AddString("baz", "bing") + + assertJSON(t, `"foo":"bar"`, parent) + assertJSON(t, `"baz":"bing"`, clone.(*jsonEncoder)) +} + +func TestJSONEscaping(t *testing.T) { + enc := &jsonEncoder{buf: bufferpool.Get()} + // Test all the edge cases of JSON escaping directly. + cases := map[string]string{ + // ASCII. + `foo`: `foo`, + // Special-cased characters. + `"`: `\"`, + `\`: `\\`, + // Special-cased characters within everyday ASCII. + `foo"foo`: `foo\"foo`, + "foo\n": `foo\n`, + // Special-cased control characters. + "\n": `\n`, + "\r": `\r`, + "\t": `\t`, + // \b and \f are sometimes backslash-escaped, but this representation is also + // conformant. + "\b": `\u0008`, + "\f": `\u000c`, + // The standard lib special-cases angle brackets and ampersands by default, + // because it wants to protect users from browser exploits. In a logging + // context, we shouldn't special-case these characters. + "<": "<", + ">": ">", + "&": "&", + // ASCII bell - not special-cased. + string(byte(0x07)): `\u0007`, + // Astral-plane unicode. + `☃`: `☃`, + // Decodes to (RuneError, 1) + "\xed\xa0\x80": `\ufffd\ufffd\ufffd`, + "foo\xed\xa0\x80": `foo\ufffd\ufffd\ufffd`, + } + + t.Run("String", func(t *testing.T) { + for input, output := range cases { + enc.truncate() + enc.safeAddString(input) + assertJSON(t, output, enc) + } + }) + + t.Run("ByteString", func(t *testing.T) { + for input, output := range cases { + enc.truncate() + enc.safeAddByteString([]byte(input)) + assertJSON(t, output, enc) + } + }) +} + +func TestJSONEncoderObjectFields(t *testing.T) { + tests := []struct { + desc string + expected string + f func(Encoder) + }{ + {"binary", `"k":"YWIxMg=="`, func(e Encoder) { e.AddBinary("k", []byte("ab12")) }}, + {"bool", `"k\\":true`, func(e Encoder) { e.AddBool(`k\`, true) }}, // test key escaping once + {"bool", `"k":true`, func(e Encoder) { e.AddBool("k", true) }}, + {"bool", `"k":false`, func(e Encoder) { e.AddBool("k", false) }}, + {"byteString", `"k":"v\\"`, func(e Encoder) { e.AddByteString(`k`, []byte(`v\`)) }}, + {"byteString", `"k":"v"`, func(e Encoder) { e.AddByteString("k", []byte("v")) }}, + {"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", []byte{}) }}, + {"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", nil) }}, + {"complex128", `"k":"1+2i"`, func(e Encoder) { e.AddComplex128("k", 1+2i) }}, + {"complex128/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex128("k", 1-2i) }}, + {"complex64", `"k":"1+2i"`, func(e Encoder) { e.AddComplex64("k", 1+2i) }}, + {"complex64/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex64("k", 1-2i) }}, + {"complex64", `"k":"2.71+3.14i"`, func(e Encoder) { e.AddComplex64("k", 2.71+3.14i) }}, + {"duration", `"k":0.000000001`, func(e Encoder) { e.AddDuration("k", 1) }}, + {"duration/negative", `"k":-0.000000001`, func(e Encoder) { e.AddDuration("k", -1) }}, + {"float64", `"k":1`, func(e Encoder) { e.AddFloat64("k", 1.0) }}, + {"float64", `"k":10000000000`, func(e Encoder) { e.AddFloat64("k", 1e10) }}, + {"float64", `"k":"NaN"`, func(e Encoder) { e.AddFloat64("k", math.NaN()) }}, + {"float64", `"k":"+Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(1)) }}, + {"float64", `"k":"-Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(-1)) }}, + {"float64/pi", `"k":3.141592653589793`, func(e Encoder) { e.AddFloat64("k", math.Pi) }}, + {"float32", `"k":1`, func(e Encoder) { e.AddFloat32("k", 1.0) }}, + {"float32", `"k":2.71`, func(e Encoder) { e.AddFloat32("k", 2.71) }}, + {"float32", `"k":0.1`, func(e Encoder) { e.AddFloat32("k", 0.1) }}, + {"float32", `"k":10000000000`, func(e Encoder) { e.AddFloat32("k", 1e10) }}, + {"float32", `"k":"NaN"`, func(e Encoder) { e.AddFloat32("k", float32(math.NaN())) }}, + {"float32", `"k":"+Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(1))) }}, + {"float32", `"k":"-Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(-1))) }}, + {"float32/pi", `"k":3.1415927`, func(e Encoder) { e.AddFloat32("k", math.Pi) }}, + {"int", `"k":42`, func(e Encoder) { e.AddInt("k", 42) }}, + {"int64", `"k":42`, func(e Encoder) { e.AddInt64("k", 42) }}, + {"int64/min", `"k":-9223372036854775808`, func(e Encoder) { e.AddInt64("k", math.MinInt64) }}, + {"int64/max", `"k":9223372036854775807`, func(e Encoder) { e.AddInt64("k", math.MaxInt64) }}, + {"int32", `"k":42`, func(e Encoder) { e.AddInt32("k", 42) }}, + {"int32/min", `"k":-2147483648`, func(e Encoder) { e.AddInt32("k", math.MinInt32) }}, + {"int32/max", `"k":2147483647`, func(e Encoder) { e.AddInt32("k", math.MaxInt32) }}, + {"int16", `"k":42`, func(e Encoder) { e.AddInt16("k", 42) }}, + {"int16/min", `"k":-32768`, func(e Encoder) { e.AddInt16("k", math.MinInt16) }}, + {"int16/max", `"k":32767`, func(e Encoder) { e.AddInt16("k", math.MaxInt16) }}, + {"int8", `"k":42`, func(e Encoder) { e.AddInt8("k", 42) }}, + {"int8/min", `"k":-128`, func(e Encoder) { e.AddInt8("k", math.MinInt8) }}, + {"int8/max", `"k":127`, func(e Encoder) { e.AddInt8("k", math.MaxInt8) }}, + {"string", `"k":"v\\"`, func(e Encoder) { e.AddString(`k`, `v\`) }}, + {"string", `"k":"v"`, func(e Encoder) { e.AddString("k", "v") }}, + {"string", `"k":""`, func(e Encoder) { e.AddString("k", "") }}, + {"time", `"k":1`, func(e Encoder) { e.AddTime("k", time.Unix(1, 0)) }}, + {"uint", `"k":42`, func(e Encoder) { e.AddUint("k", 42) }}, + {"uint64", `"k":42`, func(e Encoder) { e.AddUint64("k", 42) }}, + {"uint64/max", `"k":18446744073709551615`, func(e Encoder) { e.AddUint64("k", math.MaxUint64) }}, + {"uint32", `"k":42`, func(e Encoder) { e.AddUint32("k", 42) }}, + {"uint32/max", `"k":4294967295`, func(e Encoder) { e.AddUint32("k", math.MaxUint32) }}, + {"uint16", `"k":42`, func(e Encoder) { e.AddUint16("k", 42) }}, + {"uint16/max", `"k":65535`, func(e Encoder) { e.AddUint16("k", math.MaxUint16) }}, + {"uint8", `"k":42`, func(e Encoder) { e.AddUint8("k", 42) }}, + {"uint8/max", `"k":255`, func(e Encoder) { e.AddUint8("k", math.MaxUint8) }}, + {"uintptr", `"k":42`, func(e Encoder) { e.AddUintptr("k", 42) }}, + { + desc: "object (success)", + expected: `"k":{"loggable":"yes"}`, + f: func(e Encoder) { + assert.NoError(t, e.AddObject("k", loggable{true}), "Unexpected error calling MarshalLogObject.") + }, + }, + { + desc: "object (error)", + expected: `"k":{}`, + f: func(e Encoder) { + assert.Error(t, e.AddObject("k", loggable{false}), "Expected an error calling MarshalLogObject.") + }, + }, + { + desc: "object (with nested array)", + expected: `"turducken":{"ducks":[{"in":"chicken"},{"in":"chicken"}]}`, + f: func(e Encoder) { + assert.NoError( + t, + e.AddObject("turducken", turducken{}), + "Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.", + ) + }, + }, + { + desc: "array (with nested object)", + expected: `"turduckens":[{"ducks":[{"in":"chicken"},{"in":"chicken"}]},{"ducks":[{"in":"chicken"},{"in":"chicken"}]}]`, + f: func(e Encoder) { + assert.NoError( + t, + e.AddArray("turduckens", turduckens(2)), + "Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.", + ) + }, + }, + { + desc: "array (success)", + expected: `"k":[true]`, + f: func(e Encoder) { + assert.NoError(t, e.AddArray(`k`, loggable{true}), "Unexpected error calling MarshalLogArray.") + }, + }, + { + desc: "array (error)", + expected: `"k":[]`, + f: func(e Encoder) { + assert.Error(t, e.AddArray("k", loggable{false}), "Expected an error calling MarshalLogArray.") + }, + }, + { + desc: "reflect (success)", + expected: `"k":{"escape":"<&>","loggable":"yes"}`, + f: func(e Encoder) { + assert.NoError(t, e.AddReflected("k", map[string]string{"escape": "<&>", "loggable": "yes"}), "Unexpected error JSON-serializing a map.") + }, + }, + { + desc: "reflect (failure)", + expected: "", + f: func(e Encoder) { + assert.Error(t, e.AddReflected("k", noJSON{}), "Unexpected success JSON-serializing a noJSON.") + }, + }, + { + desc: "namespace", + // EncodeEntry is responsible for closing all open namespaces. + expected: `"outermost":{"outer":{"foo":1,"inner":{"foo":2,"innermost":{`, + f: func(e Encoder) { + e.OpenNamespace("outermost") + e.OpenNamespace("outer") + e.AddInt("foo", 1) + e.OpenNamespace("inner") + e.AddInt("foo", 2) + e.OpenNamespace("innermost") + }, + }, + { + desc: "object (no nested namespace)", + expected: `"obj":{"obj-out":"obj-outside-namespace"},"not-obj":"should-be-outside-obj"`, + f: func(e Encoder) { + e.AddObject("obj", maybeNamespace{false}) + e.AddString("not-obj", "should-be-outside-obj") + }, + }, + { + desc: "object (with nested namespace)", + expected: `"obj":{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"not-obj":"should-be-outside-obj"`, + f: func(e Encoder) { + e.AddObject("obj", maybeNamespace{true}) + e.AddString("not-obj", "should-be-outside-obj") + }, + }, + { + desc: "multiple open namespaces", + expected: `"k":{"foo":1,"middle":{"foo":2,"inner":{"foo":3}}}`, + f: func(e Encoder) { + e.AddObject("k", ObjectMarshalerFunc(func(enc ObjectEncoder) error { + e.AddInt("foo", 1) + e.OpenNamespace("middle") + e.AddInt("foo", 2) + e.OpenNamespace("inner") + e.AddInt("foo", 3) + return nil + })) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assertOutput(t, _defaultEncoderConfig, tt.expected, tt.f) + }) + } +} + +func TestJSONEncoderTimeFormats(t *testing.T) { + date := time.Date(2000, time.January, 2, 3, 4, 5, 6, time.UTC) + + f := func(e Encoder) { + e.AddTime("k", date) + e.AddArray("a", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendTime(date) + return nil + })) + } + tests := []struct { + desc string + cfg EncoderConfig + expected string + }{ + { + desc: "time.Time ISO8601", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: ISO8601TimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05.000Z","a":["2000-01-02T03:04:05.000Z"]`, + }, + { + desc: "time.Time RFC3339", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: RFC3339TimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05Z","a":["2000-01-02T03:04:05Z"]`, + }, + { + desc: "time.Time RFC3339Nano", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: RFC3339NanoTimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05.000000006Z","a":["2000-01-02T03:04:05.000000006Z"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assertOutput(t, tt.cfg, tt.expected, f) + }) + } +} + +func TestJSONEncoderArrays(t *testing.T) { + tests := []struct { + desc string + expected string // expect f to be called twice + f func(ArrayEncoder) + }{ + {"bool", `[true,true]`, func(e ArrayEncoder) { e.AppendBool(true) }}, + {"byteString", `["k","k"]`, func(e ArrayEncoder) { e.AppendByteString([]byte("k")) }}, + {"byteString", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendByteString([]byte(`k\`)) }}, + {"complex128", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex128(1 + 2i) }}, + {"complex64", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex64(1 + 2i) }}, + {"durations", `[0.000000002,0.000000002]`, func(e ArrayEncoder) { e.AppendDuration(2) }}, + {"float64", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat64(3.14) }}, + {"float32", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat32(3.14) }}, + {"int", `[42,42]`, func(e ArrayEncoder) { e.AppendInt(42) }}, + {"int64", `[42,42]`, func(e ArrayEncoder) { e.AppendInt64(42) }}, + {"int32", `[42,42]`, func(e ArrayEncoder) { e.AppendInt32(42) }}, + {"int16", `[42,42]`, func(e ArrayEncoder) { e.AppendInt16(42) }}, + {"int8", `[42,42]`, func(e ArrayEncoder) { e.AppendInt8(42) }}, + {"string", `["k","k"]`, func(e ArrayEncoder) { e.AppendString("k") }}, + {"string", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendString(`k\`) }}, + {"times", `[1,1]`, func(e ArrayEncoder) { e.AppendTime(time.Unix(1, 0)) }}, + {"uint", `[42,42]`, func(e ArrayEncoder) { e.AppendUint(42) }}, + {"uint64", `[42,42]`, func(e ArrayEncoder) { e.AppendUint64(42) }}, + {"uint32", `[42,42]`, func(e ArrayEncoder) { e.AppendUint32(42) }}, + {"uint16", `[42,42]`, func(e ArrayEncoder) { e.AppendUint16(42) }}, + {"uint8", `[42,42]`, func(e ArrayEncoder) { e.AppendUint8(42) }}, + {"uintptr", `[42,42]`, func(e ArrayEncoder) { e.AppendUintptr(42) }}, + { + desc: "arrays (success)", + expected: `[[true],[true]]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + return nil + })), "Unexpected error appending an array.") + }, + }, + { + desc: "arrays (error)", + expected: `[[true],[true]]`, + f: func(arr ArrayEncoder) { + assert.Error(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + return errors.New("fail") + })), "Expected an error appending an array.") + }, + }, + { + desc: "objects (success)", + expected: `[{"loggable":"yes"},{"loggable":"yes"}]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendObject(loggable{true}), "Unexpected error appending an object.") + }, + }, + { + desc: "objects (error)", + expected: `[{},{}]`, + f: func(arr ArrayEncoder) { + assert.Error(t, arr.AppendObject(loggable{false}), "Expected an error appending an object.") + }, + }, + { + desc: "reflect (success)", + expected: `[{"foo":5},{"foo":5}]`, + f: func(arr ArrayEncoder) { + assert.NoError( + t, + arr.AppendReflected(map[string]int{"foo": 5}), + "Unexpected an error appending an object with reflection.", + ) + }, + }, + { + desc: "reflect (error)", + expected: `[]`, + f: func(arr ArrayEncoder) { + assert.Error( + t, + arr.AppendReflected(noJSON{}), + "Unexpected an error appending an object with reflection.", + ) + }, + }, + { + desc: "object (no nested namespace) then string", + expected: `[{"obj-out":"obj-outside-namespace"},"should-be-outside-obj",{"obj-out":"obj-outside-namespace"},"should-be-outside-obj"]`, + f: func(arr ArrayEncoder) { + arr.AppendObject(maybeNamespace{false}) + arr.AppendString("should-be-outside-obj") + }, + }, + { + desc: "object (with nested namespace) then string", + expected: `[{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj",{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj"]`, + f: func(arr ArrayEncoder) { + arr.AppendObject(maybeNamespace{true}) + arr.AppendString("should-be-outside-obj") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + f := func(enc Encoder) error { + return enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + tt.f(arr) + tt.f(arr) + return nil + })) + } + assertOutput(t, _defaultEncoderConfig, `"array":`+tt.expected, func(enc Encoder) { + err := f(enc) + assert.NoError(t, err, "Unexpected error adding array to JSON encoder.") + }) + }) + } +} + +func TestJSONEncoderTimeArrays(t *testing.T) { + times := []time.Time{ + time.Unix(1008720000, 0).UTC(), // 2001-12-19 + time.Unix(1040169600, 0).UTC(), // 2002-12-18 + time.Unix(1071619200, 0).UTC(), // 2003-12-17 + } + + tests := []struct { + desc string + encoder TimeEncoder + want string + }{ + { + desc: "epoch", + encoder: EpochTimeEncoder, + want: `[1008720000,1040169600,1071619200]`, + }, + { + desc: "epoch millis", + encoder: EpochMillisTimeEncoder, + want: `[1008720000000,1040169600000,1071619200000]`, + }, + { + desc: "iso8601", + encoder: ISO8601TimeEncoder, + want: `["2001-12-19T00:00:00.000Z","2002-12-18T00:00:00.000Z","2003-12-17T00:00:00.000Z"]`, + }, + { + desc: "rfc3339", + encoder: RFC3339TimeEncoder, + want: `["2001-12-19T00:00:00Z","2002-12-18T00:00:00Z","2003-12-17T00:00:00Z"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + cfg := _defaultEncoderConfig + cfg.EncodeTime = tt.encoder + + enc := &jsonEncoder{buf: bufferpool.Get(), EncoderConfig: &cfg} + err := enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + for _, time := range times { + arr.AppendTime(time) + } + return nil + })) + assert.NoError(t, err) + assert.Equal(t, `"array":`+tt.want, enc.buf.String()) + }) + } +} + +func assertJSON(t *testing.T, expected string, enc *jsonEncoder) { + assert.Equal(t, expected, enc.buf.String(), "Encoded JSON didn't match expectations.") +} + +func assertOutput(t testing.TB, cfg EncoderConfig, expected string, f func(Encoder)) { + enc := NewJSONEncoder(cfg).(*jsonEncoder) + f(enc) + assert.Equal(t, expected, enc.buf.String(), "Unexpected encoder output after adding.") + + enc.truncate() + enc.AddString("foo", "bar") + f(enc) + expectedPrefix := `"foo":"bar"` + if expected != "" { + // If we expect output, it should be comma-separated from the previous + // field. + expectedPrefix += "," + } + assert.Equal(t, expectedPrefix+expected, enc.buf.String(), "Unexpected encoder output after adding as a second field.") +} + +// Nested Array- and ObjectMarshalers. +type turducken struct{} + +func (t turducken) MarshalLogObject(enc ObjectEncoder) error { + return enc.AddArray("ducks", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + for i := 0; i < 2; i++ { + arr.AppendObject(ObjectMarshalerFunc(func(inner ObjectEncoder) error { + inner.AddString("in", "chicken") + return nil + })) + } + return nil + })) +} + +type turduckens int + +func (t turduckens) MarshalLogArray(enc ArrayEncoder) error { + var err error + tur := turducken{} + for i := 0; i < int(t); i++ { + err = multierr.Append(err, enc.AppendObject(tur)) + } + return err +} + +type loggable struct{ bool } + +func (l loggable) MarshalLogObject(enc ObjectEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AddString("loggable", "yes") + return nil +} + +func (l loggable) MarshalLogArray(enc ArrayEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AppendBool(true) + return nil +} + +// maybeNamespace is an ObjectMarshaler that sometimes opens a namespace +type maybeNamespace struct{ bool } + +func (m maybeNamespace) MarshalLogObject(enc ObjectEncoder) error { + enc.AddString("obj-out", "obj-outside-namespace") + if m.bool { + enc.OpenNamespace("obj-namespace") + enc.AddString("obj-in", "obj-inside-namespace") + } + return nil +} + +type noJSON struct{} + +func (nj noJSON) MarshalJSON() ([]byte, error) { + return nil, errors.New("no") +} + +func zapEncode(encode func(*jsonEncoder, string)) func(s string) []byte { + return func(s string) []byte { + enc := &jsonEncoder{buf: bufferpool.Get()} + // Escape and quote a string using our encoder. + var ret []byte + encode(enc, s) + ret = make([]byte, 0, enc.buf.Len()+2) + ret = append(ret, '"') + ret = append(ret, enc.buf.Bytes()...) + ret = append(ret, '"') + return ret + } +} + +func roundTripsCorrectly(encode func(string) []byte, original string) bool { + // Encode using our encoder, decode using the standard library, and assert + // that we haven't lost any information. + encoded := encode(original) + + var decoded string + err := json.Unmarshal(encoded, &decoded) + if err != nil { + return false + } + return original == decoded +} + +func roundTripsCorrectlyString(original string) bool { + return roundTripsCorrectly(zapEncode((*jsonEncoder).safeAddString), original) +} + +func roundTripsCorrectlyByteString(original string) bool { + return roundTripsCorrectly( + zapEncode(func(enc *jsonEncoder, s string) { + enc.safeAddByteString([]byte(s)) + }), + original) +} + +type ASCII string + +func (s ASCII) Generate(r *rand.Rand, size int) reflect.Value { + bs := make([]byte, size) + for i := range bs { + bs[i] = byte(r.Intn(128)) + } + a := ASCII(bs) + return reflect.ValueOf(a) +} + +func asciiRoundTripsCorrectlyString(s ASCII) bool { + return roundTripsCorrectlyString(string(s)) +} + +func asciiRoundTripsCorrectlyByteString(s ASCII) bool { + return roundTripsCorrectlyByteString(string(s)) +} + +func TestJSONQuick(t *testing.T) { + check := func(f interface{}) { + err := quick.Check(f, &quick.Config{MaxCountScale: 100.0}) + assert.NoError(t, err) + } + // Test the full range of UTF-8 strings. + check(roundTripsCorrectlyString) + check(roundTripsCorrectlyByteString) + + // Focus on ASCII strings. + check(asciiRoundTripsCorrectlyString) + check(asciiRoundTripsCorrectlyByteString) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_test.go new file mode 100644 index 0000000..4c651cf --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/json_encoder_test.go @@ -0,0 +1,268 @@ +// Copyright (c) 2018 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "io" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// TestJSONEncodeEntry is an more "integrated" test that makes it easier to get +// coverage on the json encoder (e.g. putJSONEncoder, let alone EncodeEntry +// itself) than the tests in json_encoder_impl_test.go; it needs to be in the +// zapcore_test package, so that it can import the toplevel zap package for +// field constructor convenience. +func TestJSONEncodeEntry(t *testing.T) { + type bar struct { + Key string `json:"key"` + Val float64 `json:"val"` + } + + type foo struct { + A string `json:"aee"` + B int `json:"bee"` + C float64 `json:"cee"` + D []bar `json:"dee"` + } + + tests := []struct { + desc string + expected string + ent zapcore.Entry + fields []zapcore.Field + }{ + { + desc: "info entry with some fields", + expected: `{ + "L": "info", + "T": "2018-06-19T16:33:42.000Z", + "N": "bob", + "M": "lob law", + "so": "passes", + "answer": 42, + "a_float32": 2.71, + "common_pie": 3.14, + "complex_value": "3.14-2.71i", + "null_value": null, + "array_with_null_elements": [{}, null, null, 2], + "such": { + "aee": "lol", + "bee": 123, + "cee": 0.9999, + "dee": [ + {"key": "pi", "val": 3.141592653589793}, + {"key": "tau", "val": 6.283185307179586} + ] + } + }`, + ent: zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC), + LoggerName: "bob", + Message: "lob law", + }, + fields: []zapcore.Field{ + zap.String("so", "passes"), + zap.Int("answer", 42), + zap.Float64("common_pie", 3.14), + zap.Float32("a_float32", 2.71), + zap.Complex128("complex_value", 3.14-2.71i), + // Cover special-cased handling of nil in AddReflect() and + // AppendReflect(). Note that for the latter, we explicitly test + // correct results for both the nil static interface{} value + // (`nil`), as well as the non-nil interface value with a + // dynamic type and nil value (`(*struct{})(nil)`). + zap.Reflect("null_value", nil), + zap.Reflect("array_with_null_elements", []interface{}{&struct{}{}, nil, (*struct{})(nil), 2}), + zap.Reflect("such", foo{ + A: "lol", + B: 123, + C: 0.9999, + D: []bar{ + {"pi", 3.141592653589793}, + {"tau", 6.283185307179586}, + }, + }), + }, + }, + } + + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "M", + LevelKey: "L", + TimeKey: "T", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + }) + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + buf, err := enc.EncodeEntry(tt.ent, tt.fields) + if assert.NoError(t, err, "Unexpected JSON encoding error.") { + assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.") + } + buf.Free() + }) + } +} + +func TestNoEncodeLevelSupplied(t *testing.T) { + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "M", + LevelKey: "L", + TimeKey: "T", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + }) + + ent := zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC), + LoggerName: "bob", + Message: "lob law", + } + + fields := []zapcore.Field{ + zap.Int("answer", 42), + } + + _, err := enc.EncodeEntry(ent, fields) + assert.NoError(t, err, "Unexpected JSON encoding error.") +} + +func TestJSONEmptyConfig(t *testing.T) { + tests := []struct { + name string + field zapcore.Field + expected string + }{ + { + name: "time", + field: zap.Time("foo", time.Unix(1591287718, 0)), // 2020-06-04 09:21:58 -0700 PDT + expected: `{"foo": 1591287718000000000}`, + }, + { + name: "duration", + field: zap.Duration("bar", time.Microsecond), + expected: `{"bar": 1000}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) + + buf, err := enc.EncodeEntry(zapcore.Entry{ + Level: zapcore.DebugLevel, + Time: time.Now(), + LoggerName: "mylogger", + Message: "things happened", + }, []zapcore.Field{tt.field}) + if assert.NoError(t, err, "Unexpected JSON encoding error.") { + assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.") + } + + buf.Free() + }) + } +} + +// Encodes any object into empty json '{}' +type emptyReflectedEncoder struct { + writer io.Writer +} + +func (enc *emptyReflectedEncoder) Encode(obj interface{}) error { + _, err := enc.writer.Write([]byte("{}")) + return err +} + +func TestJSONCustomReflectedEncoder(t *testing.T) { + tests := []struct { + name string + field zapcore.Field + expected string + }{ + { + name: "encode custom map object", + field: zapcore.Field{ + Key: "data", + Type: zapcore.ReflectType, + Interface: map[string]interface{}{ + "foo": "hello", + "bar": 1111, + }, + }, + expected: `{"data":{}}`, + }, + { + name: "encode nil object", + field: zapcore.Field{ + Key: "data", + Type: zapcore.ReflectType, + }, + expected: `{"data":null}`, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + NewReflectedEncoder: func(writer io.Writer) zapcore.ReflectedEncoder { + return &emptyReflectedEncoder{ + writer: writer, + } + }, + }) + + buf, err := enc.EncodeEntry(zapcore.Entry{ + Level: zapcore.DebugLevel, + Time: time.Now(), + LoggerName: "logger", + Message: "things happened", + }, []zapcore.Field{tt.field}) + if assert.NoError(t, err, "Unexpected JSON encoding error.") { + assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.") + } + buf.Free() + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/leak_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/leak_test.go new file mode 100644 index 0000000..4ef412e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/leak_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level.go new file mode 100644 index 0000000..e01a241 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level.go @@ -0,0 +1,229 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "errors" + "fmt" +) + +var errUnmarshalNilLevel = errors.New("can't unmarshal a nil *Level") + +// A Level is a logging priority. Higher levels are more important. +type Level int8 + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel Level = iota - 1 + // InfoLevel is the default logging priority. + InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel + // DPanicLevel logs are particularly important errors. In development the + // logger panics after writing the message. + DPanicLevel + // PanicLevel logs a message, then panics. + PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel + + _minLevel = DebugLevel + _maxLevel = FatalLevel + + // InvalidLevel is an invalid value for Level. + // + // Core implementations may panic if they see messages of this level. + InvalidLevel = _maxLevel + 1 +) + +// ParseLevel parses a level based on the lower-case or all-caps ASCII +// representation of the log level. If the provided ASCII representation is +// invalid an error is returned. +// +// This is particularly useful when dealing with text input to configure log +// levels. +func ParseLevel(text string) (Level, error) { + var level Level + err := level.UnmarshalText([]byte(text)) + return level, err +} + +type leveledEnabler interface { + LevelEnabler + + Level() Level +} + +// LevelOf reports the minimum enabled log level for the given LevelEnabler +// from Zap's supported log levels, or [InvalidLevel] if none of them are +// enabled. +// +// A LevelEnabler may implement a 'Level() Level' method to override the +// behavior of this function. +// +// func (c *core) Level() Level { +// return c.currentLevel +// } +// +// It is recommended that [Core] implementations that wrap other cores use +// LevelOf to retrieve the level of the wrapped core. For example, +// +// func (c *coreWrapper) Level() Level { +// return zapcore.LevelOf(c.wrappedCore) +// } +func LevelOf(enab LevelEnabler) Level { + if lvler, ok := enab.(leveledEnabler); ok { + return lvler.Level() + } + + for lvl := _minLevel; lvl <= _maxLevel; lvl++ { + if enab.Enabled(lvl) { + return lvl + } + } + + return InvalidLevel +} + +// String returns a lower-case ASCII representation of the log level. +func (l Level) String() string { + switch l { + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warn" + case ErrorLevel: + return "error" + case DPanicLevel: + return "dpanic" + case PanicLevel: + return "panic" + case FatalLevel: + return "fatal" + default: + return fmt.Sprintf("Level(%d)", l) + } +} + +// CapitalString returns an all-caps ASCII representation of the log level. +func (l Level) CapitalString() string { + // Printing levels in all-caps is common enough that we should export this + // functionality. + switch l { + case DebugLevel: + return "DEBUG" + case InfoLevel: + return "INFO" + case WarnLevel: + return "WARN" + case ErrorLevel: + return "ERROR" + case DPanicLevel: + return "DPANIC" + case PanicLevel: + return "PANIC" + case FatalLevel: + return "FATAL" + default: + return fmt.Sprintf("LEVEL(%d)", l) + } +} + +// MarshalText marshals the Level to text. Note that the text representation +// drops the -Level suffix (see example). +func (l Level) MarshalText() ([]byte, error) { + return []byte(l.String()), nil +} + +// UnmarshalText unmarshals text to a level. Like MarshalText, UnmarshalText +// expects the text representation of a Level to drop the -Level suffix (see +// example). +// +// In particular, this makes it easy to configure logging levels using YAML, +// TOML, or JSON files. +func (l *Level) UnmarshalText(text []byte) error { + if l == nil { + return errUnmarshalNilLevel + } + if !l.unmarshalText(text) && !l.unmarshalText(bytes.ToLower(text)) { + return fmt.Errorf("unrecognized level: %q", text) + } + return nil +} + +func (l *Level) unmarshalText(text []byte) bool { + switch string(text) { + case "debug", "DEBUG": + *l = DebugLevel + case "info", "INFO", "": // make the zero value useful + *l = InfoLevel + case "warn", "WARN": + *l = WarnLevel + case "error", "ERROR": + *l = ErrorLevel + case "dpanic", "DPANIC": + *l = DPanicLevel + case "panic", "PANIC": + *l = PanicLevel + case "fatal", "FATAL": + *l = FatalLevel + default: + return false + } + return true +} + +// Set sets the level for the flag.Value interface. +func (l *Level) Set(s string) error { + return l.UnmarshalText([]byte(s)) +} + +// Get gets the level for the flag.Getter interface. +func (l *Level) Get() interface{} { + return *l +} + +// Enabled returns true if the given level is at or above this level. +func (l Level) Enabled(lvl Level) bool { + return lvl >= l +} + +// LevelEnabler decides whether a given logging level is enabled when logging a +// message. +// +// Enablers are intended to be used to implement deterministic filters; +// concerns like sampling are better implemented as a Core. +// +// Each concrete Level value implements a static LevelEnabler which returns +// true for itself and all higher logging levels. For example WarnLevel.Enabled() +// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and +// FatalLevel, but return false for InfoLevel and DebugLevel. +type LevelEnabler interface { + Enabled(Level) bool +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_strings.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_strings.go new file mode 100644 index 0000000..7af8dad --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_strings.go @@ -0,0 +1,46 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/zap/internal/color" + +var ( + _levelToColor = map[Level]color.Color{ + DebugLevel: color.Magenta, + InfoLevel: color.Blue, + WarnLevel: color.Yellow, + ErrorLevel: color.Red, + DPanicLevel: color.Red, + PanicLevel: color.Red, + FatalLevel: color.Red, + } + _unknownLevelColor = color.Red + + _levelToLowercaseColorString = make(map[Level]string, len(_levelToColor)) + _levelToCapitalColorString = make(map[Level]string, len(_levelToColor)) +) + +func init() { + for level, color := range _levelToColor { + _levelToLowercaseColorString[level] = color.Add(level.String()) + _levelToCapitalColorString[level] = color.Add(level.CapitalString()) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_strings_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_strings_test.go new file mode 100644 index 0000000..14b0bac --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_strings_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAllLevelsCoveredByLevelString(t *testing.T) { + numLevels := int((_maxLevel - _minLevel) + 1) + + isComplete := func(m map[Level]string) bool { + return len(m) == numLevels + } + + assert.True(t, isComplete(_levelToLowercaseColorString), "Colored lowercase strings don't cover all levels.") + assert.True(t, isComplete(_levelToCapitalColorString), "Colored capital strings don't cover all levels.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_test.go new file mode 100644 index 0000000..4543322 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/level_test.go @@ -0,0 +1,249 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "flag" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLevelString(t *testing.T) { + tests := map[Level]string{ + DebugLevel: "debug", + InfoLevel: "info", + WarnLevel: "warn", + ErrorLevel: "error", + DPanicLevel: "dpanic", + PanicLevel: "panic", + FatalLevel: "fatal", + Level(-42): "Level(-42)", + InvalidLevel: "Level(6)", // InvalidLevel does not have a name + } + + for lvl, stringLevel := range tests { + assert.Equal(t, stringLevel, lvl.String(), "Unexpected lowercase level string.") + assert.Equal(t, strings.ToUpper(stringLevel), lvl.CapitalString(), "Unexpected all-caps level string.") + } +} + +func TestLevelText(t *testing.T) { + tests := []struct { + text string + level Level + }{ + {"debug", DebugLevel}, + {"info", InfoLevel}, + {"", InfoLevel}, // make the zero value useful + {"warn", WarnLevel}, + {"error", ErrorLevel}, + {"dpanic", DPanicLevel}, + {"panic", PanicLevel}, + {"fatal", FatalLevel}, + } + for _, tt := range tests { + if tt.text != "" { + lvl := tt.level + marshaled, err := lvl.MarshalText() + assert.NoError(t, err, "Unexpected error marshaling level %v to text.", &lvl) + assert.Equal(t, tt.text, string(marshaled), "Marshaling level %v to text yielded unexpected result.", &lvl) + } + + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestParseLevel(t *testing.T) { + tests := []struct { + text string + level Level + err string + }{ + {"info", InfoLevel, ""}, + {"DEBUG", DebugLevel, ""}, + {"FOO", 0, `unrecognized level: "FOO"`}, + } + for _, tt := range tests { + parsedLevel, err := ParseLevel(tt.text) + if len(tt.err) == 0 { + assert.NoError(t, err) + assert.Equal(t, tt.level, parsedLevel) + } else { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.err) + } + } +} + +func TestCapitalLevelsParse(t *testing.T) { + tests := []struct { + text string + level Level + }{ + {"DEBUG", DebugLevel}, + {"INFO", InfoLevel}, + {"WARN", WarnLevel}, + {"ERROR", ErrorLevel}, + {"DPANIC", DPanicLevel}, + {"PANIC", PanicLevel}, + {"FATAL", FatalLevel}, + } + for _, tt := range tests { + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestWeirdLevelsParse(t *testing.T) { + tests := []struct { + text string + level Level + }{ + // I guess... + {"Debug", DebugLevel}, + {"Info", InfoLevel}, + {"Warn", WarnLevel}, + {"Error", ErrorLevel}, + {"Dpanic", DPanicLevel}, + {"Panic", PanicLevel}, + {"Fatal", FatalLevel}, + + // What even is... + {"DeBuG", DebugLevel}, + {"InFo", InfoLevel}, + {"WaRn", WarnLevel}, + {"ErRor", ErrorLevel}, + {"DpAnIc", DPanicLevel}, + {"PaNiC", PanicLevel}, + {"FaTaL", FatalLevel}, + } + for _, tt := range tests { + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestLevelNils(t *testing.T) { + var l *Level + + // The String() method will not handle nil level properly. + assert.Panics(t, func() { + assert.Equal(t, "Level(nil)", l.String(), "Unexpected result stringifying nil *Level.") + }, "Level(nil).String() should panic") + + assert.Panics(t, func() { + l.MarshalText() + }, "Expected to panic when marshalling a nil level.") + + err := l.UnmarshalText([]byte("debug")) + assert.Equal(t, errUnmarshalNilLevel, err, "Expected to error unmarshalling into a nil Level.") +} + +func TestLevelUnmarshalUnknownText(t *testing.T) { + var l Level + err := l.UnmarshalText([]byte("foo")) + assert.Contains(t, err.Error(), "unrecognized level", "Expected unmarshaling arbitrary text to fail.") +} + +func TestLevelAsFlagValue(t *testing.T) { + var ( + buf bytes.Buffer + lvl Level + ) + fs := flag.NewFlagSet("levelTest", flag.ContinueOnError) + fs.SetOutput(&buf) + fs.Var(&lvl, "level", "log level") + + for _, expected := range []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} { + assert.NoError(t, fs.Parse([]string{"-level", expected.String()})) + assert.Equal(t, expected, lvl, "Unexpected level after parsing flag.") + assert.Equal(t, expected, lvl.Get(), "Unexpected output using flag.Getter API.") + assert.Empty(t, buf.String(), "Unexpected error output parsing level flag.") + buf.Reset() + } + + assert.Error(t, fs.Parse([]string{"-level", "nope"})) + assert.Equal( + t, + `invalid value "nope" for flag -level: unrecognized level: "nope"`, + strings.Split(buf.String(), "\n")[0], // second line is help message + "Unexpected error output from invalid flag input.", + ) +} + +// enablerWithCustomLevel is a LevelEnabler that implements a custom Level +// method. +type enablerWithCustomLevel struct{ lvl Level } + +var _ leveledEnabler = (*enablerWithCustomLevel)(nil) + +func (l *enablerWithCustomLevel) Enabled(lvl Level) bool { + return l.lvl.Enabled(lvl) +} + +func (l *enablerWithCustomLevel) Level() Level { + return l.lvl +} + +func TestLevelOf(t *testing.T) { + tests := []struct { + desc string + give LevelEnabler + want Level + }{ + {desc: "debug", give: DebugLevel, want: DebugLevel}, + {desc: "info", give: InfoLevel, want: InfoLevel}, + {desc: "warn", give: WarnLevel, want: WarnLevel}, + {desc: "error", give: ErrorLevel, want: ErrorLevel}, + {desc: "dpanic", give: DPanicLevel, want: DPanicLevel}, + {desc: "panic", give: PanicLevel, want: PanicLevel}, + {desc: "fatal", give: FatalLevel, want: FatalLevel}, + { + desc: "leveledEnabler", + give: &enablerWithCustomLevel{lvl: InfoLevel}, + want: InfoLevel, + }, + { + desc: "noop", + give: NewNopCore(), // always disabled + want: InvalidLevel, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tt.want, LevelOf(tt.give), "Reported level did not match.") + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/marshaler.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/marshaler.go new file mode 100644 index 0000000..c3c55ba --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/marshaler.go @@ -0,0 +1,61 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// ObjectMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +// +// Note: ObjectMarshaler is only used when zap.Object is used or when +// passed directly to zap.Any. It is not used when reflection-based +// encoding is used. +type ObjectMarshaler interface { + MarshalLogObject(ObjectEncoder) error +} + +// ObjectMarshalerFunc is a type adapter that turns a function into an +// ObjectMarshaler. +type ObjectMarshalerFunc func(ObjectEncoder) error + +// MarshalLogObject calls the underlying function. +func (f ObjectMarshalerFunc) MarshalLogObject(enc ObjectEncoder) error { + return f(enc) +} + +// ArrayMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +// +// Note: ArrayMarshaler is only used when zap.Array is used or when +// passed directly to zap.Any. It is not used when reflection-based +// encoding is used. +type ArrayMarshaler interface { + MarshalLogArray(ArrayEncoder) error +} + +// ArrayMarshalerFunc is a type adapter that turns a function into an +// ArrayMarshaler. +type ArrayMarshalerFunc func(ArrayEncoder) error + +// MarshalLogArray calls the underlying function. +func (f ArrayMarshalerFunc) MarshalLogArray(enc ArrayEncoder) error { + return f(enc) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/memory_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/memory_encoder.go new file mode 100644 index 0000000..dfead08 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/memory_encoder.go @@ -0,0 +1,179 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "time" + +// MapObjectEncoder is an ObjectEncoder backed by a simple +// map[string]interface{}. It's not fast enough for production use, but it's +// helpful in tests. +type MapObjectEncoder struct { + // Fields contains the entire encoded log context. + Fields map[string]interface{} + // cur is a pointer to the namespace we're currently writing to. + cur map[string]interface{} +} + +// NewMapObjectEncoder creates a new map-backed ObjectEncoder. +func NewMapObjectEncoder() *MapObjectEncoder { + m := make(map[string]interface{}) + return &MapObjectEncoder{ + Fields: m, + cur: m, + } +} + +// AddArray implements ObjectEncoder. +func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error { + arr := &sliceArrayEncoder{elems: make([]interface{}, 0)} + err := v.MarshalLogArray(arr) + m.cur[key] = arr.elems + return err +} + +// AddObject implements ObjectEncoder. +func (m *MapObjectEncoder) AddObject(k string, v ObjectMarshaler) error { + newMap := NewMapObjectEncoder() + m.cur[k] = newMap.Fields + return v.MarshalLogObject(newMap) +} + +// AddBinary implements ObjectEncoder. +func (m *MapObjectEncoder) AddBinary(k string, v []byte) { m.cur[k] = v } + +// AddByteString implements ObjectEncoder. +func (m *MapObjectEncoder) AddByteString(k string, v []byte) { m.cur[k] = string(v) } + +// AddBool implements ObjectEncoder. +func (m *MapObjectEncoder) AddBool(k string, v bool) { m.cur[k] = v } + +// AddDuration implements ObjectEncoder. +func (m MapObjectEncoder) AddDuration(k string, v time.Duration) { m.cur[k] = v } + +// AddComplex128 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex128(k string, v complex128) { m.cur[k] = v } + +// AddComplex64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex64(k string, v complex64) { m.cur[k] = v } + +// AddFloat64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat64(k string, v float64) { m.cur[k] = v } + +// AddFloat32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat32(k string, v float32) { m.cur[k] = v } + +// AddInt implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt(k string, v int) { m.cur[k] = v } + +// AddInt64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt64(k string, v int64) { m.cur[k] = v } + +// AddInt32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt32(k string, v int32) { m.cur[k] = v } + +// AddInt16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt16(k string, v int16) { m.cur[k] = v } + +// AddInt8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt8(k string, v int8) { m.cur[k] = v } + +// AddString implements ObjectEncoder. +func (m *MapObjectEncoder) AddString(k string, v string) { m.cur[k] = v } + +// AddTime implements ObjectEncoder. +func (m MapObjectEncoder) AddTime(k string, v time.Time) { m.cur[k] = v } + +// AddUint implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint(k string, v uint) { m.cur[k] = v } + +// AddUint64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint64(k string, v uint64) { m.cur[k] = v } + +// AddUint32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint32(k string, v uint32) { m.cur[k] = v } + +// AddUint16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint16(k string, v uint16) { m.cur[k] = v } + +// AddUint8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint8(k string, v uint8) { m.cur[k] = v } + +// AddUintptr implements ObjectEncoder. +func (m *MapObjectEncoder) AddUintptr(k string, v uintptr) { m.cur[k] = v } + +// AddReflected implements ObjectEncoder. +func (m *MapObjectEncoder) AddReflected(k string, v interface{}) error { + m.cur[k] = v + return nil +} + +// OpenNamespace implements ObjectEncoder. +func (m *MapObjectEncoder) OpenNamespace(k string) { + ns := make(map[string]interface{}) + m.cur[k] = ns + m.cur = ns +} + +// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like +// the MapObjectEncoder, it's not designed for production use. +type sliceArrayEncoder struct { + elems []interface{} +} + +func (s *sliceArrayEncoder) AppendArray(v ArrayMarshaler) error { + enc := &sliceArrayEncoder{} + err := v.MarshalLogArray(enc) + s.elems = append(s.elems, enc.elems) + return err +} + +func (s *sliceArrayEncoder) AppendObject(v ObjectMarshaler) error { + m := NewMapObjectEncoder() + err := v.MarshalLogObject(m) + s.elems = append(s.elems, m.Fields) + return err +} + +func (s *sliceArrayEncoder) AppendReflected(v interface{}) error { + s.elems = append(s.elems, v) + return nil +} + +func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, string(v)) } +func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) } diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/memory_encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/memory_encoder_test.go new file mode 100644 index 0000000..052bdb0 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/memory_encoder_test.go @@ -0,0 +1,364 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMapObjectEncoderAdd(t *testing.T) { + // Expected output of a turducken. + wantTurducken := map[string]interface{}{ + "ducks": []interface{}{ + map[string]interface{}{"in": "chicken"}, + map[string]interface{}{"in": "chicken"}, + }, + } + + tests := []struct { + desc string + f func(ObjectEncoder) + expected interface{} + }{ + { + desc: "AddObject", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddObject("k", loggable{true}), "Expected AddObject to succeed.") + }, + expected: map[string]interface{}{"loggable": "yes"}, + }, + { + desc: "AddObject (nested)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddObject("k", turducken{}), "Expected AddObject to succeed.") + }, + expected: wantTurducken, + }, + { + desc: "AddArray", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + arr.AppendBool(true) + arr.AppendBool(false) + arr.AppendBool(true) + return nil + })), "Expected AddArray to succeed.") + }, + expected: []interface{}{true, false, true}, + }, + { + desc: "AddArray (nested)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.") + }, + expected: []interface{}{wantTurducken, wantTurducken}, + }, + { + desc: "AddArray (empty)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", turduckens(0)), "Expected AddArray to succeed.") + }, + expected: []interface{}{}, + }, + { + desc: "AddBinary", + f: func(e ObjectEncoder) { e.AddBinary("k", []byte("foo")) }, + expected: []byte("foo"), + }, + { + desc: "AddByteString", + f: func(e ObjectEncoder) { e.AddByteString("k", []byte("foo")) }, + expected: "foo", + }, + { + desc: "AddBool", + f: func(e ObjectEncoder) { e.AddBool("k", true) }, + expected: true, + }, + { + desc: "AddComplex128", + f: func(e ObjectEncoder) { e.AddComplex128("k", 1+2i) }, + expected: 1 + 2i, + }, + { + desc: "AddComplex64", + f: func(e ObjectEncoder) { e.AddComplex64("k", 1+2i) }, + expected: complex64(1 + 2i), + }, + { + desc: "AddDuration", + f: func(e ObjectEncoder) { e.AddDuration("k", time.Millisecond) }, + expected: time.Millisecond, + }, + { + desc: "AddFloat64", + f: func(e ObjectEncoder) { e.AddFloat64("k", 3.14) }, + expected: 3.14, + }, + { + desc: "AddFloat32", + f: func(e ObjectEncoder) { e.AddFloat32("k", 3.14) }, + expected: float32(3.14), + }, + { + desc: "AddInt", + f: func(e ObjectEncoder) { e.AddInt("k", 42) }, + expected: 42, + }, + { + desc: "AddInt64", + f: func(e ObjectEncoder) { e.AddInt64("k", 42) }, + expected: int64(42), + }, + { + desc: "AddInt32", + f: func(e ObjectEncoder) { e.AddInt32("k", 42) }, + expected: int32(42), + }, + { + desc: "AddInt16", + f: func(e ObjectEncoder) { e.AddInt16("k", 42) }, + expected: int16(42), + }, + { + desc: "AddInt8", + f: func(e ObjectEncoder) { e.AddInt8("k", 42) }, + expected: int8(42), + }, + { + desc: "AddString", + f: func(e ObjectEncoder) { e.AddString("k", "v") }, + expected: "v", + }, + { + desc: "AddTime", + f: func(e ObjectEncoder) { e.AddTime("k", time.Unix(0, 100)) }, + expected: time.Unix(0, 100), + }, + { + desc: "AddUint", + f: func(e ObjectEncoder) { e.AddUint("k", 42) }, + expected: uint(42), + }, + { + desc: "AddUint64", + f: func(e ObjectEncoder) { e.AddUint64("k", 42) }, + expected: uint64(42), + }, + { + desc: "AddUint32", + f: func(e ObjectEncoder) { e.AddUint32("k", 42) }, + expected: uint32(42), + }, + { + desc: "AddUint16", + f: func(e ObjectEncoder) { e.AddUint16("k", 42) }, + expected: uint16(42), + }, + { + desc: "AddUint8", + f: func(e ObjectEncoder) { e.AddUint8("k", 42) }, + expected: uint8(42), + }, + { + desc: "AddUintptr", + f: func(e ObjectEncoder) { e.AddUintptr("k", 42) }, + expected: uintptr(42), + }, + { + desc: "AddReflected", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddReflected("k", map[string]interface{}{"foo": 5}), "Expected AddReflected to succeed.") + }, + expected: map[string]interface{}{"foo": 5}, + }, + { + desc: "OpenNamespace", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + e.AddInt("foo", 1) + e.OpenNamespace("middle") + e.AddInt("foo", 2) + e.OpenNamespace("inner") + e.AddInt("foo", 3) + }, + expected: map[string]interface{}{ + "foo": 1, + "middle": map[string]interface{}{ + "foo": 2, + "inner": map[string]interface{}{ + "foo": 3, + }, + }, + }, + }, + { + desc: "object (no nested namespace) then string", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + e.AddObject("obj", maybeNamespace{false}) + e.AddString("not-obj", "should-be-outside-obj") + }, + expected: map[string]interface{}{ + "obj": map[string]interface{}{ + "obj-out": "obj-outside-namespace", + }, + "not-obj": "should-be-outside-obj", + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + e.AddObject("obj", maybeNamespace{true}) + e.AddString("not-obj", "should-be-outside-obj") + }, + expected: map[string]interface{}{ + "obj": map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "not-obj": "should-be-outside-obj", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := NewMapObjectEncoder() + tt.f(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "Unexpected encoder output.") + }) + } +} +func TestSliceArrayEncoderAppend(t *testing.T) { + tests := []struct { + desc string + f func(ArrayEncoder) + expected interface{} + }{ + // AppendObject and AppendArray are covered by the AddObject (nested) and + // AddArray (nested) cases above. + {"AppendBool", func(e ArrayEncoder) { e.AppendBool(true) }, true}, + {"AppendByteString", func(e ArrayEncoder) { e.AppendByteString([]byte("foo")) }, "foo"}, + {"AppendComplex128", func(e ArrayEncoder) { e.AppendComplex128(1 + 2i) }, 1 + 2i}, + {"AppendComplex64", func(e ArrayEncoder) { e.AppendComplex64(1 + 2i) }, complex64(1 + 2i)}, + {"AppendDuration", func(e ArrayEncoder) { e.AppendDuration(time.Second) }, time.Second}, + {"AppendFloat64", func(e ArrayEncoder) { e.AppendFloat64(3.14) }, 3.14}, + {"AppendFloat32", func(e ArrayEncoder) { e.AppendFloat32(3.14) }, float32(3.14)}, + {"AppendInt", func(e ArrayEncoder) { e.AppendInt(42) }, 42}, + {"AppendInt64", func(e ArrayEncoder) { e.AppendInt64(42) }, int64(42)}, + {"AppendInt32", func(e ArrayEncoder) { e.AppendInt32(42) }, int32(42)}, + {"AppendInt16", func(e ArrayEncoder) { e.AppendInt16(42) }, int16(42)}, + {"AppendInt8", func(e ArrayEncoder) { e.AppendInt8(42) }, int8(42)}, + {"AppendString", func(e ArrayEncoder) { e.AppendString("foo") }, "foo"}, + {"AppendTime", func(e ArrayEncoder) { e.AppendTime(time.Unix(0, 100)) }, time.Unix(0, 100)}, + {"AppendUint", func(e ArrayEncoder) { e.AppendUint(42) }, uint(42)}, + {"AppendUint64", func(e ArrayEncoder) { e.AppendUint64(42) }, uint64(42)}, + {"AppendUint32", func(e ArrayEncoder) { e.AppendUint32(42) }, uint32(42)}, + {"AppendUint16", func(e ArrayEncoder) { e.AppendUint16(42) }, uint16(42)}, + {"AppendUint8", func(e ArrayEncoder) { e.AppendUint8(42) }, uint8(42)}, + {"AppendUintptr", func(e ArrayEncoder) { e.AppendUintptr(42) }, uintptr(42)}, + { + desc: "AppendReflected", + f: func(e ArrayEncoder) { e.AppendReflected(map[string]interface{}{"foo": 5}) }, + expected: map[string]interface{}{"foo": 5}, + }, + { + desc: "AppendArray (arrays of arrays)", + f: func(e ArrayEncoder) { + e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + inner.AppendBool(false) + return nil + })) + }, + expected: []interface{}{true, false}, + }, + { + desc: "object (no nested namespace) then string", + f: func(e ArrayEncoder) { + e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendObject(maybeNamespace{false}) + inner.AppendString("should-be-outside-obj") + return nil + })) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + }, + "should-be-outside-obj", + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e ArrayEncoder) { + e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendObject(maybeNamespace{true}) + inner.AppendString("should-be-outside-obj") + return nil + })) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "should-be-outside-obj", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := NewMapObjectEncoder() + assert.NoError(t, enc.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + tt.f(arr) + tt.f(arr) + return nil + })), "Expected AddArray to succeed.") + + arr, ok := enc.Fields["k"].([]interface{}) + require.True(t, ok, "Test case %s didn't encode an array.", tt.desc) + assert.Equal(t, []interface{}{tt.expected, tt.expected}, arr, "Unexpected encoder output.") + }) + } +} + +func TestMapObjectEncoderReflectionFailures(t *testing.T) { + enc := NewMapObjectEncoder() + assert.Error(t, enc.AddObject("object", loggable{false}), "Expected AddObject to fail.") + assert.Equal( + t, + map[string]interface{}{"object": map[string]interface{}{}}, + enc.Fields, + "Expected encoder to use empty values on errors.", + ) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/reflected_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/reflected_encoder.go new file mode 100644 index 0000000..8746360 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/reflected_encoder.go @@ -0,0 +1,41 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "io" +) + +// ReflectedEncoder serializes log fields that can't be serialized with Zap's +// JSON encoder. These have the ReflectType field type. +// Use EncoderConfig.NewReflectedEncoder to set this. +type ReflectedEncoder interface { + // Encode encodes and writes to the underlying data stream. + Encode(interface{}) error +} + +func defaultReflectedEncoder(w io.Writer) ReflectedEncoder { + enc := json.NewEncoder(w) + // For consistency with our custom JSON encoder. + enc.SetEscapeHTML(false) + return enc +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler.go new file mode 100644 index 0000000..dc51805 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler.go @@ -0,0 +1,230 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "time" + + "go.uber.org/atomic" +) + +const ( + _numLevels = _maxLevel - _minLevel + 1 + _countersPerLevel = 4096 +) + +type counter struct { + resetAt atomic.Int64 + counter atomic.Uint64 +} + +type counters [_numLevels][_countersPerLevel]counter + +func newCounters() *counters { + return &counters{} +} + +func (cs *counters) get(lvl Level, key string) *counter { + i := lvl - _minLevel + j := fnv32a(key) % _countersPerLevel + return &cs[i][j] +} + +// fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc +func fnv32a(s string) uint32 { + const ( + offset32 = 2166136261 + prime32 = 16777619 + ) + hash := uint32(offset32) + for i := 0; i < len(s); i++ { + hash ^= uint32(s[i]) + hash *= prime32 + } + return hash +} + +func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { + tn := t.UnixNano() + resetAfter := c.resetAt.Load() + if resetAfter > tn { + return c.counter.Inc() + } + + c.counter.Store(1) + + newResetAfter := tn + tick.Nanoseconds() + if !c.resetAt.CAS(resetAfter, newResetAfter) { + // We raced with another goroutine trying to reset, and it also reset + // the counter to 1, so we need to reincrement the counter. + return c.counter.Inc() + } + + return 1 +} + +// SamplingDecision is a decision represented as a bit field made by sampler. +// More decisions may be added in the future. +type SamplingDecision uint32 + +const ( + // LogDropped indicates that the Sampler dropped a log entry. + LogDropped SamplingDecision = 1 << iota + // LogSampled indicates that the Sampler sampled a log entry. + LogSampled +) + +// optionFunc wraps a func so it satisfies the SamplerOption interface. +type optionFunc func(*sampler) + +func (f optionFunc) apply(s *sampler) { + f(s) +} + +// SamplerOption configures a Sampler. +type SamplerOption interface { + apply(*sampler) +} + +// nopSamplingHook is the default hook used by sampler. +func nopSamplingHook(Entry, SamplingDecision) {} + +// SamplerHook registers a function which will be called when Sampler makes a +// decision. +// +// This hook may be used to get visibility into the performance of the sampler. +// For example, use it to track metrics of dropped versus sampled logs. +// +// var dropped atomic.Int64 +// zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) { +// if dec&zapcore.LogDropped > 0 { +// dropped.Inc() +// } +// }) +func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption { + return optionFunc(func(s *sampler) { + s.hook = hook + }) +} + +// NewSamplerWithOptions creates a Core that samples incoming entries, which +// caps the CPU and I/O load of logging while attempting to preserve a +// representative subset of your logs. +// +// Zap samples by logging the first N entries with a given level and message +// each tick. If more Entries with the same level and message are seen during +// the same interval, every Mth message is logged and the rest are dropped. +// +// For example, +// +// core = NewSamplerWithOptions(core, time.Second, 10, 5) +// +// This will log the first 10 log entries with the same level and message +// in a one second interval as-is. Following that, it will allow through +// every 5th log entry with the same level and message in that interval. +// +// If thereafter is zero, the Core will drop all log entries after the first N +// in that interval. +// +// Sampler can be configured to report sampling decisions with the SamplerHook +// option. +// +// Keep in mind that Zap's sampling implementation is optimized for speed over +// absolute precision; under load, each tick may be slightly over- or +// under-sampled. +func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core { + s := &sampler{ + Core: core, + tick: tick, + counts: newCounters(), + first: uint64(first), + thereafter: uint64(thereafter), + hook: nopSamplingHook, + } + for _, opt := range opts { + opt.apply(s) + } + + return s +} + +type sampler struct { + Core + + counts *counters + tick time.Duration + first, thereafter uint64 + hook func(Entry, SamplingDecision) +} + +var ( + _ Core = (*sampler)(nil) + _ leveledEnabler = (*sampler)(nil) +) + +// NewSampler creates a Core that samples incoming entries, which +// caps the CPU and I/O load of logging while attempting to preserve a +// representative subset of your logs. +// +// Zap samples by logging the first N entries with a given level and message +// each tick. If more Entries with the same level and message are seen during +// the same interval, every Mth message is logged and the rest are dropped. +// +// Keep in mind that zap's sampling implementation is optimized for speed over +// absolute precision; under load, each tick may be slightly over- or +// under-sampled. +// +// Deprecated: use NewSamplerWithOptions. +func NewSampler(core Core, tick time.Duration, first, thereafter int) Core { + return NewSamplerWithOptions(core, tick, first, thereafter) +} + +func (s *sampler) Level() Level { + return LevelOf(s.Core) +} + +func (s *sampler) With(fields []Field) Core { + return &sampler{ + Core: s.Core.With(fields), + tick: s.tick, + counts: s.counts, + first: s.first, + thereafter: s.thereafter, + hook: s.hook, + } +} + +func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !s.Enabled(ent.Level) { + return ce + } + + if ent.Level >= _minLevel && ent.Level <= _maxLevel { + counter := s.counts.get(ent.Level, ent.Message) + n := counter.IncCheckReset(ent.Time, s.tick) + if n > s.first && (s.thereafter == 0 || (n-s.first)%s.thereafter != 0) { + s.hook(ent, LogDropped) + return ce + } + s.hook(ent, LogSampled) + } + return s.Core.Check(ent, ce) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler_bench_test.go new file mode 100644 index 0000000..26769a9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler_bench_test.go @@ -0,0 +1,285 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/atomic" + "go.uber.org/zap/internal/ztest" + . "go.uber.org/zap/zapcore" +) + +var counterTestCases = [][]string{ + // some stuff I made up + { + "foo", + "bar", + "baz", + "alpha", + "bravo", + "charlie", + "delta", + }, + + // shuf -n50 /usr/share/dict/words + { + "unbracing", + "stereotomy", + "supranervian", + "moaning", + "exchangeability", + "gunyang", + "sulcation", + "dariole", + "archheresy", + "synchronistically", + "clips", + "unsanctioned", + "Argoan", + "liparomphalus", + "layship", + "Fregatae", + "microzoology", + "glaciaria", + "Frugivora", + "patterist", + "Grossulariaceae", + "lithotint", + "bargander", + "opisthographical", + "cacography", + "chalkstone", + "nonsubstantialism", + "sardonicism", + "calamiform", + "lodginghouse", + "predisposedly", + "topotypic", + "broideress", + "outrange", + "gingivolabial", + "monoazo", + "sparlike", + "concameration", + "untoothed", + "Camorrism", + "reissuer", + "soap", + "palaiotype", + "countercharm", + "yellowbird", + "palterly", + "writinger", + "boatfalls", + "tuglike", + "underbitten", + }, + + // shuf -n100 /usr/share/dict/words + { + "rooty", + "malcultivation", + "degrade", + "pseudoindependent", + "stillatory", + "antiseptize", + "protoamphibian", + "antiar", + "Esther", + "pseudelminth", + "superfluitance", + "teallite", + "disunity", + "spirignathous", + "vergency", + "myliobatid", + "inosic", + "overabstemious", + "patriarchally", + "foreimagine", + "coetaneity", + "hemimellitene", + "hyperspatial", + "aulophyte", + "electropoion", + "antitrope", + "Amarantus", + "smaltine", + "lighthead", + "syntonically", + "incubous", + "versation", + "cirsophthalmia", + "Ulidian", + "homoeography", + "Velella", + "Hecatean", + "serfage", + "Spermaphyta", + "palatoplasty", + "electroextraction", + "aconite", + "avirulence", + "initiator", + "besmear", + "unrecognizably", + "euphoniousness", + "balbuties", + "pascuage", + "quebracho", + "Yakala", + "auriform", + "sevenbark", + "superorganism", + "telesterion", + "ensand", + "nagaika", + "anisuria", + "etching", + "soundingly", + "grumpish", + "drillmaster", + "perfumed", + "dealkylate", + "anthracitiferous", + "predefiance", + "sulphoxylate", + "freeness", + "untucking", + "misworshiper", + "Nestorianize", + "nonegoistical", + "construe", + "upstroke", + "teated", + "nasolachrymal", + "Mastodontidae", + "gallows", + "radioluminescent", + "uncourtierlike", + "phasmatrope", + "Clunisian", + "drainage", + "sootless", + "brachyfacial", + "antiheroism", + "irreligionize", + "ked", + "unfact", + "nonprofessed", + "milady", + "conjecture", + "Arctomys", + "guapilla", + "Sassenach", + "emmetrope", + "rosewort", + "raphidiferous", + "pooh", + "Tyndallize", + }, +} + +func BenchmarkSampler_Check(b *testing.B) { + for _, keys := range counterTestCases { + b.Run(fmt.Sprintf("%v keys", len(keys)), func(b *testing.B) { + fac := NewSamplerWithOptions( + NewCore( + NewJSONEncoder(testEncoderConfig()), + &ztest.Discarder{}, + DebugLevel, + ), + time.Millisecond, 1, 1000) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ent := Entry{ + Level: DebugLevel + Level(i%4), + Message: keys[i], + } + _ = fac.Check(ent, nil) + i++ + if n := len(keys); i >= n { + i -= n + } + } + }) + }) + } +} + +func makeSamplerCountingHook() (func(_ Entry, dec SamplingDecision), *atomic.Int64, *atomic.Int64) { + droppedCount := new(atomic.Int64) + sampledCount := new(atomic.Int64) + h := func(_ Entry, dec SamplingDecision) { + if dec&LogDropped > 0 { + droppedCount.Inc() + } else if dec&LogSampled > 0 { + sampledCount.Inc() + } + } + return h, droppedCount, sampledCount +} + +func BenchmarkSampler_CheckWithHook(b *testing.B) { + hook, dropped, sampled := makeSamplerCountingHook() + for _, keys := range counterTestCases { + b.Run(fmt.Sprintf("%v keys", len(keys)), func(b *testing.B) { + fac := NewSamplerWithOptions( + NewCore( + NewJSONEncoder(testEncoderConfig()), + &ztest.Discarder{}, + DebugLevel, + ), + time.Millisecond, + 1, + 1000, + SamplerHook(hook), + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ent := Entry{ + Level: DebugLevel + Level(i%4), + Message: keys[i], + } + _ = fac.Check(ent, nil) + i++ + if n := len(keys); i >= n { + i -= n + } + } + }) + // We expect to see 1000 dropped messages for every sampled per settings, + // with a delta due to less 1000 messages getting dropped after initial one + // is sampled. + assert.Greater(b, dropped.Load()/1000, sampled.Load()-1000) + dropped.Store(0) + sampled.Store(0) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler_test.go new file mode 100644 index 0000000..194427a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/sampler_test.go @@ -0,0 +1,328 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "fmt" + "runtime" + "sync" + "testing" + "time" + + "go.uber.org/atomic" + "go.uber.org/zap/internal/ztest" + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func fakeSampler(lvl LevelEnabler, tick time.Duration, first, thereafter int) (Core, *observer.ObservedLogs) { + core, logs := observer.New(lvl) + // Keep using deprecated constructor for cc. + core = NewSampler(core, tick, first, thereafter) + return core, logs +} + +func assertSequence(t testing.TB, logs []observer.LoggedEntry, lvl Level, seq ...int64) { + seen := make([]int64, len(logs)) + for i, entry := range logs { + require.Equal(t, "", entry.Message, "Message wasn't created by writeSequence.") + require.Equal(t, 1, len(entry.Context), "Unexpected number of fields.") + require.Equal(t, lvl, entry.Level, "Unexpected level.") + f := entry.Context[0] + require.Equal(t, "iter", f.Key, "Unexpected field key.") + require.Equal(t, Int64Type, f.Type, "Unexpected field type") + seen[i] = f.Integer + } + assert.Equal(t, seq, seen, "Unexpected sequence logged at level %v.", lvl) +} + +func writeSequence(core Core, n int, lvl Level) { + // All tests using writeSequence verify that counters are shared between + // parent and child cores. + core = core.With([]Field{makeInt64Field("iter", n)}) + if ce := core.Check(Entry{Level: lvl, Time: time.Now()}, nil); ce != nil { + ce.Write() + } +} + +func TestSampler(t *testing.T) { + for _, lvl := range []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} { + sampler, logs := fakeSampler(DebugLevel, time.Minute, 2, 3) + + // Ensure that counts aren't shared between levels. + probeLevel := DebugLevel + if lvl == DebugLevel { + probeLevel = InfoLevel + } + for i := 0; i < 10; i++ { + writeSequence(sampler, 1, probeLevel) + } + // Clear any output. + logs.TakeAll() + + for i := 1; i < 10; i++ { + writeSequence(sampler, i, lvl) + } + assertSequence(t, logs.TakeAll(), lvl, 1, 2, 5, 8) + } +} + +func TestLevelOfSampler(t *testing.T) { + levels := []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} + for _, lvl := range levels { + lvl := lvl + t.Run(lvl.String(), func(t *testing.T) { + t.Parallel() + + sampler, _ := fakeSampler(lvl, time.Minute, 2, 3) + assert.Equal(t, lvl, LevelOf(sampler), "Sampler level did not match.") + }) + } +} + +func TestSamplerDisabledLevels(t *testing.T) { + sampler, logs := fakeSampler(InfoLevel, time.Minute, 1, 100) + + // Shouldn't be counted, because debug logging isn't enabled. + writeSequence(sampler, 1, DebugLevel) + writeSequence(sampler, 2, InfoLevel) + assertSequence(t, logs.TakeAll(), InfoLevel, 2) +} + +func TestSamplerTicking(t *testing.T) { + // Ensure that we're resetting the sampler's counter every tick. + sampler, logs := fakeSampler(DebugLevel, 10*time.Millisecond, 5, 10) + + // If we log five or fewer messages every tick, none of them should be + // dropped. + for tick := 0; tick < 2; tick++ { + for i := 1; i <= 5; i++ { + writeSequence(sampler, i, InfoLevel) + } + ztest.Sleep(15 * time.Millisecond) + } + assertSequence( + t, + logs.TakeAll(), + InfoLevel, + 1, 2, 3, 4, 5, // first tick + 1, 2, 3, 4, 5, // second tick + ) + + // If we log quickly, we should drop some logs. The first five statements + // each tick should be logged, then every tenth. + for tick := 0; tick < 3; tick++ { + for i := 1; i < 18; i++ { + writeSequence(sampler, i, InfoLevel) + } + ztest.Sleep(10 * time.Millisecond) + } + + assertSequence( + t, + logs.TakeAll(), + InfoLevel, + 1, 2, 3, 4, 5, 15, // first tick + 1, 2, 3, 4, 5, 15, // second tick + 1, 2, 3, 4, 5, 15, // third tick + ) +} + +type countingCore struct { + logs atomic.Uint32 +} + +func (c *countingCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + return ce.AddCore(ent, c) +} + +func (c *countingCore) Write(Entry, []Field) error { + c.logs.Inc() + return nil +} + +func (c *countingCore) With([]Field) Core { return c } +func (*countingCore) Enabled(Level) bool { return true } +func (*countingCore) Sync() error { return nil } + +func TestSamplerConcurrent(t *testing.T) { + const ( + logsPerTick = 10 + numMessages = 5 + numTicks = 25 + numGoroutines = 10 + tick = 10 * time.Millisecond + + // We'll make a total of, + // (numGoroutines * numTicks * logsPerTick * 2) log attempts + // with numMessages unique messages. + numLogAttempts = numGoroutines * logsPerTick * numTicks * 2 + // Of those, we'll accept (logsPerTick * numTicks) entries + // for each unique message. + expectedCount = numMessages * logsPerTick * numTicks + // The rest will be dropped. + expectedDropped = numLogAttempts - expectedCount + ) + + clock := ztest.NewMockClock() + + cc := &countingCore{} + + hook, dropped, sampled := makeSamplerCountingHook() + sampler := NewSamplerWithOptions(cc, tick, logsPerTick, 100000, SamplerHook(hook)) + + stop := make(chan struct{}) + var wg sync.WaitGroup + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(i int, ticker *time.Ticker) { + defer wg.Done() + defer ticker.Stop() + + for { + select { + case <-stop: + return + + case <-ticker.C: + for j := 0; j < logsPerTick*2; j++ { + msg := fmt.Sprintf("msg%v", i%numMessages) + ent := Entry{ + Level: DebugLevel, + Message: msg, + Time: clock.Now(), + } + if ce := sampler.Check(ent, nil); ce != nil { + ce.Write() + } + + // Give a chance for other goroutines to run. + runtime.Gosched() + } + } + } + }(i, clock.NewTicker(tick)) + } + + clock.Add(tick * numTicks) + close(stop) + wg.Wait() + + assert.Equal( + t, + expectedCount, + int(cc.logs.Load()), + "Unexpected number of logs", + ) + assert.Equal(t, + expectedCount, + int(sampled.Load()), + "Unexpected number of logs sampled", + ) + assert.Equal(t, + expectedDropped, + int(dropped.Load()), + "Unexpected number of logs dropped", + ) +} + +func TestSamplerRaces(t *testing.T) { + sampler, _ := fakeSampler(DebugLevel, time.Minute, 1, 1000) + + var wg sync.WaitGroup + start := make(chan struct{}) + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + <-start + for j := 0; j < 100; j++ { + writeSequence(sampler, j, InfoLevel) + } + wg.Done() + }() + } + + close(start) + wg.Wait() +} + +func TestSamplerUnknownLevels(t *testing.T) { + // Prove that out-of-bounds levels don't panic. + unknownLevels := []Level{ + DebugLevel - 1, + FatalLevel + 1, + } + + for _, lvl := range unknownLevels { + t.Run(lvl.String(), func(t *testing.T) { + sampler, logs := fakeSampler(lvl, time.Minute, 2, 3) + for i := 1; i < 10; i++ { + writeSequence(sampler, i, lvl) + } + + // Expect no sampling for unknown levels. + assertSequence(t, logs.TakeAll(), lvl, 1, 2, 3, 4, 5, 6, 7, 8, 9) + }) + } +} + +func TestSamplerWithZeroThereafter(t *testing.T) { + var counter countingCore + + // Logs two messages per second. + sampler := NewSamplerWithOptions(&counter, time.Second, 2, 0) + + now := time.Now() + + for i := 0; i < 1000; i++ { + ent := Entry{ + Level: InfoLevel, + Message: "msg", + Time: now, + } + if ce := sampler.Check(ent, nil); ce != nil { + ce.Write() + } + } + + assert.Equal(t, 2, int(counter.logs.Load()), + "Unexpected number of logs") + + now = now.Add(time.Second) + + for i := 0; i < 1000; i++ { + ent := Entry{ + Level: InfoLevel, + Message: "msg", + Time: now, + } + if ce := sampler.Check(ent, nil); ce != nil { + ce.Write() + } + } + + assert.Equal(t, 4, int(counter.logs.Load()), + "Unexpected number of logs") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee.go new file mode 100644 index 0000000..9bb32f0 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee.go @@ -0,0 +1,96 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type multiCore []Core + +var ( + _ leveledEnabler = multiCore(nil) + _ Core = multiCore(nil) +) + +// NewTee creates a Core that duplicates log entries into two or more +// underlying Cores. +// +// Calling it with a single Core returns the input unchanged, and calling +// it with no input returns a no-op Core. +func NewTee(cores ...Core) Core { + switch len(cores) { + case 0: + return NewNopCore() + case 1: + return cores[0] + default: + return multiCore(cores) + } +} + +func (mc multiCore) With(fields []Field) Core { + clone := make(multiCore, len(mc)) + for i := range mc { + clone[i] = mc[i].With(fields) + } + return clone +} + +func (mc multiCore) Level() Level { + minLvl := _maxLevel // mc is never empty + for i := range mc { + if lvl := LevelOf(mc[i]); lvl < minLvl { + minLvl = lvl + } + } + return minLvl +} + +func (mc multiCore) Enabled(lvl Level) bool { + for i := range mc { + if mc[i].Enabled(lvl) { + return true + } + } + return false +} + +func (mc multiCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + for i := range mc { + ce = mc[i].Check(ent, ce) + } + return ce +} + +func (mc multiCore) Write(ent Entry, fields []Field) error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Write(ent, fields)) + } + return err +} + +func (mc multiCore) Sync() error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Sync()) + } + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee_logger_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee_logger_bench_test.go new file mode 100644 index 0000000..b30a173 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee_logger_bench_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "testing" + + "go.uber.org/zap/internal/ztest" + . "go.uber.org/zap/zapcore" +) + +func withBenchedTee(b *testing.B, f func(Core)) { + fac := NewTee( + NewCore(NewJSONEncoder(testEncoderConfig()), &ztest.Discarder{}, DebugLevel), + NewCore(NewJSONEncoder(testEncoderConfig()), &ztest.Discarder{}, InfoLevel), + ) + b.ResetTimer() + f(fac) +} + +func BenchmarkTeeCheck(b *testing.B) { + cases := []struct { + lvl Level + msg string + }{ + {DebugLevel, "foo"}, + {InfoLevel, "bar"}, + {WarnLevel, "baz"}, + {ErrorLevel, "babble"}, + } + withBenchedTee(b, func(core Core) { + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + tt := cases[i] + entry := Entry{Level: tt.lvl, Message: tt.msg} + if cm := core.Check(entry, nil); cm != nil { + cm.Write(Field{Key: "i", Integer: int64(i), Type: Int64Type}) + } + i = (i + 1) % len(cases) + } + }) + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee_test.go new file mode 100644 index 0000000..b2b9c9d --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/tee_test.go @@ -0,0 +1,191 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "testing" + + "go.uber.org/zap/internal/ztest" + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" +) + +func withTee(f func(core Core, debugLogs, warnLogs *observer.ObservedLogs)) { + debugLogger, debugLogs := observer.New(DebugLevel) + warnLogger, warnLogs := observer.New(WarnLevel) + tee := NewTee(debugLogger, warnLogger) + f(tee, debugLogs, warnLogs) +} + +func TestTeeUnusualInput(t *testing.T) { + // Verify that Tee handles receiving one and no inputs correctly. + t.Run("one input", func(t *testing.T) { + obs, _ := observer.New(DebugLevel) + assert.Equal(t, obs, NewTee(obs), "Expected to return single inputs unchanged.") + }) + t.Run("no input", func(t *testing.T) { + assert.Equal(t, NewNopCore(), NewTee(), "Expected to return NopCore.") + }) +} + +func TestLevelOfTee(t *testing.T) { + debugLogger, _ := observer.New(DebugLevel) + warnLogger, _ := observer.New(WarnLevel) + + tests := []struct { + desc string + give []Core + want Level + }{ + {desc: "empty", want: InvalidLevel}, + { + desc: "debug", + give: []Core{debugLogger}, + want: DebugLevel, + }, + { + desc: "warn", + give: []Core{warnLogger}, + want: WarnLevel, + }, + { + desc: "debug and warn", + give: []Core{warnLogger, debugLogger}, + want: DebugLevel, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + core := NewTee(tt.give...) + assert.Equal(t, tt.want, LevelOf(core), "Level of Tee core did not match.") + }) + } +} + +func TestTeeCheck(t *testing.T) { + withTee(func(tee Core, debugLogs, warnLogs *observer.ObservedLogs) { + debugEntry := Entry{Level: DebugLevel, Message: "log-at-debug"} + infoEntry := Entry{Level: InfoLevel, Message: "log-at-info"} + warnEntry := Entry{Level: WarnLevel, Message: "log-at-warn"} + errorEntry := Entry{Level: ErrorLevel, Message: "log-at-error"} + for _, ent := range []Entry{debugEntry, infoEntry, warnEntry, errorEntry} { + if ce := tee.Check(ent, nil); ce != nil { + ce.Write() + } + } + + assert.Equal(t, []observer.LoggedEntry{ + {Entry: debugEntry, Context: []Field{}}, + {Entry: infoEntry, Context: []Field{}}, + {Entry: warnEntry, Context: []Field{}}, + {Entry: errorEntry, Context: []Field{}}, + }, debugLogs.All()) + + assert.Equal(t, []observer.LoggedEntry{ + {Entry: warnEntry, Context: []Field{}}, + {Entry: errorEntry, Context: []Field{}}, + }, warnLogs.All()) + }) +} + +func TestTeeWrite(t *testing.T) { + // Calling the tee's Write method directly should always log, regardless of + // the configured level. + withTee(func(tee Core, debugLogs, warnLogs *observer.ObservedLogs) { + debugEntry := Entry{Level: DebugLevel, Message: "log-at-debug"} + warnEntry := Entry{Level: WarnLevel, Message: "log-at-warn"} + for _, ent := range []Entry{debugEntry, warnEntry} { + tee.Write(ent, nil) + } + + for _, logs := range []*observer.ObservedLogs{debugLogs, warnLogs} { + assert.Equal(t, []observer.LoggedEntry{ + {Entry: debugEntry, Context: []Field{}}, + {Entry: warnEntry, Context: []Field{}}, + }, logs.All()) + } + }) +} + +func TestTeeWith(t *testing.T) { + withTee(func(tee Core, debugLogs, warnLogs *observer.ObservedLogs) { + f := makeInt64Field("k", 42) + tee = tee.With([]Field{f}) + ent := Entry{Level: WarnLevel, Message: "log-at-warn"} + if ce := tee.Check(ent, nil); ce != nil { + ce.Write() + } + + for _, logs := range []*observer.ObservedLogs{debugLogs, warnLogs} { + assert.Equal(t, []observer.LoggedEntry{ + {Entry: ent, Context: []Field{f}}, + }, logs.All()) + } + }) +} + +func TestTeeEnabled(t *testing.T) { + infoLogger, _ := observer.New(InfoLevel) + warnLogger, _ := observer.New(WarnLevel) + tee := NewTee(infoLogger, warnLogger) + tests := []struct { + lvl Level + enabled bool + }{ + {DebugLevel, false}, + {InfoLevel, true}, + {WarnLevel, true}, + {ErrorLevel, true}, + {DPanicLevel, true}, + {PanicLevel, true}, + {FatalLevel, true}, + } + + for _, tt := range tests { + assert.Equal(t, tt.enabled, tee.Enabled(tt.lvl), "Unexpected Enabled result for level %s.", tt.lvl) + } +} + +func TestTeeSync(t *testing.T) { + infoLogger, _ := observer.New(InfoLevel) + warnLogger, _ := observer.New(WarnLevel) + tee := NewTee(infoLogger, warnLogger) + assert.NoError(t, tee.Sync(), "Unexpected error from Syncing a tee.") + + sink := &ztest.Discarder{} + err := errors.New("failed") + sink.SetError(err) + + noSync := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + tee = NewTee(tee, noSync) + assert.Equal(t, err, tee.Sync(), "Expected an error when part of tee can't Sync.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer.go new file mode 100644 index 0000000..d4a1af3 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer.go @@ -0,0 +1,122 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "io" + "sync" + + "go.uber.org/multierr" +) + +// A WriteSyncer is an io.Writer that can also flush any buffered data. Note +// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer. +type WriteSyncer interface { + io.Writer + Sync() error +} + +// AddSync converts an io.Writer to a WriteSyncer. It attempts to be +// intelligent: if the concrete type of the io.Writer implements WriteSyncer, +// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync. +func AddSync(w io.Writer) WriteSyncer { + switch w := w.(type) { + case WriteSyncer: + return w + default: + return writerWrapper{w} + } +} + +type lockedWriteSyncer struct { + sync.Mutex + ws WriteSyncer +} + +// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In +// particular, *os.Files must be locked before use. +func Lock(ws WriteSyncer) WriteSyncer { + if _, ok := ws.(*lockedWriteSyncer); ok { + // no need to layer on another lock + return ws + } + return &lockedWriteSyncer{ws: ws} +} + +func (s *lockedWriteSyncer) Write(bs []byte) (int, error) { + s.Lock() + n, err := s.ws.Write(bs) + s.Unlock() + return n, err +} + +func (s *lockedWriteSyncer) Sync() error { + s.Lock() + err := s.ws.Sync() + s.Unlock() + return err +} + +type writerWrapper struct { + io.Writer +} + +func (w writerWrapper) Sync() error { + return nil +} + +type multiWriteSyncer []WriteSyncer + +// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes +// and sync calls, much like io.MultiWriter. +func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer { + if len(ws) == 1 { + return ws[0] + } + return multiWriteSyncer(ws) +} + +// See https://golang.org/src/io/multi.go +// When not all underlying syncers write the same number of bytes, +// the smallest number is returned even though Write() is called on +// all of them. +func (ws multiWriteSyncer) Write(p []byte) (int, error) { + var writeErr error + nWritten := 0 + for _, w := range ws { + n, err := w.Write(p) + writeErr = multierr.Append(writeErr, err) + if nWritten == 0 && n != 0 { + nWritten = n + } else if n < nWritten { + nWritten = n + } + } + return nWritten, writeErr +} + +func (ws multiWriteSyncer) Sync() error { + var err error + for _, w := range ws { + err = multierr.Append(err, w.Sync()) + } + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer_bench_test.go new file mode 100644 index 0000000..db6ec45 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer_bench_test.go @@ -0,0 +1,90 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +func BenchmarkMultiWriteSyncer(b *testing.B) { + b.Run("2 discarder", func(b *testing.B) { + w := NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + w.Write([]byte("foobarbazbabble")) + } + }) + }) + b.Run("4 discarder", func(b *testing.B) { + w := NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + w.Write([]byte("foobarbazbabble")) + } + }) + }) + b.Run("4 discarder with buffer", func(b *testing.B) { + w := &BufferedWriteSyncer{ + WS: NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + ), + } + defer w.Stop() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + w.Write([]byte("foobarbazbabble")) + } + }) + }) +} + +func BenchmarkWriteSyncer(b *testing.B) { + b.Run("write file with no buffer", func(b *testing.B) { + file, err := os.CreateTemp(b.TempDir(), "test.log") + require.NoError(b, err) + + w := AddSync(file) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + w.Write([]byte("foobarbazbabble")) + } + }) + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer_test.go new file mode 100644 index 0000000..4748be7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zapcore/write_syncer_test.go @@ -0,0 +1,136 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +type writeSyncSpy struct { + io.Writer + ztest.Syncer +} + +func requireWriteWorks(t testing.TB, ws WriteSyncer) { + n, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + require.Equal(t, 3, n, "Wrote an unexpected number of bytes.") +} + +func TestAddSyncWriteSyncer(t *testing.T) { + buf := &bytes.Buffer{} + concrete := &writeSyncSpy{Writer: buf} + ws := AddSync(concrete) + requireWriteWorks(t, ws) + + require.NoError(t, ws.Sync(), "Unexpected error syncing a WriteSyncer.") + require.True(t, concrete.Called(), "Expected to dispatch to concrete type's Sync method.") + + concrete.SetError(errors.New("fail")) + assert.Error(t, ws.Sync(), "Expected to propagate errors from concrete type's Sync method.") +} + +func TestAddSyncWriter(t *testing.T) { + // If we pass a plain io.Writer, make sure that we still get a WriteSyncer + // with a no-op Sync. + buf := &bytes.Buffer{} + ws := AddSync(buf) + requireWriteWorks(t, ws) + assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.") +} + +func TestNewMultiWriteSyncerWorksForSingleWriter(t *testing.T) { + w := &ztest.Buffer{} + + ws := NewMultiWriteSyncer(w) + assert.Equal(t, w, ws, "Expected NewMultiWriteSyncer to return the same WriteSyncer object for a single argument.") + + ws.Sync() + assert.True(t, w.Called(), "Expected Sync to be called on the created WriteSyncer") +} + +func TestMultiWriteSyncerWritesBoth(t *testing.T) { + first := &bytes.Buffer{} + second := &bytes.Buffer{} + ws := NewMultiWriteSyncer(AddSync(first), AddSync(second)) + + msg := []byte("dumbledore") + n, err := ws.Write(msg) + require.NoError(t, err, "Expected successful buffer write") + assert.Equal(t, len(msg), n) + + assert.Equal(t, msg, first.Bytes()) + assert.Equal(t, msg, second.Bytes()) +} + +func TestMultiWriteSyncerFailsWrite(t *testing.T) { + ws := NewMultiWriteSyncer(AddSync(&ztest.FailWriter{})) + _, err := ws.Write([]byte("test")) + assert.Error(t, err, "Write error should propagate") +} + +func TestMultiWriteSyncerFailsShortWrite(t *testing.T) { + ws := NewMultiWriteSyncer(AddSync(&ztest.ShortWriter{})) + n, err := ws.Write([]byte("test")) + assert.NoError(t, err, "Expected fake-success from short write") + assert.Equal(t, 3, n, "Expected byte count to return from underlying writer") +} + +func TestWritestoAllSyncs_EvenIfFirstErrors(t *testing.T) { + failer := &ztest.FailWriter{} + second := &bytes.Buffer{} + ws := NewMultiWriteSyncer(AddSync(failer), AddSync(second)) + + _, err := ws.Write([]byte("fail")) + assert.Error(t, err, "Expected error from call to a writer that failed") + assert.Equal(t, []byte("fail"), second.Bytes(), "Expected second sink to be written after first error") +} + +func TestMultiWriteSyncerSync_PropagatesErrors(t *testing.T) { + badsink := &ztest.Buffer{} + badsink.SetError(errors.New("sink is full")) + ws := NewMultiWriteSyncer(&ztest.Discarder{}, badsink) + + assert.Error(t, ws.Sync(), "Expected sync error to propagate") +} + +func TestMultiWriteSyncerSync_NoErrorsOnDiscard(t *testing.T) { + ws := NewMultiWriteSyncer(&ztest.Discarder{}) + assert.NoError(t, ws.Sync(), "Expected error-free sync to /dev/null") +} + +func TestMultiWriteSyncerSync_AllCalled(t *testing.T) { + failed, second := &ztest.Buffer{}, &ztest.Buffer{} + + failed.SetError(errors.New("disposal broken")) + ws := NewMultiWriteSyncer(failed, second) + + assert.Error(t, ws.Sync(), "Expected first sink to fail") + assert.True(t, failed.Called(), "Expected first sink to have Sync method called.") + assert.True(t, second.Called(), "Expected call to Sync even with first failure.") +} |