github.com/Laisky/zap@v1.27.0/zapcore/encoder_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 zapcore_test 22 23 import ( 24 "encoding/json" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 "gopkg.in/yaml.v3" 32 33 //revive:disable:dot-imports 34 . "github.com/Laisky/zap/zapcore" 35 ) 36 37 var ( 38 _epoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) 39 _testEntry = Entry{ 40 LoggerName: "main", 41 Level: InfoLevel, 42 Message: `hello`, 43 Time: _epoch, 44 Stack: "fake-stack", 45 Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"}, 46 } 47 ) 48 49 func testEncoderConfig() EncoderConfig { 50 return EncoderConfig{ 51 MessageKey: "msg", 52 LevelKey: "level", 53 NameKey: "name", 54 TimeKey: "ts", 55 CallerKey: "caller", 56 FunctionKey: "func", 57 StacktraceKey: "stacktrace", 58 LineEnding: "\n", 59 EncodeTime: EpochTimeEncoder, 60 EncodeLevel: LowercaseLevelEncoder, 61 EncodeDuration: SecondsDurationEncoder, 62 EncodeCaller: ShortCallerEncoder, 63 } 64 } 65 66 func humanEncoderConfig() EncoderConfig { 67 cfg := testEncoderConfig() 68 cfg.EncodeTime = ISO8601TimeEncoder 69 cfg.EncodeLevel = CapitalLevelEncoder 70 cfg.EncodeDuration = StringDurationEncoder 71 return cfg 72 } 73 74 func capitalNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { 75 enc.AppendString(strings.ToUpper(loggerName)) 76 } 77 78 func TestEncoderConfiguration(t *testing.T) { 79 base := testEncoderConfig() 80 81 tests := []struct { 82 desc string 83 cfg EncoderConfig 84 amendEntry func(Entry) Entry 85 extra func(Encoder) 86 expectedJSON string 87 expectedConsole string 88 }{ 89 { 90 desc: "messages to be escaped", 91 cfg: base, 92 amendEntry: func(ent Entry) Entry { 93 ent.Message = `hello\` 94 return ent 95 }, 96 expectedJSON: `{"level":"info","ts":0,"name":"main","caller":"foo.go:42","func":"foo.Foo","msg":"hello\\","stacktrace":"fake-stack"}` + "\n", 97 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\\\nfake-stack\n", 98 }, 99 { 100 desc: "use custom entry keys in JSON output and ignore them in console output", 101 cfg: EncoderConfig{ 102 LevelKey: "L", 103 TimeKey: "T", 104 MessageKey: "M", 105 NameKey: "N", 106 CallerKey: "C", 107 FunctionKey: "F", 108 StacktraceKey: "S", 109 LineEnding: base.LineEnding, 110 EncodeTime: base.EncodeTime, 111 EncodeDuration: base.EncodeDuration, 112 EncodeLevel: base.EncodeLevel, 113 EncodeCaller: base.EncodeCaller, 114 }, 115 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 116 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 117 }, 118 { 119 desc: "skip line ending if SkipLineEnding is 'true'", 120 cfg: EncoderConfig{ 121 LevelKey: "L", 122 TimeKey: "T", 123 MessageKey: "M", 124 NameKey: "N", 125 CallerKey: "C", 126 FunctionKey: "F", 127 StacktraceKey: "S", 128 LineEnding: base.LineEnding, 129 SkipLineEnding: true, 130 EncodeTime: base.EncodeTime, 131 EncodeDuration: base.EncodeDuration, 132 EncodeLevel: base.EncodeLevel, 133 EncodeCaller: base.EncodeCaller, 134 }, 135 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}`, 136 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack", 137 }, 138 { 139 desc: "skip level if LevelKey is omitted", 140 cfg: EncoderConfig{ 141 LevelKey: OmitKey, 142 TimeKey: "T", 143 MessageKey: "M", 144 NameKey: "N", 145 CallerKey: "C", 146 FunctionKey: "F", 147 StacktraceKey: "S", 148 LineEnding: base.LineEnding, 149 EncodeTime: base.EncodeTime, 150 EncodeDuration: base.EncodeDuration, 151 EncodeLevel: base.EncodeLevel, 152 EncodeCaller: base.EncodeCaller, 153 }, 154 expectedJSON: `{"T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 155 expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 156 }, 157 { 158 desc: "skip timestamp if TimeKey is omitted", 159 cfg: EncoderConfig{ 160 LevelKey: "L", 161 TimeKey: OmitKey, 162 MessageKey: "M", 163 NameKey: "N", 164 CallerKey: "C", 165 FunctionKey: "F", 166 StacktraceKey: "S", 167 LineEnding: base.LineEnding, 168 EncodeTime: base.EncodeTime, 169 EncodeDuration: base.EncodeDuration, 170 EncodeLevel: base.EncodeLevel, 171 EncodeCaller: base.EncodeCaller, 172 }, 173 expectedJSON: `{"L":"info","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 174 expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 175 }, 176 { 177 desc: "skip message if MessageKey is omitted", 178 cfg: EncoderConfig{ 179 LevelKey: "L", 180 TimeKey: "T", 181 MessageKey: OmitKey, 182 NameKey: "N", 183 CallerKey: "C", 184 FunctionKey: "F", 185 StacktraceKey: "S", 186 LineEnding: base.LineEnding, 187 EncodeTime: base.EncodeTime, 188 EncodeDuration: base.EncodeDuration, 189 EncodeLevel: base.EncodeLevel, 190 EncodeCaller: base.EncodeCaller, 191 }, 192 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","S":"fake-stack"}` + "\n", 193 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\nfake-stack\n", 194 }, 195 { 196 desc: "skip name if NameKey is omitted", 197 cfg: EncoderConfig{ 198 LevelKey: "L", 199 TimeKey: "T", 200 MessageKey: "M", 201 NameKey: OmitKey, 202 CallerKey: "C", 203 FunctionKey: "F", 204 StacktraceKey: "S", 205 LineEnding: base.LineEnding, 206 EncodeTime: base.EncodeTime, 207 EncodeDuration: base.EncodeDuration, 208 EncodeLevel: base.EncodeLevel, 209 EncodeCaller: base.EncodeCaller, 210 }, 211 expectedJSON: `{"L":"info","T":0,"C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 212 expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 213 }, 214 { 215 desc: "skip caller if CallerKey is omitted", 216 cfg: EncoderConfig{ 217 LevelKey: "L", 218 TimeKey: "T", 219 MessageKey: "M", 220 NameKey: "N", 221 CallerKey: OmitKey, 222 FunctionKey: "F", 223 StacktraceKey: "S", 224 LineEnding: base.LineEnding, 225 EncodeTime: base.EncodeTime, 226 EncodeDuration: base.EncodeDuration, 227 EncodeLevel: base.EncodeLevel, 228 EncodeCaller: base.EncodeCaller, 229 }, 230 expectedJSON: `{"L":"info","T":0,"N":"main","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 231 expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", 232 }, 233 { 234 desc: "skip function if FunctionKey is omitted", 235 cfg: EncoderConfig{ 236 LevelKey: "L", 237 TimeKey: "T", 238 MessageKey: "M", 239 NameKey: "N", 240 CallerKey: "C", 241 FunctionKey: OmitKey, 242 StacktraceKey: "S", 243 LineEnding: base.LineEnding, 244 EncodeTime: base.EncodeTime, 245 EncodeDuration: base.EncodeDuration, 246 EncodeLevel: base.EncodeLevel, 247 EncodeCaller: base.EncodeCaller, 248 }, 249 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", 250 expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\n", 251 }, 252 { 253 desc: "skip stacktrace if StacktraceKey is omitted", 254 cfg: EncoderConfig{ 255 LevelKey: "L", 256 TimeKey: "T", 257 MessageKey: "M", 258 NameKey: "N", 259 CallerKey: "C", 260 FunctionKey: "F", 261 StacktraceKey: OmitKey, 262 LineEnding: base.LineEnding, 263 EncodeTime: base.EncodeTime, 264 EncodeDuration: base.EncodeDuration, 265 EncodeLevel: base.EncodeLevel, 266 EncodeCaller: base.EncodeCaller, 267 }, 268 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello"}` + "\n", 269 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\n", 270 }, 271 { 272 desc: "use the supplied EncodeTime, for both the entry and any times added", 273 cfg: EncoderConfig{ 274 LevelKey: "L", 275 TimeKey: "T", 276 MessageKey: "M", 277 NameKey: "N", 278 CallerKey: "C", 279 FunctionKey: "F", 280 StacktraceKey: "S", 281 LineEnding: base.LineEnding, 282 EncodeTime: func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) }, 283 EncodeDuration: base.EncodeDuration, 284 EncodeLevel: base.EncodeLevel, 285 EncodeCaller: base.EncodeCaller, 286 }, 287 extra: func(enc Encoder) { 288 enc.AddTime("extra", _epoch) 289 err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { 290 enc.AppendTime(_epoch) 291 return nil 292 })) 293 assert.NoError(t, err) 294 }, 295 expectedJSON: `{"L":"info","T":"1970-01-01 00:00:00 +0000 UTC","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1970-01-01 00:00:00 +0000 UTC","extras":["1970-01-01 00:00:00 +0000 UTC"],"S":"fake-stack"}` + "\n", 296 expectedConsole: "1970-01-01 00:00:00 +0000 UTC\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // plain-text preamble 297 `{"extra": "1970-01-01 00:00:00 +0000 UTC", "extras": ["1970-01-01 00:00:00 +0000 UTC"]}` + // JSON context 298 "\nfake-stack\n", // stacktrace after newline 299 }, 300 { 301 desc: "use the supplied EncodeDuration for any durations added", 302 cfg: EncoderConfig{ 303 LevelKey: "L", 304 TimeKey: "T", 305 MessageKey: "M", 306 NameKey: "N", 307 CallerKey: "C", 308 FunctionKey: "F", 309 StacktraceKey: "S", 310 LineEnding: base.LineEnding, 311 EncodeTime: base.EncodeTime, 312 EncodeDuration: StringDurationEncoder, 313 EncodeLevel: base.EncodeLevel, 314 EncodeCaller: base.EncodeCaller, 315 }, 316 extra: func(enc Encoder) { 317 enc.AddDuration("extra", time.Second) 318 err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { 319 enc.AppendDuration(time.Minute) 320 return nil 321 })) 322 assert.NoError(t, err) 323 }, 324 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1s","extras":["1m0s"],"S":"fake-stack"}` + "\n", 325 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // preamble 326 `{"extra": "1s", "extras": ["1m0s"]}` + // context 327 "\nfake-stack\n", // stacktrace 328 }, 329 { 330 desc: "use the supplied EncodeLevel", 331 cfg: EncoderConfig{ 332 LevelKey: "L", 333 TimeKey: "T", 334 MessageKey: "M", 335 NameKey: "N", 336 CallerKey: "C", 337 FunctionKey: "F", 338 StacktraceKey: "S", 339 LineEnding: base.LineEnding, 340 EncodeTime: base.EncodeTime, 341 EncodeDuration: base.EncodeDuration, 342 EncodeLevel: CapitalLevelEncoder, 343 EncodeCaller: base.EncodeCaller, 344 }, 345 expectedJSON: `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 346 expectedConsole: "0\tINFO\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 347 }, 348 { 349 desc: "use the supplied EncodeName", 350 cfg: EncoderConfig{ 351 LevelKey: "L", 352 TimeKey: "T", 353 MessageKey: "M", 354 NameKey: "N", 355 CallerKey: "C", 356 FunctionKey: "F", 357 StacktraceKey: "S", 358 LineEnding: base.LineEnding, 359 EncodeTime: base.EncodeTime, 360 EncodeDuration: base.EncodeDuration, 361 EncodeLevel: base.EncodeLevel, 362 EncodeCaller: base.EncodeCaller, 363 EncodeName: capitalNameEncoder, 364 }, 365 expectedJSON: `{"L":"info","T":0,"N":"MAIN","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 366 expectedConsole: "0\tinfo\tMAIN\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 367 }, 368 { 369 desc: "close all open namespaces", 370 cfg: EncoderConfig{ 371 LevelKey: "L", 372 TimeKey: "T", 373 MessageKey: "M", 374 NameKey: "N", 375 CallerKey: "C", 376 FunctionKey: "F", 377 StacktraceKey: "S", 378 LineEnding: base.LineEnding, 379 EncodeTime: base.EncodeTime, 380 EncodeDuration: base.EncodeDuration, 381 EncodeLevel: base.EncodeLevel, 382 EncodeCaller: base.EncodeCaller, 383 }, 384 extra: func(enc Encoder) { 385 enc.OpenNamespace("outer") 386 enc.OpenNamespace("inner") 387 enc.AddString("foo", "bar") 388 enc.OpenNamespace("innermost") 389 }, 390 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","outer":{"inner":{"foo":"bar","innermost":{}}},"S":"fake-stack"}` + "\n", 391 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + 392 `{"outer": {"inner": {"foo": "bar", "innermost": {}}}}` + 393 "\nfake-stack\n", 394 }, 395 { 396 desc: "handle no-op EncodeTime", 397 cfg: EncoderConfig{ 398 LevelKey: "L", 399 TimeKey: "T", 400 MessageKey: "M", 401 NameKey: "N", 402 CallerKey: "C", 403 FunctionKey: "F", 404 StacktraceKey: "S", 405 LineEnding: base.LineEnding, 406 EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, 407 EncodeDuration: base.EncodeDuration, 408 EncodeLevel: base.EncodeLevel, 409 EncodeCaller: base.EncodeCaller, 410 }, 411 extra: func(enc Encoder) { enc.AddTime("sometime", time.Unix(0, 100)) }, 412 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","sometime":100,"S":"fake-stack"}` + "\n", 413 expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"sometime": 100}` + "\nfake-stack\n", 414 }, 415 { 416 desc: "handle no-op EncodeDuration", 417 cfg: EncoderConfig{ 418 LevelKey: "L", 419 TimeKey: "T", 420 MessageKey: "M", 421 NameKey: "N", 422 CallerKey: "C", 423 FunctionKey: "F", 424 StacktraceKey: "S", 425 LineEnding: base.LineEnding, 426 EncodeTime: base.EncodeTime, 427 EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {}, 428 EncodeLevel: base.EncodeLevel, 429 EncodeCaller: base.EncodeCaller, 430 }, 431 extra: func(enc Encoder) { enc.AddDuration("someduration", time.Microsecond) }, 432 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","someduration":1000,"S":"fake-stack"}` + "\n", 433 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"someduration": 1000}` + "\nfake-stack\n", 434 }, 435 { 436 desc: "handle no-op EncodeLevel", 437 cfg: EncoderConfig{ 438 LevelKey: "L", 439 TimeKey: "T", 440 MessageKey: "M", 441 NameKey: "N", 442 CallerKey: "C", 443 FunctionKey: "F", 444 StacktraceKey: "S", 445 LineEnding: base.LineEnding, 446 EncodeTime: base.EncodeTime, 447 EncodeDuration: base.EncodeDuration, 448 EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, 449 EncodeCaller: base.EncodeCaller, 450 }, 451 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 452 expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 453 }, 454 { 455 desc: "handle no-op EncodeCaller", 456 cfg: EncoderConfig{ 457 LevelKey: "L", 458 TimeKey: "T", 459 MessageKey: "M", 460 NameKey: "N", 461 CallerKey: "C", 462 FunctionKey: "F", 463 StacktraceKey: "S", 464 LineEnding: base.LineEnding, 465 EncodeTime: base.EncodeTime, 466 EncodeDuration: base.EncodeDuration, 467 EncodeLevel: base.EncodeLevel, 468 EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {}, 469 }, 470 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 471 expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", 472 }, 473 { 474 desc: "handle no-op EncodeName", 475 cfg: EncoderConfig{ 476 LevelKey: "L", 477 TimeKey: "T", 478 MessageKey: "M", 479 NameKey: "N", 480 CallerKey: "C", 481 FunctionKey: "F", 482 StacktraceKey: "S", 483 LineEnding: base.LineEnding, 484 EncodeTime: base.EncodeTime, 485 EncodeDuration: base.EncodeDuration, 486 EncodeLevel: base.EncodeLevel, 487 EncodeCaller: base.EncodeCaller, 488 EncodeName: func(string, PrimitiveArrayEncoder) {}, 489 }, 490 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", 491 expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", 492 }, 493 { 494 desc: "use custom line separator", 495 cfg: EncoderConfig{ 496 LevelKey: "L", 497 TimeKey: "T", 498 MessageKey: "M", 499 NameKey: "N", 500 CallerKey: "C", 501 FunctionKey: "F", 502 StacktraceKey: "S", 503 LineEnding: "\r\n", 504 EncodeTime: base.EncodeTime, 505 EncodeDuration: base.EncodeDuration, 506 EncodeLevel: base.EncodeLevel, 507 EncodeCaller: base.EncodeCaller, 508 }, 509 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\r\n", 510 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\r\n", 511 }, 512 { 513 desc: "omit line separator definition - fall back to default", 514 cfg: EncoderConfig{ 515 LevelKey: "L", 516 TimeKey: "T", 517 MessageKey: "M", 518 NameKey: "N", 519 CallerKey: "C", 520 FunctionKey: "F", 521 StacktraceKey: "S", 522 EncodeTime: base.EncodeTime, 523 EncodeDuration: base.EncodeDuration, 524 EncodeLevel: base.EncodeLevel, 525 EncodeCaller: base.EncodeCaller, 526 }, 527 expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + DefaultLineEnding, 528 expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack" + DefaultLineEnding, 529 }, 530 } 531 532 for i, tt := range tests { 533 json := NewJSONEncoder(tt.cfg) 534 console := NewConsoleEncoder(tt.cfg) 535 if tt.extra != nil { 536 tt.extra(json) 537 tt.extra(console) 538 } 539 entry := _testEntry 540 if tt.amendEntry != nil { 541 entry = tt.amendEntry(_testEntry) 542 } 543 jsonOut, jsonErr := json.EncodeEntry(entry, nil) 544 if assert.NoError(t, jsonErr, "Unexpected error JSON-encoding entry in case #%d.", i) { 545 assert.Equal( 546 t, 547 tt.expectedJSON, 548 jsonOut.String(), 549 "Unexpected JSON output: expected to %v.", tt.desc, 550 ) 551 } 552 consoleOut, consoleErr := console.EncodeEntry(entry, nil) 553 if assert.NoError(t, consoleErr, "Unexpected error console-encoding entry in case #%d.", i) { 554 assert.Equal( 555 t, 556 tt.expectedConsole, 557 consoleOut.String(), 558 "Unexpected console output: expected to %v.", tt.desc, 559 ) 560 } 561 } 562 } 563 564 func TestLevelEncoders(t *testing.T) { 565 tests := []struct { 566 name string 567 expected interface{} // output of encoding InfoLevel 568 }{ 569 {"capital", "INFO"}, 570 {"lower", "info"}, 571 {"", "info"}, 572 {"something-random", "info"}, 573 } 574 575 for _, tt := range tests { 576 var le LevelEncoder 577 require.NoError(t, le.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) 578 assertAppended( 579 t, 580 tt.expected, 581 func(arr ArrayEncoder) { le(InfoLevel, arr) }, 582 "Unexpected output serializing InfoLevel with %q.", tt.name, 583 ) 584 } 585 } 586 587 func TestTimeEncoders(t *testing.T) { 588 moment := time.Unix(100, 50005000).UTC() 589 tests := []struct { 590 yamlDoc string 591 expected interface{} // output of serializing moment 592 }{ 593 {"timeEncoder: iso8601", "1970-01-01T00:01:40.050Z"}, 594 {"timeEncoder: ISO8601", "1970-01-01T00:01:40.050Z"}, 595 {"timeEncoder: millis", 100050.005}, 596 {"timeEncoder: nanos", int64(100050005000)}, 597 {"timeEncoder: {layout: 06/01/02 03:04pm}", "70/01/01 12:01am"}, 598 {"timeEncoder: ''", 100.050005}, 599 {"timeEncoder: something-random", 100.050005}, 600 {"timeEncoder: rfc3339", "1970-01-01T00:01:40Z"}, 601 {"timeEncoder: RFC3339", "1970-01-01T00:01:40Z"}, 602 {"timeEncoder: rfc3339nano", "1970-01-01T00:01:40.050005Z"}, 603 {"timeEncoder: RFC3339Nano", "1970-01-01T00:01:40.050005Z"}, 604 } 605 606 for _, tt := range tests { 607 cfg := EncoderConfig{} 608 require.NoError(t, yaml.Unmarshal([]byte(tt.yamlDoc), &cfg), "Unexpected error unmarshaling %q.", tt.yamlDoc) 609 require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.yamlDoc) 610 assertAppended( 611 t, 612 tt.expected, 613 func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, 614 "Unexpected output serializing %v with %q.", moment, tt.yamlDoc, 615 ) 616 } 617 } 618 619 func TestTimeEncodersWrongYAML(t *testing.T) { 620 tests := []string{ 621 "timeEncoder: [1, 2, 3]", // wrong type 622 "timeEncoder: {foo:bar", // broken yaml 623 } 624 for _, tt := range tests { 625 cfg := EncoderConfig{} 626 assert.Error(t, yaml.Unmarshal([]byte(tt), &cfg), "Expected unmarshaling %q to become error, but not.", tt) 627 } 628 } 629 630 func TestTimeEncodersParseFromJSON(t *testing.T) { 631 moment := time.Unix(100, 50005000).UTC() 632 tests := []struct { 633 jsonDoc string 634 expected interface{} // output of serializing moment 635 }{ 636 {`{"timeEncoder": "iso8601"}`, "1970-01-01T00:01:40.050Z"}, 637 {`{"timeEncoder": {"layout": "06/01/02 03:04pm"}}`, "70/01/01 12:01am"}, 638 } 639 640 for _, tt := range tests { 641 cfg := EncoderConfig{} 642 require.NoError(t, json.Unmarshal([]byte(tt.jsonDoc), &cfg), "Unexpected error unmarshaling %q.", tt.jsonDoc) 643 require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.jsonDoc) 644 assertAppended( 645 t, 646 tt.expected, 647 func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, 648 "Unexpected output serializing %v with %q.", moment, tt.jsonDoc, 649 ) 650 } 651 } 652 653 func TestDurationEncoders(t *testing.T) { 654 elapsed := time.Second + 500*time.Nanosecond 655 tests := []struct { 656 name string 657 expected interface{} // output of serializing elapsed 658 }{ 659 {"string", "1.0000005s"}, 660 {"nanos", int64(1000000500)}, 661 {"ms", int64(1000)}, 662 {"", 1.0000005}, 663 {"something-random", 1.0000005}, 664 } 665 666 for _, tt := range tests { 667 var de DurationEncoder 668 require.NoError(t, de.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) 669 assertAppended( 670 t, 671 tt.expected, 672 func(arr ArrayEncoder) { de(elapsed, arr) }, 673 "Unexpected output serializing %v with %q.", elapsed, tt.name, 674 ) 675 } 676 } 677 678 func TestCallerEncoders(t *testing.T) { 679 caller := EntryCaller{Defined: true, File: "/home/jack/src/github.com/foo/foo.go", Line: 42} 680 tests := []struct { 681 name string 682 expected interface{} // output of serializing caller 683 }{ 684 {"", "foo/foo.go:42"}, 685 {"something-random", "foo/foo.go:42"}, 686 {"short", "foo/foo.go:42"}, 687 {"full", "/home/jack/src/github.com/foo/foo.go:42"}, 688 } 689 690 for _, tt := range tests { 691 var ce CallerEncoder 692 require.NoError(t, ce.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) 693 assertAppended( 694 t, 695 tt.expected, 696 func(arr ArrayEncoder) { ce(caller, arr) }, 697 "Unexpected output serializing file name as %v with %q.", tt.expected, tt.name, 698 ) 699 } 700 } 701 702 func TestNameEncoders(t *testing.T) { 703 tests := []struct { 704 name string 705 expected interface{} // output of encoding InfoLevel 706 }{ 707 {"", "main"}, 708 {"full", "main"}, 709 {"something-random", "main"}, 710 } 711 712 for _, tt := range tests { 713 var ne NameEncoder 714 require.NoError(t, ne.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) 715 assertAppended( 716 t, 717 tt.expected, 718 func(arr ArrayEncoder) { ne("main", arr) }, 719 "Unexpected output serializing logger name with %q.", tt.name, 720 ) 721 } 722 } 723 724 func assertAppended(t testing.TB, expected interface{}, f func(ArrayEncoder), msgAndArgs ...interface{}) { 725 mem := NewMapObjectEncoder() 726 err := mem.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { 727 f(arr) 728 return nil 729 })) 730 assert.NoError(t, err, msgAndArgs...) 731 arr := mem.Fields["k"].([]interface{}) 732 require.Equal(t, 1, len(arr), "Expected to append exactly one element to array.") 733 assert.Equal(t, expected, arr[0], msgAndArgs...) 734 }