1// Copyright (c) 2016 Uber Technologies, Inc. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a copy 4// of this software and associated documentation files (the "Software"), to deal 5// in the Software without restriction, including without limitation the rights 6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7// copies of the Software, and to permit persons to whom the Software is 8// furnished to do so, subject to the following conditions: 9// 10// The above copyright notice and this permission notice shall be included in 11// all copies or substantial portions of the Software. 12// 13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19// THE SOFTWARE. 20 21package observer_test 22 23import ( 24 "testing" 25 "time" 26 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 30 "go.uber.org/zap" 31 "go.uber.org/zap/zapcore" 32 . "go.uber.org/zap/zaptest/observer" 33) 34 35func assertEmpty(t testing.TB, logs *ObservedLogs) { 36 assert.Equal(t, 0, logs.Len(), "Expected empty ObservedLogs to have zero length.") 37 assert.Equal(t, []LoggedEntry{}, logs.All(), "Unexpected LoggedEntries in empty ObservedLogs.") 38} 39 40func TestObserver(t *testing.T) { 41 observer, logs := New(zap.InfoLevel) 42 assertEmpty(t, logs) 43 44 assert.NoError(t, observer.Sync(), "Unexpected failure in no-op Sync") 45 46 obs := zap.New(observer).With(zap.Int("i", 1)) 47 obs.Info("foo") 48 obs.Debug("bar") 49 want := []LoggedEntry{{ 50 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "foo"}, 51 Context: []zapcore.Field{zap.Int("i", 1)}, 52 }} 53 54 assert.Equal(t, 1, logs.Len(), "Unexpected observed logs Len.") 55 assert.Equal(t, want, logs.AllUntimed(), "Unexpected contents from AllUntimed.") 56 57 all := logs.All() 58 require.Equal(t, 1, len(all), "Unexpected numbed of LoggedEntries returned from All.") 59 assert.NotEqual(t, time.Time{}, all[0].Time, "Expected non-zero time on LoggedEntry.") 60 61 // copy & zero time for stable assertions 62 untimed := append([]LoggedEntry{}, all...) 63 untimed[0].Time = time.Time{} 64 assert.Equal(t, want, untimed, "Unexpected LoggedEntries from All.") 65 66 assert.Equal(t, all, logs.TakeAll(), "Expected All and TakeAll to return identical results.") 67 assertEmpty(t, logs) 68} 69 70func TestObserverWith(t *testing.T) { 71 sf1, logs := New(zap.InfoLevel) 72 73 // need to pad out enough initial fields so that the underlying slice cap() 74 // gets ahead of its len() so that the sf3/4 With append's could choose 75 // not to copy (if the implementation doesn't force them) 76 sf1 = sf1.With([]zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}) 77 78 sf2 := sf1.With([]zapcore.Field{zap.Int("c", 3)}) 79 sf3 := sf2.With([]zapcore.Field{zap.Int("d", 4)}) 80 sf4 := sf2.With([]zapcore.Field{zap.Int("e", 5)}) 81 ent := zapcore.Entry{Level: zap.InfoLevel, Message: "hello"} 82 83 for i, core := range []zapcore.Core{sf2, sf3, sf4} { 84 if ce := core.Check(ent, nil); ce != nil { 85 ce.Write(zap.Int("i", i)) 86 } 87 } 88 89 assert.Equal(t, []LoggedEntry{ 90 { 91 Entry: ent, 92 Context: []zapcore.Field{ 93 zap.Int("a", 1), 94 zap.Int("b", 2), 95 zap.Int("c", 3), 96 zap.Int("i", 0), 97 }, 98 }, 99 { 100 Entry: ent, 101 Context: []zapcore.Field{ 102 zap.Int("a", 1), 103 zap.Int("b", 2), 104 zap.Int("c", 3), 105 zap.Int("d", 4), 106 zap.Int("i", 1), 107 }, 108 }, 109 { 110 Entry: ent, 111 Context: []zapcore.Field{ 112 zap.Int("a", 1), 113 zap.Int("b", 2), 114 zap.Int("c", 3), 115 zap.Int("e", 5), 116 zap.Int("i", 2), 117 }, 118 }, 119 }, logs.All(), "expected no field sharing between With siblings") 120} 121 122func TestFilters(t *testing.T) { 123 logs := []LoggedEntry{ 124 { 125 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, 126 Context: []zapcore.Field{zap.String("fStr", "1"), zap.Int("a", 1)}, 127 }, 128 { 129 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, 130 Context: []zapcore.Field{zap.String("fStr", "2"), zap.Int("b", 2)}, 131 }, 132 { 133 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log b"}, 134 Context: []zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}, 135 }, 136 { 137 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log c"}, 138 Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns"), zap.Int("a", 2)}, 139 }, 140 { 141 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "msg 1"}, 142 Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns")}, 143 }, 144 { 145 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any map"}, 146 Context: []zapcore.Field{zap.Any("map", map[string]string{"a": "b"})}, 147 }, 148 { 149 Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any slice"}, 150 Context: []zapcore.Field{zap.Any("slice", []string{"a"})}, 151 }, 152 } 153 154 logger, sink := New(zap.InfoLevel) 155 for _, log := range logs { 156 logger.Write(log.Entry, log.Context) 157 } 158 159 tests := []struct { 160 msg string 161 filtered *ObservedLogs 162 want []LoggedEntry 163 }{ 164 { 165 msg: "filter by message", 166 filtered: sink.FilterMessage("log a"), 167 want: logs[0:2], 168 }, 169 { 170 msg: "filter by field", 171 filtered: sink.FilterField(zap.String("fStr", "1")), 172 want: logs[0:1], 173 }, 174 { 175 msg: "filter by message and field", 176 filtered: sink.FilterMessage("log a").FilterField(zap.Int("b", 2)), 177 want: logs[1:2], 178 }, 179 { 180 msg: "filter by field with duplicate fields", 181 filtered: sink.FilterField(zap.Int("a", 2)), 182 want: logs[3:4], 183 }, 184 { 185 msg: "filter doesn't match any messages", 186 filtered: sink.FilterMessage("no match"), 187 want: []LoggedEntry{}, 188 }, 189 { 190 msg: "filter by snippet", 191 filtered: sink.FilterMessageSnippet("log"), 192 want: logs[0:4], 193 }, 194 { 195 msg: "filter by snippet and field", 196 filtered: sink.FilterMessageSnippet("a").FilterField(zap.Int("b", 2)), 197 want: logs[1:2], 198 }, 199 { 200 msg: "filter for map", 201 filtered: sink.FilterField(zap.Any("map", map[string]string{"a": "b"})), 202 want: logs[5:6], 203 }, 204 { 205 msg: "filter for slice", 206 filtered: sink.FilterField(zap.Any("slice", []string{"a"})), 207 want: logs[6:7], 208 }, 209 } 210 211 for _, tt := range tests { 212 got := tt.filtered.AllUntimed() 213 assert.Equal(t, tt.want, got, tt.msg) 214 } 215} 216