github.com/Laisky/zap@v1.27.0/global_test.go (about) 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 21 package zap 22 23 import ( 24 "log" 25 "sync" 26 "sync/atomic" 27 "testing" 28 "time" 29 30 "github.com/Laisky/zap/internal/exit" 31 "github.com/Laisky/zap/internal/ztest" 32 33 "github.com/Laisky/zap/zapcore" 34 "github.com/Laisky/zap/zaptest/observer" 35 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 ) 39 40 func TestReplaceGlobals(t *testing.T) { 41 initialL := *L() 42 initialS := *S() 43 44 withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { 45 L().Info("no-op") 46 S().Info("no-op") 47 assert.Equal(t, 0, logs.Len(), "Expected initial logs to go to default no-op global.") 48 49 defer ReplaceGlobals(l)() 50 51 L().Info("captured") 52 S().Info("captured") 53 expected := observer.LoggedEntry{ 54 Entry: zapcore.Entry{Message: "captured"}, 55 Context: []Field{}, 56 } 57 assert.Equal( 58 t, 59 []observer.LoggedEntry{expected, expected}, 60 logs.AllUntimed(), 61 "Unexpected global log output.", 62 ) 63 }) 64 65 assert.Equal(t, initialL, *L(), "Expected func returned from ReplaceGlobals to restore initial L.") 66 assert.Equal(t, initialS, *S(), "Expected func returned from ReplaceGlobals to restore initial S.") 67 } 68 69 func TestGlobalsConcurrentUse(t *testing.T) { 70 var ( 71 stop atomic.Bool 72 wg sync.WaitGroup 73 ) 74 75 for i := 0; i < 100; i++ { 76 wg.Add(2) 77 go func() { 78 for !stop.Load() { 79 ReplaceGlobals(NewNop()) 80 } 81 wg.Done() 82 }() 83 go func() { 84 for !stop.Load() { 85 L().With(Int("foo", 42)).Named("main").WithOptions(Development()).Info("") 86 S().Info("") 87 } 88 wg.Done() 89 }() 90 } 91 92 ztest.Sleep(100 * time.Millisecond) 93 // CAS loop to toggle the current value. 94 for old := stop.Load(); !stop.CompareAndSwap(old, !old); { 95 old = stop.Load() 96 } 97 wg.Wait() 98 } 99 100 func TestNewStdLog(t *testing.T) { 101 withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { 102 std := NewStdLog(l) 103 std.Print("redirected") 104 checkStdLogMessage(t, "redirected", logs) 105 }) 106 } 107 108 func TestNewStdLogAt(t *testing.T) { 109 // include DPanicLevel here, but do not include Development in options 110 levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} 111 for _, level := range levels { 112 withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { 113 std, err := NewStdLogAt(l, level) 114 require.NoError(t, err, "Unexpected error.") 115 std.Print("redirected") 116 checkStdLogMessage(t, "redirected", logs) 117 }) 118 } 119 } 120 121 func TestNewStdLogAtPanics(t *testing.T) { 122 // include DPanicLevel here and enable Development in options 123 levels := []zapcore.Level{DPanicLevel, PanicLevel} 124 for _, level := range levels { 125 withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) { 126 std, err := NewStdLogAt(l, level) 127 require.NoError(t, err, "Unexpected error") 128 assert.Panics(t, func() { std.Print("redirected") }, "Expected log to panic.") 129 checkStdLogMessage(t, "redirected", logs) 130 }) 131 } 132 } 133 134 func TestNewStdLogAtFatal(t *testing.T) { 135 withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { 136 stub := exit.WithStub(func() { 137 std, err := NewStdLogAt(l, FatalLevel) 138 require.NoError(t, err, "Unexpected error.") 139 std.Print("redirected") 140 checkStdLogMessage(t, "redirected", logs) 141 }) 142 assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.") 143 stub.Unstub() 144 }) 145 } 146 147 func TestNewStdLogAtInvalid(t *testing.T) { 148 _, err := NewStdLogAt(NewNop(), zapcore.Level(99)) 149 assert.ErrorContains(t, err, "99", "Expected level code in error message") 150 } 151 152 func TestRedirectStdLog(t *testing.T) { 153 initialFlags := log.Flags() 154 initialPrefix := log.Prefix() 155 156 withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { 157 defer RedirectStdLog(l)() 158 log.Print("redirected") 159 160 assert.Equal(t, []observer.LoggedEntry{{ 161 Entry: zapcore.Entry{Message: "redirected"}, 162 Context: []Field{}, 163 }}, logs.AllUntimed(), "Unexpected global log output.") 164 }) 165 166 assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") 167 assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") 168 } 169 170 func TestRedirectStdLogCaller(t *testing.T) { 171 withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { 172 defer RedirectStdLog(l)() 173 log.Print("redirected") 174 entries := logs.All() 175 require.Len(t, entries, 1, "Unexpected number of logs.") 176 assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.") 177 }) 178 } 179 180 func TestRedirectStdLogAt(t *testing.T) { 181 initialFlags := log.Flags() 182 initialPrefix := log.Prefix() 183 184 // include DPanicLevel here, but do not include Development in options 185 levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} 186 for _, level := range levels { 187 withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { 188 restore, err := RedirectStdLogAt(l, level) 189 require.NoError(t, err, "Unexpected error.") 190 defer restore() 191 log.Print("redirected") 192 193 assert.Equal(t, []observer.LoggedEntry{{ 194 Entry: zapcore.Entry{Level: level, Message: "redirected"}, 195 Context: []Field{}, 196 }}, logs.AllUntimed(), "Unexpected global log output.") 197 }) 198 } 199 200 assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") 201 assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") 202 } 203 204 func TestRedirectStdLogAtCaller(t *testing.T) { 205 // include DPanicLevel here, but do not include Development in options 206 levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} 207 for _, level := range levels { 208 withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { 209 restore, err := RedirectStdLogAt(l, level) 210 require.NoError(t, err, "Unexpected error.") 211 defer restore() 212 log.Print("redirected") 213 entries := logs.All() 214 require.Len(t, entries, 1, "Unexpected number of logs.") 215 assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.") 216 }) 217 } 218 } 219 220 func TestRedirectStdLogAtPanics(t *testing.T) { 221 initialFlags := log.Flags() 222 initialPrefix := log.Prefix() 223 224 // include DPanicLevel here and enable Development in options 225 levels := []zapcore.Level{DPanicLevel, PanicLevel} 226 for _, level := range levels { 227 withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) { 228 restore, err := RedirectStdLogAt(l, level) 229 require.NoError(t, err, "Unexpected error.") 230 defer restore() 231 assert.Panics(t, func() { log.Print("redirected") }, "Expected log to panic.") 232 checkStdLogMessage(t, "redirected", logs) 233 }) 234 } 235 236 assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") 237 assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") 238 } 239 240 func TestRedirectStdLogAtFatal(t *testing.T) { 241 initialFlags := log.Flags() 242 initialPrefix := log.Prefix() 243 244 withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { 245 stub := exit.WithStub(func() { 246 restore, err := RedirectStdLogAt(l, FatalLevel) 247 require.NoError(t, err, "Unexpected error.") 248 defer restore() 249 log.Print("redirected") 250 checkStdLogMessage(t, "redirected", logs) 251 }) 252 assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.") 253 stub.Unstub() 254 }) 255 256 assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") 257 assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") 258 } 259 260 func TestRedirectStdLogAtInvalid(t *testing.T) { 261 restore, err := RedirectStdLogAt(NewNop(), zapcore.Level(99)) 262 defer func() { 263 if restore != nil { 264 restore() 265 } 266 }() 267 assert.ErrorContains(t, err, "99", "Expected level code in error message") 268 } 269 270 func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) { 271 require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged") 272 entry := logs.AllUntimed()[0] 273 assert.Equal(t, []Field{}, entry.Context, "Unexpected entry context.") 274 assert.Equal(t, "redirected", entry.Message, "Unexpected entry message.") 275 assert.Regexp( 276 t, 277 `/global_test.go:\d+$`, 278 entry.Caller.String(), 279 "Unexpected caller annotation.", 280 ) 281 }