github.com/GuanceCloud/cliutils@v1.1.21/logger/logger_test.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the MIT License. 3 // This product includes software developed at Guance Cloud (https://www.guance.com/). 4 // Copyright 2021-present Guance, Inc. 5 6 package logger 7 8 import ( 9 "bytes" 10 "context" 11 "encoding/json" 12 "fmt" 13 "io" 14 "log" 15 "os" 16 "strings" 17 "testing" 18 19 "github.com/stretchr/testify/assert" 20 "go.uber.org/zap/zapcore" 21 ) 22 23 func BenchmarkMuitiLogs(b *testing.B) { 24 opt := &Option{ 25 Path: "/dev/null", 26 Level: INFO, 27 Flags: OPT_ENC_CONSOLE | OPT_SHORT_CALLER, 28 } 29 30 if err := InitRoot(opt); err != nil { 31 b.Fatal(err) 32 } 33 34 for i := 0; i < b.N; i++ { 35 l := SLogger(fmt.Sprintf("bench-multi-%d", i)) 36 37 l.Debug("debug message") 38 l.Info("info message") 39 l.Warn("warn message") 40 41 l.Debugf("debugf message: %s", "hello debug") 42 l.Infof("info message: %s", "hello info") 43 l.Warnf("warn message: %s", "hello warn") 44 } 45 } 46 47 func BenchmarkBasic(b *testing.B) { 48 opt := &Option{ 49 Path: "/dev/null", 50 Level: INFO, 51 Flags: OPT_ENC_CONSOLE | OPT_SHORT_CALLER, 52 } 53 54 if err := InitRoot(opt); err != nil { 55 b.Fatal(err) 56 } 57 58 l := SLogger("bench") 59 for i := 0; i < b.N; i++ { 60 l.Debug("debug message") 61 l.Info("info message") 62 l.Warn("warn message") 63 64 l.Debugf("debugf message: %s", "hello debug") 65 l.Infof("info message: %s", "hello info") 66 l.Warnf("warn message: %s", "hello warn") 67 } 68 } 69 70 func TestLoggerSideEffect(t *testing.T) { 71 type abc struct { 72 i int 73 } 74 75 opt := &Option{ 76 Level: INFO, 77 Flags: OPT_DEFAULT, 78 } 79 80 f := func(x *abc) string { 81 x.i++ 82 return fmt.Sprintf("%d", x.i) 83 } 84 85 if err := InitRoot(opt); err != nil { 86 t.Error(err) 87 } 88 89 l := SLogger("TestLoggerSideEffect") 90 91 x := &abc{} 92 l.Debugf("%+#v", f(x)) // under info level, on debug, the f() still effected 93 94 assert.Equal(t, 1, x.i) 95 } 96 97 func TestJsonLogging(t *testing.T) { 98 opt := &Option{ 99 Path: "abc.json", 100 Level: DEBUG, 101 Flags: OPT_SHORT_CALLER | OPT_ROTATE, 102 } 103 104 if err := InitRoot(opt); err != nil { 105 t.Fatal(err) 106 } 107 108 l := SLogger("json") 109 l.Debug("this is the json message with short path") 110 showLog(opt.Path) 111 112 // check json elements 113 j, err := os.ReadFile(opt.Path) 114 if err != nil { 115 t.Error(err) 116 } 117 118 var logdata map[string]string 119 120 if err := json.Unmarshal(j, &logdata); err != nil { 121 t.Error(err) 122 } 123 124 for _, k := range []string{ 125 NameKeyMod, 126 NameKeyMsg, 127 NameKeyLevel, 128 NameKeyTime, 129 NameKeyPos, 130 } { 131 _, ok := logdata[k] 132 assert.True(t, ok) 133 } 134 135 Reset() 136 137 opt1 := &Option{ 138 Path: "abc.log", 139 Level: DEBUG, 140 Flags: OPT_ENC_CONSOLE | OPT_ROTATE, 141 } 142 143 if err := InitRoot(opt1); err != nil { 144 t.Fatal(err) 145 } 146 147 l2 := SLogger("log") 148 l2.Debug("this is the raw message with full path") 149 showLog(opt1.Path) 150 151 os.Remove(opt.Path) 152 os.Remove(opt1.Path) 153 } 154 155 func TestEnvLogPath(t *testing.T) { 156 cases := []struct { 157 name string 158 envPath string 159 msg string 160 fail bool 161 }{ 162 { 163 name: "stdout", 164 envPath: "", 165 msg: "this is debug log", 166 }, 167 { 168 name: "windows-nul", 169 envPath: "nul", 170 }, 171 { 172 name: "unix-null", 173 envPath: "/dev/null", 174 }, 175 } 176 177 for _, tc := range cases { 178 t.Run(tc.name, func(t *testing.T) { 179 Reset() 180 os.Clearenv() 181 182 if err := os.Setenv("LOGGER_PATH", tc.envPath); err != nil { 183 t.Fatal(err) 184 } 185 186 opt := &Option{Path: "" /* path not set, use env only */} 187 188 err := InitRoot(opt) 189 if tc.fail { 190 assert.True(t, err != nil) 191 t.Logf("expect error: %s", err) 192 } else { 193 assert.NoError(t, err) 194 } 195 196 l := SLogger(tc.name) 197 l.Debug(tc.msg) 198 }) 199 } 200 } 201 202 func TestLogAppend(t *testing.T) { 203 Reset() 204 205 f := "TestLogAppend.log" 206 opt := &Option{ 207 Path: f, 208 Level: DEBUG, 209 Flags: OPT_DEFAULT, 210 } 211 212 defer os.Remove(f) 213 214 if err := InitRoot(opt); err != nil { 215 t.Fatal(err) 216 } 217 218 l := SLogger("test1") 219 l.Debug("this is the first time logging") 220 221 Close() 222 223 if err := InitRoot(opt); err != nil { 224 t.Fatal(err) 225 } 226 227 l = SLogger("test1") 228 l.Debug("this is the second append logging") 229 230 Close() 231 232 assert.Equal(t, 2, logLines(opt.Path)) 233 showLog(opt.Path) 234 } 235 236 func TestTotalSLoggers(t *testing.T) { 237 Reset() 238 239 f := "TestTotalSLoggers" 240 opt := &Option{ 241 Path: f, 242 Level: DEBUG, 243 Flags: OPT_DEFAULT, 244 } 245 246 defer os.Remove(f) 247 248 if err := InitRoot(opt); err != nil { 249 t.Fatal(err) 250 } 251 252 n := int64(1000) 253 254 for i := int64(0); i < n; i++ { 255 _ = SLogger(fmt.Sprintf("slogger-%d", i)) 256 } 257 258 // should not create new SLogger any more 259 for i := int64(0); i < n; i++ { 260 _ = SLogger(fmt.Sprintf("slogger-%d", i)) 261 } 262 263 total := TotalSLoggers() 264 265 assert.Equalf(t, n, total, fmt.Sprintf("%d != %d", n, total)) 266 } 267 268 func TestInitRoot(t *testing.T) { 269 Reset() 270 271 cases := []struct { 272 name string 273 opt *Option 274 logs [][2]string 275 color, fail bool 276 }{ 277 { 278 name: "stdout-log-no-color", 279 opt: &Option{ 280 Level: INFO, 281 Flags: (OPT_DEFAULT | OPT_STDOUT), 282 }, 283 logs: [][2]string{ 284 {INFO, "stdout info log"}, 285 {DEBUG, "stdout debug log"}, 286 }, 287 fail: false, 288 }, 289 290 { 291 name: "stdout-log-with-color", 292 opt: &Option{ 293 Level: INFO, 294 Flags: (OPT_DEFAULT | OPT_STDOUT | OPT_COLOR), 295 }, 296 logs: [][2]string{ 297 {INFO, "stdout info log with color"}, 298 {DEBUG, "stdout debug log with color"}, 299 }, 300 fail: false, 301 }, 302 303 { 304 name: "normal case", 305 opt: &Option{ 306 Path: "0.log", 307 Level: DEBUG, 308 Flags: OPT_DEFAULT, 309 }, 310 logs: [][2]string{ 311 {DEBUG, "abc123"}, 312 {INFO, "abc123"}, 313 {WARN, "abc123"}, 314 }, 315 color: false, 316 }, 317 318 { 319 name: "with color", 320 opt: &Option{ 321 Path: "1.log", 322 Level: DEBUG, 323 Flags: OPT_DEFAULT | OPT_COLOR, 324 }, 325 logs: [][2]string{ 326 {DEBUG, "abc123"}, 327 {INFO, "abc123"}, 328 {WARN, "abc123"}, 329 }, 330 color: true, 331 }, 332 333 { 334 name: "stdout log with path set", 335 opt: &Option{ 336 Path: "2.log", 337 Level: DEBUG, 338 Flags: (OPT_DEFAULT | OPT_STDOUT), 339 }, 340 logs: [][2]string{ 341 {DEBUG, "abc123"}, 342 }, 343 fail: true, 344 }, 345 346 { 347 name: "no flags", 348 opt: &Option{ 349 Path: "3.log", 350 Level: DEBUG, 351 Flags: OPT_ROTATE | OPT_ENC_CONSOLE, 352 }, 353 logs: [][2]string{ 354 {DEBUG, "abc123"}, 355 {INFO, "abc123"}, 356 {WARN, "abc123"}, 357 }, 358 color: false, 359 }, 360 } 361 362 for idx, c := range cases { 363 t.Run(c.name, func(t *testing.T) { 364 err := InitRoot(c.opt) 365 l := SLogger(fmt.Sprintf("case-%d", idx)) 366 if c.fail { 367 assert.Error(t, err) 368 t.Logf("[%d] expected failing", idx) 369 return 370 } 371 372 assert.NoError(t, err) 373 374 for _, arr := range c.logs { 375 switch arr[0] { 376 case DEBUG: 377 l.Debug(arr[1]) 378 case INFO: 379 l.Info(arr[1]) 380 case WARN: 381 l.Warn(arr[1]) 382 case ERROR: 383 l.Error(arr[1]) 384 default: 385 l.Debug(arr[1]) 386 } 387 } 388 389 Reset() // reset root logger 390 if c.opt.Flags&OPT_STDOUT == 0 { 391 t.Logf("case %d on file: %s", idx, c.opt.Path) 392 assert.Equal(t, len(c.logs), logLines(c.opt.Path)) 393 assert.Equal(t, c.color, colorExits(c.opt.Path)) 394 showLog(c.opt.Path) 395 os.Remove(c.opt.Path) 396 } 397 }) 398 } 399 } 400 401 func TestRotateOnDevNull(t *testing.T) { 402 MaxSize = 1 // set to 1MB 403 404 opt := &Option{ 405 Path: "/dev/null", 406 Level: INFO, 407 Flags: OPT_ENC_CONSOLE | OPT_SHORT_CALLER | OPT_ROTATE, // set rotate 408 } 409 410 assert.NoError(t, InitRoot(opt)) 411 412 t.Logf("MaxSize: %d", MaxSize) 413 414 l := SLogger(t.Name()) 415 logData := strings.Repeat("3.1415926x", 1024) // 10kb 416 i := 0 417 for { 418 l.Info(logData) 419 i++ 420 if i >= 200 { // 2MB 421 break 422 } 423 } 424 } 425 426 type BufferSync struct { 427 io.ReadWriter 428 } 429 430 func (b *BufferSync) Sync() error { 431 return nil 432 } 433 434 func TestTrace(t *testing.T) { 435 tests := []struct { 436 name string 437 enabled bool 438 traceLoggerName string 439 spanLoggerName string 440 message string 441 extractTrace ExtractTrace 442 expectedOutput string 443 }{ 444 { 445 name: "parse trace", 446 enabled: true, 447 traceLoggerName: "traceID", 448 spanLoggerName: "span_id", 449 message: "test", 450 extractTrace: func(ctx context.Context) Trace { 451 return Trace{ 452 SpanID: "2", 453 TraceID: "1", 454 } 455 }, 456 expectedOutput: `{"level":"DEBUG","message":"test","traceID":"1","span_id":"2"}` + "\n", 457 }, 458 { 459 name: "not trace", 460 enabled: false, 461 message: "test", 462 extractTrace: nil, 463 expectedOutput: `{"level":"DEBUG","message":"test"}` + "\n", 464 }, 465 { 466 name: "not parse", 467 enabled: true, 468 traceLoggerName: "traceID", 469 spanLoggerName: "span_id", 470 message: "test", 471 extractTrace: nil, 472 expectedOutput: `{"level":"DEBUG","message":"test","traceID":"","span_id":""}` + "\n", 473 }, 474 } 475 476 buf := bytes.NewBufferString("") 477 bsync := &BufferSync{buf} 478 479 for _, test := range tests { 480 t.Run(test.name, func(t *testing.T) { 481 ast := assert.New(t) 482 opt := make([]CtxOption, 0) 483 opt = append(opt, 484 WithTraceKey(test.traceLoggerName, test.spanLoggerName), 485 WithParseTrace(test.extractTrace), 486 ) 487 if test.enabled { 488 opt = append(opt, EnableTrace()) 489 } 490 491 opt = append(opt, WithZapCore( 492 zapcore.NewCore( 493 zapcore.NewJSONEncoder(zapcore.EncoderConfig{ 494 MessageKey: "message", 495 LevelKey: "level", 496 EncodeLevel: zapcore.CapitalLevelEncoder, 497 }), 498 bsync, 499 zapcore.DebugLevel, 500 ))) 501 502 logger := NewLoggerCtx(opt...) 503 504 ctx := context.Background() 505 506 logger.DebugfCtx(ctx, test.message) 507 re, err := io.ReadAll(bsync) 508 if err != nil { 509 log.Fatal(err) 510 } 511 ast.Equal(test.expectedOutput, string(re)) 512 }) 513 } 514 } 515 516 //nolint:forbidigo 517 func showLog(f string) { 518 logdata, err := os.ReadFile(f) 519 if err != nil { 520 panic(err) 521 } 522 523 fmt.Printf("---------- %s ----------\n", f) 524 fmt.Println(string(logdata)) 525 } 526 527 func colorExits(f string) bool { 528 logdata, err := os.ReadFile(f) 529 if err != nil { 530 panic(err) 531 } 532 533 // there should be `[0m` in log files if color enabled 534 return bytes.Contains(logdata, []byte("[0m")) 535 } 536 537 func logLines(f string) int { 538 logdata, err := os.ReadFile(f) 539 if err != nil { 540 panic(fmt.Sprintf("ReadFile(%s) failed: %s", f, err)) 541 } 542 543 return len(bytes.Split(logdata, []byte("\n"))) - 1 544 }