adding in logging
This commit is contained in:
174
vendor/go.uber.org/zap/zaptest/observer/observer.go
generated
vendored
Normal file
174
vendor/go.uber.org/zap/zaptest/observer/observer.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
// 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 observer provides a zapcore.Core that keeps an in-memory,
|
||||
// encoding-agnostic repesentation 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/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
|
||||
}
|
||||
|
||||
// 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))
|
||||
for i := range o.logs {
|
||||
ret[i] = o.logs[i]
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
func (o *ObservedLogs) filter(match func(LoggedEntry) bool) *ObservedLogs {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
|
||||
var filtered []LoggedEntry
|
||||
for _, entry := range o.logs {
|
||||
if match(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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
215
vendor/go.uber.org/zap/zaptest/observer/observer_test.go
generated
vendored
Normal file
215
vendor/go.uber.org/zap/zaptest/observer/observer_test.go
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
// 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 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)
|
||||
|
||||
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 numbed 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"})},
|
||||
},
|
||||
}
|
||||
|
||||
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],
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tt.filtered.AllUntimed()
|
||||
assert.Equal(t, tt.want, got, tt.msg)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user