diff options
Diffstat (limited to 'dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer')
4 files changed, 581 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/logged_entry.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/logged_entry.go new file mode 100644 index 0000000..a4ea7ec --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/logged_entry.go @@ -0,0 +1,39 @@ +// 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 observer + +import "go.uber.org/zap/zapcore" + +// An LoggedEntry is an encoding-agnostic representation of a log message. +// Field availability is context dependant. +type LoggedEntry struct { + zapcore.Entry + Context []zapcore.Field +} + +// ContextMap returns a map for all fields in Context. +func (e LoggedEntry) ContextMap() map[string]interface{} { + encoder := zapcore.NewMapObjectEncoder() + for _, f := range e.Context { + f.AddTo(encoder) + } + return encoder.Fields +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/logged_entry_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/logged_entry_test.go new file mode 100644 index 0000000..50f6123 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/logged_entry_test.go @@ -0,0 +1,88 @@ +// 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 observer + +import ( + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestLoggedEntryContextMap(t *testing.T) { + tests := []struct { + msg string + fields []zapcore.Field + want map[string]interface{} + }{ + { + msg: "no fields", + fields: nil, + want: map[string]interface{}{}, + }, + { + msg: "simple", + fields: []zapcore.Field{ + zap.String("k1", "v"), + zap.Int64("k2", 10), + }, + want: map[string]interface{}{ + "k1": "v", + "k2": int64(10), + }, + }, + { + msg: "overwrite", + fields: []zapcore.Field{ + zap.String("k1", "v1"), + zap.String("k1", "v2"), + }, + want: map[string]interface{}{ + "k1": "v2", + }, + }, + { + msg: "nested", + fields: []zapcore.Field{ + zap.String("k1", "v1"), + zap.Namespace("nested"), + zap.String("k2", "v2"), + }, + want: map[string]interface{}{ + "k1": "v1", + "nested": map[string]interface{}{ + "k2": "v2", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + entry := LoggedEntry{ + Context: tt.fields, + } + assert.Equal(t, tt.want, entry.ContextMap()) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/observer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/observer.go new file mode 100644 index 0000000..f77f130 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/observer.go @@ -0,0 +1,196 @@ +// 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 observer provides a zapcore.Core that keeps an in-memory, +// encoding-agnostic representation of log entries. It's useful for +// applications that want to unit test their log output without tying their +// tests to a particular output encoding. +package observer // import "go.uber.org/zap/zaptest/observer" + +import ( + "strings" + "sync" + "time" + + "go.uber.org/zap/internal" + "go.uber.org/zap/zapcore" +) + +// ObservedLogs is a concurrency-safe, ordered collection of observed logs. +type ObservedLogs struct { + mu sync.RWMutex + logs []LoggedEntry +} + +// Len returns the number of items in the collection. +func (o *ObservedLogs) Len() int { + o.mu.RLock() + n := len(o.logs) + o.mu.RUnlock() + return n +} + +// All returns a copy of all the observed logs. +func (o *ObservedLogs) All() []LoggedEntry { + o.mu.RLock() + ret := make([]LoggedEntry, len(o.logs)) + copy(ret, o.logs) + o.mu.RUnlock() + return ret +} + +// TakeAll returns a copy of all the observed logs, and truncates the observed +// slice. +func (o *ObservedLogs) TakeAll() []LoggedEntry { + o.mu.Lock() + ret := o.logs + o.logs = nil + o.mu.Unlock() + return ret +} + +// AllUntimed returns a copy of all the observed logs, but overwrites the +// observed timestamps with time.Time's zero value. This is useful when making +// assertions in tests. +func (o *ObservedLogs) AllUntimed() []LoggedEntry { + ret := o.All() + for i := range ret { + ret[i].Time = time.Time{} + } + return ret +} + +// FilterLevelExact filters entries to those logged at exactly the given level. +func (o *ObservedLogs) FilterLevelExact(level zapcore.Level) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return e.Level == level + }) +} + +// FilterMessage filters entries to those that have the specified message. +func (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return e.Message == msg + }) +} + +// FilterMessageSnippet filters entries to those that have a message containing the specified snippet. +func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return strings.Contains(e.Message, snippet) + }) +} + +// FilterField filters entries to those that have the specified field. +func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + for _, ctxField := range e.Context { + if ctxField.Equals(field) { + return true + } + } + return false + }) +} + +// FilterFieldKey filters entries to those that have the specified key. +func (o *ObservedLogs) FilterFieldKey(key string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + for _, ctxField := range e.Context { + if ctxField.Key == key { + return true + } + } + return false + }) +} + +// Filter returns a copy of this ObservedLogs containing only those entries +// for which the provided function returns true. +func (o *ObservedLogs) Filter(keep func(LoggedEntry) bool) *ObservedLogs { + o.mu.RLock() + defer o.mu.RUnlock() + + var filtered []LoggedEntry + for _, entry := range o.logs { + if keep(entry) { + filtered = append(filtered, entry) + } + } + return &ObservedLogs{logs: filtered} +} + +func (o *ObservedLogs) add(log LoggedEntry) { + o.mu.Lock() + o.logs = append(o.logs, log) + o.mu.Unlock() +} + +// New creates a new Core that buffers logs in memory (without any encoding). +// It's particularly useful in tests. +func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) { + ol := &ObservedLogs{} + return &contextObserver{ + LevelEnabler: enab, + logs: ol, + }, ol +} + +type contextObserver struct { + zapcore.LevelEnabler + logs *ObservedLogs + context []zapcore.Field +} + +var ( + _ zapcore.Core = (*contextObserver)(nil) + _ internal.LeveledEnabler = (*contextObserver)(nil) +) + +func (co *contextObserver) Level() zapcore.Level { + return zapcore.LevelOf(co.LevelEnabler) +} + +func (co *contextObserver) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if co.Enabled(ent.Level) { + return ce.AddCore(ent, co) + } + return ce +} + +func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core { + return &contextObserver{ + LevelEnabler: co.LevelEnabler, + logs: co.logs, + context: append(co.context[:len(co.context):len(co.context)], fields...), + } +} + +func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error { + all := make([]zapcore.Field, 0, len(fields)+len(co.context)) + all = append(all, co.context...) + all = append(all, fields...) + co.logs.add(LoggedEntry{ent, all}) + return nil +} + +func (co *contextObserver) Sync() error { + return nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/observer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/observer_test.go new file mode 100644 index 0000000..2a901b1 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.23.0/zaptest/observer/observer_test.go @@ -0,0 +1,258 @@ +// 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 observer_test + +import ( + "testing" + "time" + + "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 assertEmpty(t testing.TB, logs *ObservedLogs) { + assert.Equal(t, 0, logs.Len(), "Expected empty ObservedLogs to have zero length.") + assert.Equal(t, []LoggedEntry{}, logs.All(), "Unexpected LoggedEntries in empty ObservedLogs.") +} + +func TestObserver(t *testing.T) { + observer, logs := New(zap.InfoLevel) + assertEmpty(t, logs) + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, zap.InfoLevel, zapcore.LevelOf(observer), "Observer reported the wrong log level.") + }) + + assert.NoError(t, observer.Sync(), "Unexpected failure in no-op Sync") + + obs := zap.New(observer).With(zap.Int("i", 1)) + obs.Info("foo") + obs.Debug("bar") + want := []LoggedEntry{{ + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "foo"}, + Context: []zapcore.Field{zap.Int("i", 1)}, + }} + + assert.Equal(t, 1, logs.Len(), "Unexpected observed logs Len.") + assert.Equal(t, want, logs.AllUntimed(), "Unexpected contents from AllUntimed.") + + all := logs.All() + require.Equal(t, 1, len(all), "Unexpected number of LoggedEntries returned from All.") + assert.NotEqual(t, time.Time{}, all[0].Time, "Expected non-zero time on LoggedEntry.") + + // copy & zero time for stable assertions + untimed := append([]LoggedEntry{}, all...) + untimed[0].Time = time.Time{} + assert.Equal(t, want, untimed, "Unexpected LoggedEntries from All.") + + assert.Equal(t, all, logs.TakeAll(), "Expected All and TakeAll to return identical results.") + assertEmpty(t, logs) +} + +func TestObserverWith(t *testing.T) { + sf1, logs := New(zap.InfoLevel) + + // need to pad out enough initial fields so that the underlying slice cap() + // gets ahead of its len() so that the sf3/4 With append's could choose + // not to copy (if the implementation doesn't force them) + sf1 = sf1.With([]zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}) + + sf2 := sf1.With([]zapcore.Field{zap.Int("c", 3)}) + sf3 := sf2.With([]zapcore.Field{zap.Int("d", 4)}) + sf4 := sf2.With([]zapcore.Field{zap.Int("e", 5)}) + ent := zapcore.Entry{Level: zap.InfoLevel, Message: "hello"} + + for i, core := range []zapcore.Core{sf2, sf3, sf4} { + if ce := core.Check(ent, nil); ce != nil { + ce.Write(zap.Int("i", i)) + } + } + + assert.Equal(t, []LoggedEntry{ + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("i", 0), + }, + }, + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("d", 4), + zap.Int("i", 1), + }, + }, + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("e", 5), + zap.Int("i", 2), + }, + }, + }, logs.All(), "expected no field sharing between With siblings") +} + +func TestFilters(t *testing.T) { + logs := []LoggedEntry{ + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, + Context: []zapcore.Field{zap.String("fStr", "1"), zap.Int("a", 1)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, + Context: []zapcore.Field{zap.String("fStr", "2"), zap.Int("b", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log b"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log c"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns"), zap.Int("a", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "msg 1"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns")}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any map"}, + Context: []zapcore.Field{zap.Any("map", map[string]string{"a": "b"})}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any slice"}, + Context: []zapcore.Field{zap.Any("slice", []string{"a"})}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "msg 2"}, + Context: []zapcore.Field{zap.Int("b", 2), zap.Namespace("filterMe")}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any slice"}, + Context: []zapcore.Field{zap.Any("filterMe", []string{"b"})}, + }, + { + Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "danger will robinson"}, + Context: []zapcore.Field{zap.Int("b", 42)}, + }, + { + Entry: zapcore.Entry{Level: zap.ErrorLevel, Message: "warp core breach"}, + Context: []zapcore.Field{zap.Int("b", 42)}, + }, + } + + logger, sink := New(zap.InfoLevel) + for _, log := range logs { + logger.Write(log.Entry, log.Context) + } + + tests := []struct { + msg string + filtered *ObservedLogs + want []LoggedEntry + }{ + { + msg: "filter by message", + filtered: sink.FilterMessage("log a"), + want: logs[0:2], + }, + { + msg: "filter by field", + filtered: sink.FilterField(zap.String("fStr", "1")), + want: logs[0:1], + }, + { + msg: "filter by message and field", + filtered: sink.FilterMessage("log a").FilterField(zap.Int("b", 2)), + want: logs[1:2], + }, + { + msg: "filter by field with duplicate fields", + filtered: sink.FilterField(zap.Int("a", 2)), + want: logs[3:4], + }, + { + msg: "filter doesn't match any messages", + filtered: sink.FilterMessage("no match"), + want: []LoggedEntry{}, + }, + { + msg: "filter by snippet", + filtered: sink.FilterMessageSnippet("log"), + want: logs[0:4], + }, + { + msg: "filter by snippet and field", + filtered: sink.FilterMessageSnippet("a").FilterField(zap.Int("b", 2)), + want: logs[1:2], + }, + { + msg: "filter for map", + filtered: sink.FilterField(zap.Any("map", map[string]string{"a": "b"})), + want: logs[5:6], + }, + { + msg: "filter for slice", + filtered: sink.FilterField(zap.Any("slice", []string{"a"})), + want: logs[6:7], + }, + { + msg: "filter field key", + filtered: sink.FilterFieldKey("filterMe"), + want: logs[7:9], + }, + { + msg: "filter by arbitrary function", + filtered: sink.Filter(func(e LoggedEntry) bool { + return len(e.Context) > 1 + }), + want: func() []LoggedEntry { + // Do not modify logs slice. + w := make([]LoggedEntry, 0, len(logs)) + w = append(w, logs[0:5]...) + w = append(w, logs[7]) + return w + }(), + }, + { + msg: "filter level", + filtered: sink.FilterLevelExact(zap.WarnLevel), + want: logs[9:10], + }, + } + + for _, tt := range tests { + got := tt.filtered.AllUntimed() + assert.Equal(t, tt.want, got, tt.msg) + } +} |