github.com/blend/go-sdk@v1.20240719.1/logger/logger_test.go (about) 1 /* 2 3 Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package logger 9 10 import ( 11 "bytes" 12 "context" 13 "fmt" 14 "testing" 15 "time" 16 17 "github.com/blend/go-sdk/assert" 18 "github.com/blend/go-sdk/uuid" 19 ) 20 21 func TestNew(t *testing.T) { 22 assert := assert.New(t) 23 24 log, err := New() 25 assert.Nil(err) 26 assert.NotNil(log.Flags) 27 assert.NotNil(log.Writable) 28 assert.NotNil(log.Scope) 29 assert.NotNil(log.Formatter) 30 assert.NotNil(log.Output) 31 assert.True(log.RecoverPanics) 32 33 for _, defaultFlag := range DefaultFlags { 34 assert.True(log.Flags.IsEnabled(defaultFlag)) 35 } 36 assert.True(log.Writable.All()) 37 38 log, err = New(OptAll(), OptFormatter(NewJSONOutputFormatter())) 39 assert.Nil(err) 40 assert.True(log.Flags.IsEnabled(uuid.V4().String())) 41 typed, ok := log.Formatter.(*JSONOutputFormatter) 42 assert.True(ok) 43 assert.NotNil(typed) 44 } 45 46 func TestLoggerFlagsWritten(t *testing.T) { 47 its := assert.New(t) 48 49 buf := new(bytes.Buffer) 50 log := Memory(buf) 51 defer log.Close() 52 53 log.Writable.Disable(Info) 54 55 eventTriggered := make(chan struct{}) 56 log.Listen(Info, DefaultListenerName, func(_ context.Context, e Event) { 57 close(eventTriggered) 58 }) 59 60 log.Dispatch(context.TODO(), NewMessageEvent(Info, "test")) 61 <-eventTriggered 62 its.Empty(buf.String()) 63 64 log.Dispatch(context.TODO(), NewMessageEvent(Error, "this is just a test")) 65 its.Equal("[error] this is just a test\n", buf.String()) 66 } 67 68 func TestLoggerScopes(t *testing.T) { 69 its := assert.New(t) 70 71 buf := new(bytes.Buffer) 72 log := Memory(buf) 73 defer log.Close() 74 75 log.Scopes.Disable("disabled/*") 76 77 eventTriggered := make(chan struct{}) 78 log.Listen(Error, DefaultListenerName, func(_ context.Context, e Event) { 79 close(eventTriggered) 80 }) 81 82 log.WithPath("disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Info, "test")) 83 // should not trigger with scope disabled 84 its.Empty(buf.String()) 85 86 log.WithPath("not-disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Error, "this is just a test")) 87 <-eventTriggered 88 its.Equal("[not-disabled > foo] [error] this is just a test\n", buf.String()) 89 } 90 91 func TestLoggerWritableScopes(t *testing.T) { 92 its := assert.New(t) 93 94 buf := new(bytes.Buffer) 95 log := Memory(buf) 96 defer log.Close() 97 98 log.WritableScopes.Disable("disabled/*") 99 100 eventTriggered := make(chan struct{}) 101 log.Listen(Error, DefaultListenerName, func(_ context.Context, e Event) { 102 close(eventTriggered) 103 }) 104 105 log.WithPath("disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Error, "test")) 106 <-eventTriggered 107 its.Empty(buf.String()) 108 109 eventTriggered = make(chan struct{}) 110 log.WithPath("not-disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Error, "this is just a test")) 111 <-eventTriggered 112 its.Equal("[not-disabled > foo] [error] this is just a test\n", buf.String()) 113 } 114 115 func TestLoggerE2ESubContext(t *testing.T) { 116 assert := assert.New(t) 117 118 output := new(bytes.Buffer) 119 log, err := New( 120 OptOutput(output), 121 OptText(OptTextHideTimestamp(), OptTextNoColor()), 122 ) 123 assert.Nil(err) 124 defer log.Close() 125 126 scID := uuid.V4().String() 127 sc := log.WithPath(scID) 128 129 sc.Infof("this is infof") 130 sc.Errorf("this is errorf") 131 sc.Fatalf("this is fatalf") 132 133 sc.Trigger(NewMessageEvent(Info, "this is a triggered message")) 134 135 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 136 defer cancel() 137 log.DrainContext(ctx) 138 139 assert.Contains(output.String(), fmt.Sprintf("[%s] [info] this is infof", scID)) 140 assert.Contains(output.String(), fmt.Sprintf("[%s] [error] this is errorf", scID)) 141 assert.Contains(output.String(), fmt.Sprintf("[%s] [fatal] this is fatalf", scID)) 142 assert.Contains(output.String(), fmt.Sprintf("[%s] [info] this is a triggered message", scID)) 143 } 144 145 func TestLoggerE2ESubContextFields(t *testing.T) { 146 assert := assert.New(t) 147 148 output := new(bytes.Buffer) 149 log, err := New( 150 OptOutput(output), 151 OptText(OptTextHideTimestamp(), OptTextNoColor()), 152 ) 153 assert.Nil(err) 154 155 fieldKey := uuid.V4().String() 156 fieldValue := uuid.V4().String() 157 sc := log.WithLabels(Labels{fieldKey: fieldValue}) 158 159 sc.Infof("this is infof") 160 sc.Errorf("this is errorf") 161 sc.Fatalf("this is fatalf") 162 163 sc.Trigger(NewMessageEvent(Info, "this is a triggered message")) 164 log.DrainContext(context.Background()) 165 166 assert.Contains(output.String(), fmt.Sprintf("[info] this is infof\t%s=%s", fieldKey, fieldValue)) 167 assert.Contains(output.String(), fmt.Sprintf("[error] this is errorf\t%s=%s", fieldKey, fieldValue)) 168 assert.Contains(output.String(), fmt.Sprintf("[fatal] this is fatalf\t%s=%s", fieldKey, fieldValue)) 169 assert.Contains(output.String(), fmt.Sprintf("[info] this is a triggered message\t%s=%s", fieldKey, fieldValue)) 170 } 171 172 func TestLoggerAnnotations(t *testing.T) { 173 assert := assert.New(t) 174 175 output := new(bytes.Buffer) 176 log, err := New( 177 OptOutput(output), 178 OptText(OptTextHideTimestamp(), OptTextNoColor()), 179 ) 180 assert.Nil(err) 181 182 fieldKey := uuid.V4().String() 183 fieldValue := uuid.V4().String() 184 sc := log.WithAnnotations(Annotations{fieldKey: fieldValue}) 185 186 sc.Infof("this is infof") 187 sc.Errorf("this is errorf") 188 sc.Fatalf("this is fatalf") 189 190 sc.Trigger(NewMessageEvent(Info, "this is a triggered message")) 191 log.DrainContext(context.Background()) 192 193 assert.Contains(output.String(), fmt.Sprintf("[info] this is infof\t%s=%s", fieldKey, fieldValue)) 194 assert.Contains(output.String(), fmt.Sprintf("[error] this is errorf\t%s=%s", fieldKey, fieldValue)) 195 assert.Contains(output.String(), fmt.Sprintf("[fatal] this is fatalf\t%s=%s", fieldKey, fieldValue)) 196 assert.Contains(output.String(), fmt.Sprintf("[info] this is a triggered message\t%s=%s", fieldKey, fieldValue)) 197 } 198 199 func TestLoggerSkipTrigger(t *testing.T) { 200 assert := assert.New(t) 201 202 output := new(bytes.Buffer) 203 log, err := New( 204 OptOutput(output), 205 OptText(OptTextHideTimestamp(), OptTextNoColor()), 206 ) 207 assert.Nil(err) 208 defer log.Close() 209 210 var wasCalled bool 211 log.Listen(Info, "---", func(ctx context.Context, e Event) { 212 wasCalled = true 213 }) 214 215 log.TriggerContext(WithSkipTrigger(context.Background(), true), NewMessageEvent(Info, "this is a triggered message")) 216 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 217 defer cancel() 218 log.DrainContext(ctx) 219 220 assert.False(wasCalled) 221 assert.Contains(output.String(), "[info] this is a triggered message") 222 } 223 224 func TestLoggerSkipWrite(t *testing.T) { 225 assert := assert.New(t) 226 227 output := new(bytes.Buffer) 228 log, err := New( 229 OptOutput(output), 230 OptText(OptTextHideTimestamp(), OptTextNoColor()), 231 ) 232 assert.Nil(err) 233 defer log.Close() 234 235 var wasCalled bool 236 log.Listen(Info, "---", func(ctx context.Context, e Event) { 237 wasCalled = true 238 }) 239 assert.True(log.HasListener(Info, "---")) 240 241 log.TriggerContext(WithSkipWrite(context.Background(), true), NewMessageEvent(Info, "this is a triggered message")) 242 243 // at the very least this cannot cause a deadlock. 244 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 245 defer cancel() 246 247 log.DrainContext(ctx) 248 assert.True(wasCalled) 249 assert.Empty(output.String()) 250 } 251 252 func TestLoggerListeners(t *testing.T) { 253 assert := assert.New(t) 254 255 log := MustNew() 256 defer log.Close() 257 258 assert.Empty(log.Listeners) 259 log.Listen(Info, "foo", NewMessageEventListener(func(_ context.Context, me MessageEvent) {})) 260 assert.NotEmpty(log.Listeners) 261 assert.True(log.HasListeners(Info)) 262 assert.True(log.HasListener(Info, "foo")) 263 assert.False(log.HasListener(Info, "bar")) 264 265 log.Listen(Error, "foo", NewMessageEventListener(func(_ context.Context, me MessageEvent) {})) 266 assert.True(log.HasListeners(Error)) 267 assert.True(log.HasListener(Error, "foo")) 268 assert.False(log.HasListener(Error, "bar")) 269 270 log.Listen(Info, "bar", NewMessageEventListener(func(_ context.Context, me MessageEvent) {})) 271 assert.True(log.HasListeners(Info)) 272 assert.True(log.HasListener(Info, "foo")) 273 assert.True(log.HasListener(Info, "bar")) 274 275 log.Listen(Error, "bar", NewMessageEventListener(func(_ context.Context, me MessageEvent) {})) 276 assert.True(log.HasListeners(Error)) 277 assert.True(log.HasListener(Error, "foo")) 278 assert.True(log.HasListener(Error, "bar")) 279 280 assert.Nil(log.RemoveListener(Info, "foo")) 281 assert.True(log.HasListeners(Info)) 282 assert.False(log.HasListener(Info, "foo")) 283 assert.True(log.HasListener(Info, "bar")) 284 285 assert.Nil(log.RemoveListeners(Error)) 286 assert.False(log.HasListeners(Error)) 287 assert.False(log.HasListener(Error, "foo")) 288 assert.False(log.HasListener(Error, "bar")) 289 } 290 291 func TestLoggerFilters(t *testing.T) { 292 assert := assert.New(t) 293 294 log := MustNew() 295 defer log.Close() 296 297 noop := func(_ context.Context, e MessageEvent) (MessageEvent, bool) { 298 return e, false 299 } 300 301 assert.Empty(log.Filters) 302 log.Filter(Info, "foo", NewMessageEventFilter(noop)) 303 assert.NotEmpty(log.Filters) 304 assert.True(log.HasFilters(Info)) 305 assert.True(log.HasFilter(Info, "foo")) 306 assert.False(log.HasFilter(Info, "bar")) 307 308 log.Filter(Error, "foo", NewMessageEventFilter(noop)) 309 assert.True(log.HasFilters(Error)) 310 assert.True(log.HasFilter(Error, "foo")) 311 assert.False(log.HasFilter(Error, "bar")) 312 313 log.Filter(Info, "bar", NewMessageEventFilter(noop)) 314 assert.True(log.HasFilters(Info)) 315 assert.True(log.HasFilter(Info, "foo")) 316 assert.True(log.HasFilter(Info, "bar")) 317 318 log.Filter(Error, "bar", NewMessageEventFilter(noop)) 319 assert.True(log.HasFilters(Error)) 320 assert.True(log.HasFilter(Error, "foo")) 321 assert.True(log.HasFilter(Error, "bar")) 322 323 log.RemoveFilter(Info, "foo") 324 assert.True(log.HasFilters(Info)) 325 assert.False(log.HasFilter(Info, "foo")) 326 assert.True(log.HasFilter(Info, "bar")) 327 328 log.RemoveFilters(Error) 329 assert.False(log.HasFilters(Error)) 330 assert.False(log.HasFilter(Error, "foo")) 331 assert.False(log.HasFilter(Error, "bar")) 332 } 333 334 func TestLoggerDispatchFilterMutate(t *testing.T) { 335 it := assert.New(t) 336 337 output := new(bytes.Buffer) 338 log, err := New( 339 OptOutput(output), 340 OptText(OptTextHideTimestamp(), OptTextNoColor()), 341 ) 342 it.Nil(err) 343 defer log.Close() 344 345 var wasCalled bool 346 var textWasModified bool 347 348 log.Listen(Info, "---", func(ctx context.Context, e Event) { 349 wasCalled = true 350 textWasModified = e.(MessageEvent).Text == "not_test_message" 351 }) 352 var wasFiltered bool 353 log.Filter(Info, "---", func(ctx context.Context, e Event) (Event, bool) { 354 wasFiltered = true 355 copy := e.(MessageEvent) 356 copy.Text = "not_" + copy.Text 357 return copy, false 358 }) 359 360 log.TriggerContext(context.Background(), NewMessageEvent(Info, "test_message")) 361 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 362 defer cancel() 363 log.DrainContext(ctx) 364 365 it.True(wasFiltered) 366 it.True(wasCalled) 367 it.True(textWasModified) 368 369 it.Equal("[info] not_test_message\n", output.String()) 370 } 371 372 func TestLoggerDispatchFilterDrop(t *testing.T) { 373 it := assert.New(t) 374 375 output := new(bytes.Buffer) 376 log, err := New( 377 OptOutput(output), 378 OptText(OptTextHideTimestamp(), OptTextNoColor()), 379 ) 380 it.Nil(err) 381 defer log.Close() 382 383 var wasCalled bool 384 log.Listen(Info, "---", func(ctx context.Context, e Event) { 385 wasCalled = true 386 }) 387 var wasFiltered bool 388 log.Filter(Info, "---", func(ctx context.Context, e Event) (Event, bool) { 389 wasFiltered = true 390 return nil, true 391 }) 392 393 log.TriggerContext(context.Background(), NewMessageEvent(Info, "this is a triggered message")) 394 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 395 defer cancel() 396 log.DrainContext(ctx) 397 398 it.True(wasFiltered) 399 it.False(wasCalled) 400 it.Empty(output.String()) 401 } 402 403 func TestLoggerDrain(t *testing.T) { 404 assert := assert.New(t) 405 406 log := MustNew() 407 defer log.Close() 408 assert.Empty(log.Listeners) 409 410 eventsCounted := 0 411 log.Listen(Info, "foo", NewMessageEventListener(func(_ context.Context, me MessageEvent) { 412 eventsCounted++ 413 })) 414 415 for i := 0; i < 5; i++ { 416 log.Info("event") 417 } 418 log.Drain() 419 assert.Equal(5, eventsCounted) 420 421 for i := 0; i < 4; i++ { 422 log.Info("event") 423 } 424 log.Drain() 425 assert.Equal(9, eventsCounted) 426 } 427 428 func TestLoggerProd(t *testing.T) { 429 assert := assert.New(t) 430 431 p := Prod(OptEnabled("example-string")) 432 defer p.Close() 433 434 assert.True(p.Flags.IsEnabled("example-string")) 435 assert.True(p.Formatter.(*TextOutputFormatter).NoColor) 436 } 437 438 func TestLoggerClose(t *testing.T) { 439 assert := assert.New(t) 440 441 buf0 := new(bytes.Buffer) 442 l0 := Memory(buf0) 443 buf1 := new(bytes.Buffer) 444 l1 := Memory(buf1) 445 446 l0.Close() 447 l1.Infof("this is a test") 448 assert.Empty(buf0.String()) 449 assert.NotEmpty(buf1.String()) 450 }