github.com/AndrienkoAleksandr/go@v0.0.19/src/log/slog/handler_test.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // TODO: verify that the output of Marshal{Text,JSON} is suitably escaped. 6 7 package slog 8 9 import ( 10 "bytes" 11 "context" 12 "encoding/json" 13 "io" 14 "log/slog/internal/buffer" 15 "path/filepath" 16 "slices" 17 "strconv" 18 "strings" 19 "testing" 20 "time" 21 ) 22 23 func TestDefaultHandle(t *testing.T) { 24 ctx := context.Background() 25 preAttrs := []Attr{Int("pre", 0)} 26 attrs := []Attr{Int("a", 1), String("b", "two")} 27 for _, test := range []struct { 28 name string 29 with func(Handler) Handler 30 attrs []Attr 31 want string 32 }{ 33 { 34 name: "no attrs", 35 want: "INFO message", 36 }, 37 { 38 name: "attrs", 39 attrs: attrs, 40 want: "INFO message a=1 b=two", 41 }, 42 { 43 name: "preformatted", 44 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) }, 45 attrs: attrs, 46 want: "INFO message pre=0 a=1 b=two", 47 }, 48 { 49 name: "groups", 50 attrs: []Attr{ 51 Int("a", 1), 52 Group("g", 53 Int("b", 2), 54 Group("h", Int("c", 3)), 55 Int("d", 4)), 56 Int("e", 5), 57 }, 58 want: "INFO message a=1 g.b=2 g.h.c=3 g.d=4 e=5", 59 }, 60 { 61 name: "group", 62 with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("s") }, 63 attrs: attrs, 64 want: "INFO message pre=0 s.a=1 s.b=two", 65 }, 66 { 67 name: "preformatted groups", 68 with: func(h Handler) Handler { 69 return h.WithAttrs([]Attr{Int("p1", 1)}). 70 WithGroup("s1"). 71 WithAttrs([]Attr{Int("p2", 2)}). 72 WithGroup("s2") 73 }, 74 attrs: attrs, 75 want: "INFO message p1=1 s1.p2=2 s1.s2.a=1 s1.s2.b=two", 76 }, 77 { 78 name: "two with-groups", 79 with: func(h Handler) Handler { 80 return h.WithAttrs([]Attr{Int("p1", 1)}). 81 WithGroup("s1"). 82 WithGroup("s2") 83 }, 84 attrs: attrs, 85 want: "INFO message p1=1 s1.s2.a=1 s1.s2.b=two", 86 }, 87 } { 88 t.Run(test.name, func(t *testing.T) { 89 var got string 90 var h Handler = newDefaultHandler(func(_ uintptr, b []byte) error { 91 got = string(b) 92 return nil 93 }) 94 if test.with != nil { 95 h = test.with(h) 96 } 97 r := NewRecord(time.Time{}, LevelInfo, "message", 0) 98 r.AddAttrs(test.attrs...) 99 if err := h.Handle(ctx, r); err != nil { 100 t.Fatal(err) 101 } 102 if got != test.want { 103 t.Errorf("\ngot %s\nwant %s", got, test.want) 104 } 105 }) 106 } 107 } 108 109 // Verify the common parts of TextHandler and JSONHandler. 110 func TestJSONAndTextHandlers(t *testing.T) { 111 ctx := context.Background() 112 113 // remove all Attrs 114 removeAll := func(_ []string, a Attr) Attr { return Attr{} } 115 116 attrs := []Attr{String("a", "one"), Int("b", 2), Any("", nil)} 117 preAttrs := []Attr{Int("pre", 3), String("x", "y")} 118 119 for _, test := range []struct { 120 name string 121 replace func([]string, Attr) Attr 122 addSource bool 123 with func(Handler) Handler 124 preAttrs []Attr 125 attrs []Attr 126 wantText string 127 wantJSON string 128 }{ 129 { 130 name: "basic", 131 attrs: attrs, 132 wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2", 133 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2}`, 134 }, 135 { 136 name: "empty key", 137 attrs: append(slices.Clip(attrs), Any("", "v")), 138 wantText: `time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2 ""=v`, 139 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2,"":"v"}`, 140 }, 141 { 142 name: "cap keys", 143 replace: upperCaseKey, 144 attrs: attrs, 145 wantText: "TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG=message A=one B=2", 146 wantJSON: `{"TIME":"2000-01-02T03:04:05Z","LEVEL":"INFO","MSG":"message","A":"one","B":2}`, 147 }, 148 { 149 name: "remove all", 150 replace: removeAll, 151 attrs: attrs, 152 wantText: "", 153 wantJSON: `{}`, 154 }, 155 { 156 name: "preformatted", 157 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) }, 158 preAttrs: preAttrs, 159 attrs: attrs, 160 wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message pre=3 x=y a=one b=2", 161 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","pre":3,"x":"y","a":"one","b":2}`, 162 }, 163 { 164 name: "preformatted cap keys", 165 replace: upperCaseKey, 166 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) }, 167 preAttrs: preAttrs, 168 attrs: attrs, 169 wantText: "TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG=message PRE=3 X=y A=one B=2", 170 wantJSON: `{"TIME":"2000-01-02T03:04:05Z","LEVEL":"INFO","MSG":"message","PRE":3,"X":"y","A":"one","B":2}`, 171 }, 172 { 173 name: "preformatted remove all", 174 replace: removeAll, 175 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) }, 176 preAttrs: preAttrs, 177 attrs: attrs, 178 wantText: "", 179 wantJSON: "{}", 180 }, 181 { 182 name: "remove built-in", 183 replace: removeKeys(TimeKey, LevelKey, MessageKey), 184 attrs: attrs, 185 wantText: "a=one b=2", 186 wantJSON: `{"a":"one","b":2}`, 187 }, 188 { 189 name: "preformatted remove built-in", 190 replace: removeKeys(TimeKey, LevelKey, MessageKey), 191 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) }, 192 attrs: attrs, 193 wantText: "pre=3 x=y a=one b=2", 194 wantJSON: `{"pre":3,"x":"y","a":"one","b":2}`, 195 }, 196 { 197 name: "groups", 198 replace: removeKeys(TimeKey, LevelKey), // to simplify the result 199 attrs: []Attr{ 200 Int("a", 1), 201 Group("g", 202 Int("b", 2), 203 Group("h", Int("c", 3)), 204 Int("d", 4)), 205 Int("e", 5), 206 }, 207 wantText: "msg=message a=1 g.b=2 g.h.c=3 g.d=4 e=5", 208 wantJSON: `{"msg":"message","a":1,"g":{"b":2,"h":{"c":3},"d":4},"e":5}`, 209 }, 210 { 211 name: "empty group", 212 replace: removeKeys(TimeKey, LevelKey), 213 attrs: []Attr{Group("g"), Group("h", Int("a", 1))}, 214 wantText: "msg=message h.a=1", 215 wantJSON: `{"msg":"message","h":{"a":1}}`, 216 }, 217 { 218 name: "escapes", 219 replace: removeKeys(TimeKey, LevelKey), 220 attrs: []Attr{ 221 String("a b", "x\t\n\000y"), 222 Group(" b.c=\"\\x2E\t", 223 String("d=e", "f.g\""), 224 Int("m.d", 1)), // dot is not escaped 225 }, 226 wantText: `msg=message "a b"="x\t\n\x00y" " b.c=\"\\x2E\t.d=e"="f.g\"" " b.c=\"\\x2E\t.m.d"=1`, 227 wantJSON: `{"msg":"message","a b":"x\t\n\u0000y"," b.c=\"\\x2E\t":{"d=e":"f.g\"","m.d":1}}`, 228 }, 229 { 230 name: "LogValuer", 231 replace: removeKeys(TimeKey, LevelKey), 232 attrs: []Attr{ 233 Int("a", 1), 234 Any("name", logValueName{"Ren", "Hoek"}), 235 Int("b", 2), 236 }, 237 wantText: "msg=message a=1 name.first=Ren name.last=Hoek b=2", 238 wantJSON: `{"msg":"message","a":1,"name":{"first":"Ren","last":"Hoek"},"b":2}`, 239 }, 240 { 241 // Test resolution when there is no ReplaceAttr function. 242 name: "resolve", 243 attrs: []Attr{ 244 Any("", &replace{Value{}}), // should be elided 245 Any("name", logValueName{"Ren", "Hoek"}), 246 }, 247 wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message name.first=Ren name.last=Hoek", 248 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","name":{"first":"Ren","last":"Hoek"}}`, 249 }, 250 { 251 name: "with-group", 252 replace: removeKeys(TimeKey, LevelKey), 253 with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("s") }, 254 attrs: attrs, 255 wantText: "msg=message pre=3 x=y s.a=one s.b=2", 256 wantJSON: `{"msg":"message","pre":3,"x":"y","s":{"a":"one","b":2}}`, 257 }, 258 { 259 name: "preformatted with-groups", 260 replace: removeKeys(TimeKey, LevelKey), 261 with: func(h Handler) Handler { 262 return h.WithAttrs([]Attr{Int("p1", 1)}). 263 WithGroup("s1"). 264 WithAttrs([]Attr{Int("p2", 2)}). 265 WithGroup("s2"). 266 WithAttrs([]Attr{Int("p3", 3)}) 267 }, 268 attrs: attrs, 269 wantText: "msg=message p1=1 s1.p2=2 s1.s2.p3=3 s1.s2.a=one s1.s2.b=2", 270 wantJSON: `{"msg":"message","p1":1,"s1":{"p2":2,"s2":{"p3":3,"a":"one","b":2}}}`, 271 }, 272 { 273 name: "two with-groups", 274 replace: removeKeys(TimeKey, LevelKey), 275 with: func(h Handler) Handler { 276 return h.WithAttrs([]Attr{Int("p1", 1)}). 277 WithGroup("s1"). 278 WithGroup("s2") 279 }, 280 attrs: attrs, 281 wantText: "msg=message p1=1 s1.s2.a=one s1.s2.b=2", 282 wantJSON: `{"msg":"message","p1":1,"s1":{"s2":{"a":"one","b":2}}}`, 283 }, 284 { 285 name: "GroupValue as Attr value", 286 replace: removeKeys(TimeKey, LevelKey), 287 attrs: []Attr{{"v", AnyValue(IntValue(3))}}, 288 wantText: "msg=message v=3", 289 wantJSON: `{"msg":"message","v":3}`, 290 }, 291 { 292 name: "byte slice", 293 replace: removeKeys(TimeKey, LevelKey), 294 attrs: []Attr{Any("bs", []byte{1, 2, 3, 4})}, 295 wantText: `msg=message bs="\x01\x02\x03\x04"`, 296 wantJSON: `{"msg":"message","bs":"AQIDBA=="}`, 297 }, 298 { 299 name: "json.RawMessage", 300 replace: removeKeys(TimeKey, LevelKey), 301 attrs: []Attr{Any("bs", json.RawMessage([]byte("1234")))}, 302 wantText: `msg=message bs="1234"`, 303 wantJSON: `{"msg":"message","bs":1234}`, 304 }, 305 { 306 name: "inline group", 307 replace: removeKeys(TimeKey, LevelKey), 308 attrs: []Attr{ 309 Int("a", 1), 310 Group("", Int("b", 2), Int("c", 3)), 311 Int("d", 4), 312 }, 313 wantText: `msg=message a=1 b=2 c=3 d=4`, 314 wantJSON: `{"msg":"message","a":1,"b":2,"c":3,"d":4}`, 315 }, 316 { 317 name: "Source", 318 replace: func(gs []string, a Attr) Attr { 319 if a.Key == SourceKey { 320 s := a.Value.Any().(*Source) 321 s.File = filepath.Base(s.File) 322 return Any(a.Key, s) 323 } 324 return removeKeys(TimeKey, LevelKey)(gs, a) 325 }, 326 addSource: true, 327 wantText: `source=handler_test.go:$LINE msg=message`, 328 wantJSON: `{"source":{"function":"log/slog.TestJSONAndTextHandlers","file":"handler_test.go","line":$LINE},"msg":"message"}`, 329 }, 330 { 331 name: "replace built-in with group", 332 replace: func(_ []string, a Attr) Attr { 333 if a.Key == TimeKey { 334 return Group(TimeKey, "mins", 3, "secs", 2) 335 } 336 if a.Key == LevelKey { 337 return Attr{} 338 } 339 return a 340 }, 341 wantText: `time.mins=3 time.secs=2 msg=message`, 342 wantJSON: `{"time":{"mins":3,"secs":2},"msg":"message"}`, 343 }, 344 } { 345 r := NewRecord(testTime, LevelInfo, "message", callerPC(2)) 346 line := strconv.Itoa(r.source().Line) 347 r.AddAttrs(test.attrs...) 348 var buf bytes.Buffer 349 opts := HandlerOptions{ReplaceAttr: test.replace, AddSource: test.addSource} 350 t.Run(test.name, func(t *testing.T) { 351 for _, handler := range []struct { 352 name string 353 h Handler 354 want string 355 }{ 356 {"text", NewTextHandler(&buf, &opts), test.wantText}, 357 {"json", NewJSONHandler(&buf, &opts), test.wantJSON}, 358 } { 359 t.Run(handler.name, func(t *testing.T) { 360 h := handler.h 361 if test.with != nil { 362 h = test.with(h) 363 } 364 buf.Reset() 365 if err := h.Handle(ctx, r); err != nil { 366 t.Fatal(err) 367 } 368 want := strings.ReplaceAll(handler.want, "$LINE", line) 369 got := strings.TrimSuffix(buf.String(), "\n") 370 if got != want { 371 t.Errorf("\ngot %s\nwant %s\n", got, want) 372 } 373 }) 374 } 375 }) 376 } 377 } 378 379 // removeKeys returns a function suitable for HandlerOptions.ReplaceAttr 380 // that removes all Attrs with the given keys. 381 func removeKeys(keys ...string) func([]string, Attr) Attr { 382 return func(_ []string, a Attr) Attr { 383 for _, k := range keys { 384 if a.Key == k { 385 return Attr{} 386 } 387 } 388 return a 389 } 390 } 391 392 func upperCaseKey(_ []string, a Attr) Attr { 393 a.Key = strings.ToUpper(a.Key) 394 return a 395 } 396 397 type logValueName struct { 398 first, last string 399 } 400 401 func (n logValueName) LogValue() Value { 402 return GroupValue( 403 String("first", n.first), 404 String("last", n.last)) 405 } 406 407 func TestHandlerEnabled(t *testing.T) { 408 levelVar := func(l Level) *LevelVar { 409 var al LevelVar 410 al.Set(l) 411 return &al 412 } 413 414 for _, test := range []struct { 415 leveler Leveler 416 want bool 417 }{ 418 {nil, true}, 419 {LevelWarn, false}, 420 {&LevelVar{}, true}, // defaults to Info 421 {levelVar(LevelWarn), false}, 422 {LevelDebug, true}, 423 {levelVar(LevelDebug), true}, 424 } { 425 h := &commonHandler{opts: HandlerOptions{Level: test.leveler}} 426 got := h.enabled(LevelInfo) 427 if got != test.want { 428 t.Errorf("%v: got %t, want %t", test.leveler, got, test.want) 429 } 430 } 431 } 432 433 func TestSecondWith(t *testing.T) { 434 // Verify that a second call to Logger.With does not corrupt 435 // the original. 436 var buf bytes.Buffer 437 h := NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeKeys(TimeKey)}) 438 logger := New(h).With( 439 String("app", "playground"), 440 String("role", "tester"), 441 Int("data_version", 2), 442 ) 443 appLogger := logger.With("type", "log") // this becomes type=met 444 _ = logger.With("type", "metric") 445 appLogger.Info("foo") 446 got := strings.TrimSpace(buf.String()) 447 want := `level=INFO msg=foo app=playground role=tester data_version=2 type=log` 448 if got != want { 449 t.Errorf("\ngot %s\nwant %s", got, want) 450 } 451 } 452 453 func TestReplaceAttrGroups(t *testing.T) { 454 // Verify that ReplaceAttr is called with the correct groups. 455 type ga struct { 456 groups string 457 key string 458 val string 459 } 460 461 var got []ga 462 463 h := NewTextHandler(io.Discard, &HandlerOptions{ReplaceAttr: func(gs []string, a Attr) Attr { 464 v := a.Value.String() 465 if a.Key == TimeKey { 466 v = "<now>" 467 } 468 got = append(got, ga{strings.Join(gs, ","), a.Key, v}) 469 return a 470 }}) 471 New(h). 472 With(Int("a", 1)). 473 WithGroup("g1"). 474 With(Int("b", 2)). 475 WithGroup("g2"). 476 With( 477 Int("c", 3), 478 Group("g3", Int("d", 4)), 479 Int("e", 5)). 480 Info("m", 481 Int("f", 6), 482 Group("g4", Int("h", 7)), 483 Int("i", 8)) 484 485 want := []ga{ 486 {"", "a", "1"}, 487 {"g1", "b", "2"}, 488 {"g1,g2", "c", "3"}, 489 {"g1,g2,g3", "d", "4"}, 490 {"g1,g2", "e", "5"}, 491 {"", "time", "<now>"}, 492 {"", "level", "INFO"}, 493 {"", "msg", "m"}, 494 {"g1,g2", "f", "6"}, 495 {"g1,g2,g4", "h", "7"}, 496 {"g1,g2", "i", "8"}, 497 } 498 if !slices.Equal(got, want) { 499 t.Errorf("\ngot %v\nwant %v", got, want) 500 } 501 } 502 503 const rfc3339Millis = "2006-01-02T15:04:05.000Z07:00" 504 505 func TestWriteTimeRFC3339(t *testing.T) { 506 for _, tm := range []time.Time{ 507 time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC), 508 time.Date(2000, 1, 2, 3, 4, 5, 400, time.Local), 509 time.Date(2000, 11, 12, 3, 4, 500, 5e7, time.UTC), 510 } { 511 want := tm.Format(rfc3339Millis) 512 buf := buffer.New() 513 defer buf.Free() 514 writeTimeRFC3339Millis(buf, tm) 515 got := buf.String() 516 if got != want { 517 t.Errorf("got %s, want %s", got, want) 518 } 519 } 520 } 521 522 func BenchmarkWriteTime(b *testing.B) { 523 buf := buffer.New() 524 defer buf.Free() 525 tm := time.Date(2022, 3, 4, 5, 6, 7, 823456789, time.Local) 526 b.ResetTimer() 527 for i := 0; i < b.N; i++ { 528 writeTimeRFC3339Millis(buf, tm) 529 buf.Reset() 530 } 531 }