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