github.com/oarkflow/log@v1.0.78/README.md (about) 1 # phuslog - Fastest structured logging 2 3 [![godoc][godoc-img]][godoc] 4 [![goreport][report-img]][report] 5 [![build][build-img]][build] 6 ![stability-stable][stability-img] 7 8 ## Features 9 10 * Dependency Free 11 * Simple and Clean Interface 12 * Consistent Writer 13 - `IOWriter`, *io.Writer wrapper* 14 - `ConsoleWriter`, *colorful & formatting* 15 - `FileWriter`, *rotating & effective* 16 - `MultiLevelWriter`, *multiple level dispatch* 17 - `SyslogWriter`, *memory efficient syslog* 18 - `JournalWriter`, *linux systemd logging* 19 - `EventlogWriter`, *windows system event* 20 - `AsyncWriter`, *asynchronously writing* 21 * Stdlib Log Adapter 22 - `Logger.Std`, *transform to std log instances* 23 - `Logger.Slog`, *transform to log/slog instances* 24 * Third-party Logger Interceptor 25 - `logr`, *logr interceptor* 26 - `gin`, *gin logging middleware* 27 - `gorm`, *gorm logger interface* 28 - `fiber`, *fiber logging handler* 29 - `grpc`, *grpc logger interceptor* 30 - `grpcgateway`, *grpcgateway logger interceptor* 31 * Useful utility function 32 - `Goid()`, *the goroutine id matches stack trace* 33 - `NewXID()`, *create a tracing id* 34 - `Fastrandn(n uint32)`, *fast pseudorandom uint32 in [0,n)* 35 - `IsTerminal(fd uintptr)`, *isatty for golang* 36 - `Printf(fmt string, a ...interface{})`, *printf logging* 37 * High Performance 38 - [Significantly faster][high-performance] than all other json loggers. 39 40 ## Interfaces 41 42 ### Logger 43 ```go 44 // DefaultLogger is the global logger. 45 var DefaultLogger = Logger{ 46 Level: DebugLevel, 47 Caller: 0, 48 TimeField: "", 49 TimeFormat: "", 50 Writer: &IOWriter{os.Stderr}, 51 } 52 53 // Logger represents an active logging object that generates lines of JSON output to an io.Writer. 54 type Logger struct { 55 // Level defines log levels. 56 Level Level 57 58 // Caller determines if adds the file:line of the "caller" key. 59 // If Caller is negative, adds the full /path/to/file:line of the "caller" key. 60 Caller int 61 62 // TimeField defines the time field name in output. It uses "time" in if empty. 63 TimeField string 64 65 // TimeFormat specifies the time format in output. Uses RFC3339 with millisecond if empty. 66 // If set to `TimeFormatUnix/TimeFormatUnixMs`, timestamps will be formatted. 67 TimeFormat string 68 69 // TimeLocation specifices that the location of TimeFormat used. Uses time.Local if empty. 70 TimeLocation *time.Location 71 72 // Writer specifies the writer of output. It uses a wrapped os.Stderr Writer in if empty. 73 Writer Writer 74 } 75 ``` 76 77 ### ConsoleWriter 78 ```go 79 // ConsoleWriter parses the JSON input and writes it in a colorized, human-friendly format to Writer. 80 // IMPORTANT: Don't use ConsoleWriter on critical path of a high concurrency and low latency application. 81 // 82 // Default output format: 83 // {Time} {Level} {Goid} {Caller} > {Message} {Key}={Value} {Key}={Value} 84 type ConsoleWriter struct { 85 // ColorOutput determines if used colorized output. 86 ColorOutput bool 87 88 // QuoteString determines if quoting string values. 89 QuoteString bool 90 91 // EndWithMessage determines if output message in the end of line. 92 EndWithMessage bool 93 94 // Writer is the output destination. using os.Stderr if empty. 95 Writer io.Writer 96 97 // Formatter specifies an optional text formatter for creating a customized output, 98 // If it is set, ColorOutput, QuoteString and EndWithMessage will be ignored. 99 Formatter func(w io.Writer, args *FormatterArgs) (n int, err error) 100 } 101 102 // FormatterArgs is a parsed sturct from json input 103 type FormatterArgs struct { 104 Time string // "2019-07-10T05:35:54.277Z" 105 Message string // "a structure message" 106 Level string // "info" 107 Caller string // "main.go:123" 108 Goid string // "1" 109 Stack string // "<stack string>" 110 KeyValues []struct { 111 Key string // "foo" 112 Value string // "bar" 113 } 114 } 115 ``` 116 117 ### FileWriter 118 ```go 119 // FileWriter is an Writer that writes to the specified filename. 120 type FileWriter struct { 121 // Filename is the file to write logs to. Backup log files will be retained 122 // in the same directory. 123 Filename string 124 125 // FileMode represents the file's mode and permission bits. The default 126 // mode is 0644 127 FileMode os.FileMode 128 129 // MaxSize is the maximum size in bytes of the log file before it gets rotated. 130 MaxSize int64 131 132 // MaxBackups is the maximum number of old log files to retain. The default 133 // is to retain all old log files 134 MaxBackups int 135 136 // TimeFormat specifies the time format of filename, uses `2006-01-02T15-04-05` as default format. 137 // If set with `TimeFormatUnix`, `TimeFormatUnixMs`, times are formated as UNIX timestamp. 138 TimeFormat string 139 140 // LocalTime determines if the time used for formatting the timestamps in 141 // log files is the computer's local time. The default is to use UTC time. 142 LocalTime bool 143 144 // HostName determines if the hostname used for formatting in log files. 145 HostName bool 146 147 // ProcessID determines if the pid used for formatting in log files. 148 ProcessID bool 149 150 // EnsureFolder ensures the file directory creation before writing. 151 EnsureFolder bool 152 153 // Header specifies an optional header function of log file after rotation, 154 Header func(fileinfo os.FileInfo) []byte 155 156 // Cleaner specifies an optional cleanup function of log backups after rotation, 157 // if not set, the default behavior is to delete more than MaxBackups log files. 158 Cleaner func(filename string, maxBackups int, matches []os.FileInfo) 159 } 160 ``` 161 *Highlights*: 162 - FileWriter implements log.Writer and io.Writer interfaces both, it is a recommended alternative to [lumberjack][lumberjack]. 163 - FileWriter creates a symlink to the current logging file, it requires administrator privileges on Windows. 164 - FileWriter does not rotate if you define a broad TimeFormat value(daily or monthly) until reach its MaxSize. 165 166 ## Getting Started 167 168 ### Simple Logging Example 169 170 An out of box example. [![playground][play-simple-img]][play-simple] 171 ```go 172 package main 173 174 import ( 175 "github.com/phuslu/log" 176 ) 177 178 func main() { 179 log.Info().Str("foo", "bar").Int("number", 42).Msg("hi, phuslog") 180 log.Info().Msgf("foo=%s number=%d error=%+v", "bar", 42, "an error") 181 } 182 183 // Output: 184 // {"time":"2020-03-22T09:58:41.828Z","level":"info","foo":"bar","number":42,"message":"hi, phuslog"} 185 // {"time":"2020-03-22T09:58:41.828Z","level":"info","message":"foo=bar number=42 error=an error"} 186 ``` 187 > Note: By default log writes to `os.Stderr` 188 189 ### Customize the configuration and formatting: 190 191 To customize logger filed name and format. [![playground][play-customize-img]][play-customize] 192 ```go 193 package main 194 195 import ( 196 "github.com/phuslu/log" 197 ) 198 199 func main() { 200 log.DefaultLogger = log.Logger{ 201 Level: log.InfoLevel, 202 Caller: 1, 203 TimeField: "date", 204 TimeFormat: "2006-01-02", 205 Writer: &log.IOWriter{os.Stdout}, 206 } 207 208 log.Info().Str("foo", "bar").Msgf("hello %s", "world") 209 210 logger := log.Logger{ 211 Level: log.InfoLevel, 212 TimeField: "ts", 213 TimeFormat: log.TimeFormatUnixMs, 214 } 215 216 logger.Log().Str("foo", "bar").Msg("") 217 } 218 219 // Output: 220 // {"date":"2019-07-04","level":"info","caller":"prog.go:16","foo":"bar","message":"hello world"} 221 // {"ts":1257894000000,"foo":"bar"} 222 ``` 223 224 ### Customize the log writer 225 226 To allow the use of ordinary functions as log writers, use `WriterFunc`. 227 228 ```go 229 logger := log.Logger{ 230 Writer: log.WriterFunc(func(e *log.Entry) (int, error) { 231 if e.Level >= log.ErrorLevel { 232 return os.Stderr.Write(e.Value()) 233 } else { 234 return os.Stdout.Write(e.Value()) 235 } 236 }), 237 } 238 239 logger.Info().Msg("a stdout entry") 240 logger.Error().Msg("a stderr entry") 241 ``` 242 243 ### Pretty Console Writer 244 245 To log a human-friendly, colorized output, use `ConsoleWriter`. [![playground][play-pretty-img]][play-pretty] 246 247 ```go 248 if log.IsTerminal(os.Stderr.Fd()) { 249 log.DefaultLogger = log.Logger{ 250 TimeFormat: "15:04:05", 251 Caller: 1, 252 Writer: &log.ConsoleWriter{ 253 ColorOutput: true, 254 QuoteString: true, 255 EndWithMessage: true, 256 }, 257 } 258 } 259 260 log.Debug().Int("everything", 42).Str("foo", "bar").Msg("hello world") 261 log.Info().Int("everything", 42).Str("foo", "bar").Msg("hello world") 262 log.Warn().Int("everything", 42).Str("foo", "bar").Msg("hello world") 263 log.Error().Err(errors.New("an error")).Msg("hello world") 264 ``` 265 ![Pretty logging][pretty-img] 266 > Note: pretty logging also works on windows console 267 268 ### Formatting Console Writer 269 270 To log with user-defined format(e.g. glog), using `ConsoleWriter.Formatter`. [![playground][play-glog-img]][play-glog] 271 272 ```go 273 package main 274 275 import ( 276 "fmt" 277 "io" 278 279 "github.com/phuslu/log" 280 ) 281 282 type Glog struct { 283 Logger log.Logger 284 } 285 286 func (l *Glog) Infof(fmt string, a ...any) { l.Logger.Info().Msgf(fmt, a...) } 287 288 func (l *Glog) Warnf(fmt string, a ...any) { l.Logger.Warn().Msgf(fmt, a...) } 289 290 func (l *Glog) Errorf(fmt string, a ...any) { l.Logger.Error().Msgf(fmt, a...) } 291 292 var glog = &Glog{log.Logger{ 293 Level: log.InfoLevel, 294 Caller: 2, 295 TimeFormat: "0102 15:04:05.999999", 296 Writer: &log.ConsoleWriter{Formatter: func(w io.Writer, a *log.FormatterArgs) (int, error) { 297 return fmt.Fprintf(w, "%c%s %s %s] %s\n%s", a.Level[0]-32, a.Time, a.Goid, a.Caller, a.Message, a.Stack) 298 }}, 299 }} 300 301 func main() { 302 glog.Infof("hello glog %s", "Info") 303 glog.Warnf("hello glog %s", "Warn") 304 glog.Errorf("hello glog %s", "Error") 305 } 306 307 // Output: 308 // I0725 09:59:57.503246 19 console_test.go:183] hello glog Info 309 // W0725 09:59:57.504247 19 console_test.go:184] hello glog Warn 310 // E0725 09:59:57.504247 19 console_test.go:185] hello glog Error 311 ``` 312 313 ### Formatting Logfmt output 314 315 To log with logfmt format, also using `ConsoleWriter.Formatter`. [![playground][play-logfmt-img]][play-logfmt] 316 317 ```go 318 package main 319 320 import ( 321 "io" 322 "os" 323 324 "github.com/phuslu/log" 325 ) 326 327 func main() { 328 log.DefaultLogger = log.Logger{ 329 Level: log.InfoLevel, 330 Caller: 1, 331 TimeField: "ts", 332 TimeFormat: log.TimeFormatUnixWithMs, 333 Writer: &log.ConsoleWriter{ 334 Formatter: log.LogfmtFormatter{"ts"}.Formatter, 335 Writer: io.MultiWriter(os.Stdout, os.Stderr), 336 }, 337 } 338 339 log.Info().Str("foo", "bar").Int("no", 42).Msgf("a logfmt %s", "info") 340 } 341 342 // Output: 343 // ts=1257894000.000 level=info goid=1 caller="prog.go:20" foo="bar" no=42 "a logfmt info" 344 // ts=1257894000.000 level=info goid=1 caller="prog.go:20" foo="bar" no=42 "a logfmt info" 345 ``` 346 347 ### Rotating File Writer 348 349 To log to a daily-rotating file, use `FileWriter`. [![playground][play-file-img]][play-file] 350 ```go 351 package main 352 353 import ( 354 "os" 355 "path/filepath" 356 "time" 357 358 "github.com/phuslu/log" 359 "github.com/robfig/cron/v3" 360 ) 361 362 func main() { 363 logger := log.Logger{ 364 Level: log.ParseLevel("info"), 365 Writer: &log.FileWriter{ 366 Filename: "logs/main.log", 367 FileMode: 0600, 368 MaxSize: 100 * 1024 * 1024, 369 MaxBackups: 7, 370 EnsureFolder: true, 371 LocalTime: true, 372 }, 373 } 374 375 runner := cron.New(cron.WithLocation(time.Local)) 376 runner.AddFunc("0 0 * * *", func() { logger.Writer.(*log.FileWriter).Rotate() }) 377 go runner.Run() 378 379 for { 380 time.Sleep(time.Second) 381 logger.Info().Msg("hello world") 382 } 383 } 384 ``` 385 386 ### Rotating File Writer within a total size 387 388 To rotating log file hourly and keep in a total size, use `FileWriter.Cleaner`. 389 ```go 390 package main 391 392 import ( 393 "os" 394 "path/filepath" 395 "time" 396 397 "github.com/phuslu/log" 398 "github.com/robfig/cron/v3" 399 ) 400 401 func main() { 402 logger := log.Logger{ 403 Level: log.ParseLevel("info"), 404 Writer: &log.FileWriter{ 405 Filename: "main.log", 406 MaxSize: 500 * 1024 * 1024, 407 Cleaner: func(filename string, maxBackups int, matches []os.FileInfo) { 408 var dir = filepath.Dir(filename) 409 var total int64 410 for i := len(matches) - 1; i >= 0; i-- { 411 total += matches[i].Size() 412 if total > 5*1024*1024*1024 { 413 os.Remove(filepath.Join(dir, matches[i].Name())) 414 } 415 } 416 }, 417 }, 418 } 419 420 runner := cron.New(cron.WithLocation(time.UTC)) 421 runner.AddFunc("0 * * * *", func() { logger.Writer.(*log.FileWriter).Rotate() }) 422 go runner.Run() 423 424 for { 425 time.Sleep(time.Second) 426 logger.Info().Msg("hello world") 427 } 428 } 429 ``` 430 431 ### Rotating File Writer with compression 432 433 To rotating log file hourly and compressing after rotation, use `FileWriter.Cleaner`. 434 ```go 435 package main 436 437 import ( 438 "os" 439 "os/exec" 440 "path/filepath" 441 "time" 442 443 "github.com/phuslu/log" 444 "github.com/robfig/cron/v3" 445 ) 446 447 func main() { 448 logger := log.Logger{ 449 Level: log.ParseLevel("info"), 450 Writer: &log.FileWriter{ 451 Filename: "main.log", 452 MaxSize: 500 * 1024 * 1024, 453 Cleaner: func(filename string, maxBackups int, matches []os.FileInfo) { 454 var dir = filepath.Dir(filename) 455 for i, fi := range matches { 456 filename := filepath.Join(dir, fi.Name()) 457 switch { 458 case i > maxBackups: 459 os.Remove(filename) 460 case !strings.HasSuffix(filename, ".gz"): 461 go exec.Command("nice", "gzip", filename).Run() 462 } 463 } 464 }, 465 }, 466 } 467 468 runner := cron.New(cron.WithLocation(time.UTC)) 469 runner.AddFunc("0 * * * *", func() { logger.Writer.(*log.FileWriter).Rotate() }) 470 go runner.Run() 471 472 for { 473 time.Sleep(time.Second) 474 logger.Info().Msg("hello world") 475 } 476 } 477 ``` 478 479 ### Random Sample Logger: 480 481 To logging only 5% logs, use below idiom. 482 ```go 483 if log.Fastrandn(100) < 5 { 484 log.Log().Msg("hello world") 485 } 486 ``` 487 488 ### Multiple Dispatching Writer 489 490 To log to different writers by different levels, use `MultiLevelWriter`. 491 492 ```go 493 log.DefaultLogger.Writer = &log.MultiLevelWriter{ 494 InfoWriter: &log.FileWriter{Filename: "main.INFO", MaxSize: 100<<20}, 495 WarnWriter: &log.FileWriter{Filename: "main.WARNING", MaxSize: 100<<20}, 496 ErrorWriter: &log.FileWriter{Filename: "main.ERROR", MaxSize: 100<<20}, 497 ConsoleWriter: &log.ConsoleWriter{ColorOutput: true}, 498 ConsoleLevel: log.ErrorLevel, 499 } 500 501 log.Info().Int("number", 42).Str("foo", "bar").Msg("a info log") 502 log.Warn().Int("number", 42).Str("foo", "bar").Msg("a warn log") 503 log.Error().Int("number", 42).Str("foo", "bar").Msg("a error log") 504 ``` 505 506 ### Multiple Entry Writer 507 To log to different writers, use `MultiEntryWriter`. 508 509 ```go 510 log.DefaultLogger.Writer = &log.MultiEntryWriter{ 511 &log.ConsoleWriter{ColorOutput: true}, 512 &log.FileWriter{Filename: "main.log", MaxSize: 100<<20}, 513 &log.EventlogWriter{Source: ".NET Runtime", ID: 1000}, 514 } 515 516 log.Info().Int("number", 42).Str("foo", "bar").Msg("a info log") 517 ``` 518 519 ### Multiple IO Writer 520 521 To log to multiple io writers like `io.MultiWriter`, use below idiom. [![playground][play-multiio-img]][play-multiio] 522 523 ```go 524 log.DefaultLogger.Writer = &log.MultiIOWriter{ 525 os.Stdout, 526 &log.FileWriter{Filename: "main.log", MaxSize: 100<<20}, 527 } 528 529 log.Info().Int("number", 42).Str("foo", "bar").Msg("a info log") 530 ``` 531 532 ### Multiple Combined Logger: 533 534 To logging to different logger as you want, use below idiom. [![playground][play-combined-img]][play-combined] 535 ```go 536 package main 537 538 import ( 539 "github.com/phuslu/log" 540 ) 541 542 var logger = struct { 543 Console log.Logger 544 Access log.Logger 545 Data log.Logger 546 }{ 547 Console: log.Logger{ 548 TimeFormat: "15:04:05", 549 Caller: 1, 550 Writer: &log.ConsoleWriter{ 551 ColorOutput: true, 552 EndWithMessage: true, 553 }, 554 }, 555 Access: log.Logger{ 556 Level: log.InfoLevel, 557 Writer: &log.FileWriter{ 558 Filename: "access.log", 559 MaxSize: 50 * 1024 * 1024, 560 MaxBackups: 7, 561 LocalTime: false, 562 }, 563 }, 564 Data: log.Logger{ 565 Level: log.InfoLevel, 566 Writer: &log.FileWriter{ 567 Filename: "data.log", 568 MaxSize: 50 * 1024 * 1024, 569 MaxBackups: 7, 570 LocalTime: false, 571 }, 572 }, 573 } 574 575 func main() { 576 logger.Console.Info().Msgf("hello world") 577 logger.Access.Log().Msgf("handle request") 578 logger.Data.Log().Msgf("some data") 579 } 580 ``` 581 582 ### SyslogWriter 583 584 `SyslogWriter` is a memory-efficient, cross-platform, dependency-free syslog writer, outperforms all other structured logging libraries. 585 586 ```go 587 package main 588 589 import ( 590 "net" 591 "time" 592 593 "github.com/phuslu/log" 594 ) 595 596 func main() { 597 go func() { 598 ln, _ := net.Listen("tcp", "127.0.0.1:1601") 599 for { 600 conn, _ := ln.Accept() 601 go func(c net.Conn) { 602 b := make([]byte, 8192) 603 n, _ := conn.Read(b) 604 println(string(b[:n])) 605 }(conn) 606 } 607 }() 608 609 syslog := log.Logger{ 610 Level: log.InfoLevel, 611 TimeField: "ts", 612 TimeFormat: log.TimeFormatUnixMs, 613 Writer: &log.SyslogWriter{ 614 Network: "tcp", // "unixgram", 615 Address: "127.0.0.1:1601", // "/run/systemd/journal/syslog", 616 Tag: "", 617 Marker: "@cee:", 618 Dial: net.Dial, 619 }, 620 } 621 622 syslog.Info().Str("foo", "bar").Int("an", 42).Msg("a syslog info") 623 syslog.Warn().Str("foo", "bar").Int("an", 42).Msg("a syslog warn") 624 time.Sleep(2) 625 } 626 627 // Output: 628 // <6>2022-07-24T18:48:15+08:00 127.0.0.1:59277 [11516]: @cee:{"ts":1658659695428,"level":"info","foo":"bar","an":42,"message":"a syslog info"} 629 // <4>2022-07-24T18:48:15+08:00 127.0.0.1:59277 [11516]: @cee:{"ts":1658659695429,"level":"warn","foo":"bar","an":42,"message":"a syslog warn"} 630 ``` 631 632 ### JournalWriter 633 634 To log to linux systemd journald, using `JournalWriter`. 635 636 ```go 637 log.DefaultLogger.Writer = &log.JournalWriter{ 638 JournalSocket: "/run/systemd/journal/socket", 639 } 640 641 log.Info().Int("number", 42).Str("foo", "bar").Msg("hello world") 642 ``` 643 644 ### EventlogWriter 645 646 To log to windows system event, using `EventlogWriter`. 647 648 ```go 649 log.DefaultLogger.Writer = &log.EventlogWriter{ 650 Source: ".NET Runtime", 651 ID: 1000, 652 } 653 654 log.Info().Int("number", 42).Str("foo", "bar").Msg("hello world") 655 ``` 656 657 ### AsyncWriter 658 659 To logging asynchronously for performance stability, use `AsyncWriter`. 660 661 ```go 662 logger := log.Logger{ 663 Level: log.InfoLevel, 664 Writer: &log.AsyncWriter{ 665 ChannelSize: 100, 666 Writer: &log.FileWriter{ 667 Filename: "main.log", 668 FileMode: 0600, 669 MaxSize: 50*1024*1024, 670 MaxBackups: 7, 671 LocalTime: false, 672 }, 673 }, 674 } 675 676 logger.Info().Int("number", 42).Str("foo", "bar").Msg("a async info log") 677 logger.Warn().Int("number", 42).Str("foo", "bar").Msg("a async warn log") 678 logger.Writer.(io.Closer).Close() 679 ``` 680 681 > Note: To flush data and quit safely, call `AsyncWriter.Close()` explicitly. 682 683 ### Stdlib Log Adapter 684 685 Using wrapped loggers for stdlog. [![playground][play-stdlog-img]][play-stdlog] 686 687 ```go 688 package main 689 690 import ( 691 stdlog "log" 692 "os" 693 694 "github.com/phuslu/log" 695 ) 696 697 func main() { 698 var logger *stdlog.Logger = (&log.Logger{ 699 Level: log.InfoLevel, 700 TimeField: "date", 701 TimeFormat: "2006-01-02", 702 Caller: 1, 703 Context: log.NewContext(nil).Str("logger", "mystdlog").Int("myid", 42).Value(), 704 Writer: &log.IOWriter{os.Stdout}, 705 }).Std("", 0) 706 707 logger.Print("hello from stdlog Print") 708 logger.Println("hello from stdlog Println") 709 logger.Printf("hello from stdlog %s", "Printf") 710 } 711 ``` 712 713 ### slog Adapter 714 715 Using wrapped loggers for slog. [![playground][play-slog-img]][play-slog] 716 717 ```go 718 package main 719 720 import ( 721 "log/slog" 722 723 "github.com/phuslu/log" 724 ) 725 726 func main() { 727 var logger *slog.Logger = (&log.Logger{ 728 Level: log.InfoLevel, 729 TimeField: "date", 730 TimeFormat: "2006-01-02", 731 Caller: 1, 732 }).Slog() 733 734 logger = logger.With("logger", "a_test_slog").With("everything", 42) 735 736 logger.Info("hello from slog Info") 737 logger.Warn("hello from slog Warn") 738 logger.Error("hello from slog Error") 739 } 740 ``` 741 742 ### Third-party Logger Interceptor 743 744 | Logger | Interceptor | 745 |---|---| 746 | logr | https://github.com/phuslu/log-contrib/tree/master/logr | 747 | gin | https://github.com/phuslu/log-contrib/tree/master/gin | 748 | fiber | https://github.com/phuslu/log-contrib/tree/master/fiber | 749 | gorm | https://github.com/phuslu/log-contrib/tree/master/gorm | 750 | grpc | https://github.com/phuslu/log-contrib/tree/master/grpc | 751 | grpcgateway | https://github.com/phuslu/log-contrib/tree/master/grpcgateway | 752 753 ### User-defined Data Structure 754 755 To log with user-defined struct effectively, implements `MarshalObject`. [![playground][play-marshal-img]][play-marshal] 756 757 ```go 758 package main 759 760 import ( 761 "github.com/phuslu/log" 762 ) 763 764 type User struct { 765 ID int 766 Name string 767 Pass string 768 } 769 770 func (u *User) MarshalObject(e *log.Entry) { 771 e.Int("id", u.ID).Str("name", u.Name).Str("password", "***") 772 } 773 774 func main() { 775 log.Info().Object("user", &User{1, "neo", "123456"}).Msg("") 776 log.Info().EmbedObject(&User{2, "john", "abc"}).Msg("") 777 } 778 779 // Output: 780 // {"time":"2020-07-12T05:03:43.949Z","level":"info","user":{"id":1,"name":"neo","password":"***"}} 781 // {"time":"2020-07-12T05:03:43.949Z","level":"info","id":2,"name":"john","password":"***"} 782 ``` 783 784 ### Contextual Fields 785 786 To add preserved `key:value` pairs to each entry, use `NewContext`. [![playground][play-context-img]][play-context] 787 788 ```go 789 logger := log.Logger{ 790 Level: log.InfoLevel, 791 Context: log.NewContext(nil).Str("ctx", "some_ctx").Value(), 792 } 793 794 logger.Debug().Int("no0", 0).Msg("zero") 795 logger.Info().Int("no1", 1).Msg("first") 796 logger.Info().Int("no2", 2).Msg("second") 797 798 // Output: 799 // {"time":"2020-07-12T05:03:43.949Z","level":"info","ctx":"some_ctx","no1":1,"message":"first"} 800 // {"time":"2020-07-12T05:03:43.949Z","level":"info","ctx":"some_ctx","no2":2,"message":"second"} 801 ``` 802 803 You can make a copy of log and add contextual fields. [![playground][play-context-add-img]][play-context-add] 804 805 ```go 806 package main 807 808 import ( 809 "github.com/phuslu/log" 810 ) 811 812 func main() { 813 sublogger := log.DefaultLogger 814 sublogger.Level = log.InfoLevel 815 sublogger.Context = log.NewContext(nil).Str("ctx", "some_ctx").Value() 816 817 sublogger.Debug().Int("no0", 0).Msg("zero") 818 sublogger.Info().Int("no1", 1).Msg("first") 819 sublogger.Info().Int("no2", 2).Msg("second") 820 log.Debug().Int("no3", 3).Msg("no context") 821 } 822 823 // Output: 824 // {"time":"2021-06-14T06:36:42.904+02:00","level":"info","ctx":"some_ctx","no1":1,"message":"first"} 825 // {"time":"2021-06-14T06:36:42.905+02:00","level":"info","ctx":"some_ctx","no2":2,"message":"second"} 826 // {"time":"2021-06-14T06:36:42.906+02:00","level":"debug","no3":3,"message":"no context"} 827 ``` 828 829 ### High Performance 830 831 <details> 832 <summary>The most common benchmarks(disable/normal/caller/printf/interface) against slog/zap/zerolog</summary> 833 834 ```go 835 // go test -v -cpu=4 -run=none -bench=. -benchtime=10s -benchmem bench_test.go 836 package main 837 838 import ( 839 "io" 840 "log" 841 "log/slog" 842 "testing" 843 844 phuslog "github.com/phuslu/log" 845 "github.com/rs/zerolog" 846 "go.uber.org/zap" 847 "go.uber.org/zap/zapcore" 848 ) 849 850 const msg = "The quick brown fox jumps over the lazy dog" 851 var obj = struct {Rate string; Low int; High float32}{"15", 16, 123.2} 852 853 func BenchmarkSlogDisable(b *testing.B) { 854 logger := slog.New(slog.NewJSONHandler(io.Discard, nil)) 855 for i := 0; i < b.N; i++ { 856 logger.Debug(msg, "rate", "15", "low", 16, "high", 123.2) 857 } 858 } 859 860 func BenchmarkSlogNormal(b *testing.B) { 861 logger := slog.New(slog.NewJSONHandler(io.Discard, nil)) 862 for i := 0; i < b.N; i++ { 863 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 864 } 865 } 866 867 func BenchmarkSlogPrintf(b *testing.B) { 868 slog.SetDefault(slog.New(slog.NewJSONHandler(io.Discard, nil))) 869 for i := 0; i < b.N; i++ { 870 log.Printf("rate=%s low=%d high=%f msg=%s", "15", 16, 123.2, msg) 871 } 872 } 873 874 func BenchmarkSlogCaller(b *testing.B) { 875 logger := slog.New(slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{AddSource: true})) 876 for i := 0; i < b.N; i++ { 877 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 878 } 879 } 880 881 func BenchmarkSlogInterface(b *testing.B) { 882 logger := slog.New(slog.NewJSONHandler(io.Discard, nil)) 883 for i := 0; i < b.N; i++ { 884 logger.Info(msg, "object", &obj) 885 } 886 } 887 888 func BenchmarkZapDisable(b *testing.B) { 889 logger := zap.New(zapcore.NewCore( 890 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), 891 zapcore.AddSync(io.Discard), 892 zapcore.InfoLevel, 893 )).Sugar() 894 for i := 0; i < b.N; i++ { 895 logger.Debugw(msg, "rate", "15", "low", 16, "high", 123.2) 896 } 897 } 898 899 func BenchmarkZapNormal(b *testing.B) { 900 logger := zap.New(zapcore.NewCore( 901 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), 902 zapcore.AddSync(io.Discard), 903 zapcore.InfoLevel, 904 )).Sugar() 905 for i := 0; i < b.N; i++ { 906 logger.Infow(msg, "rate", "15", "low", 16, "high", 123.2) 907 } 908 } 909 910 func BenchmarkZapPrintf(b *testing.B) { 911 logger := zap.New(zapcore.NewCore( 912 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), 913 zapcore.AddSync(io.Discard), 914 zapcore.InfoLevel, 915 )).Sugar() 916 for i := 0; i < b.N; i++ { 917 logger.Infof("rate=%s low=%d high=%f msg=%s", "15", 16, 123.2, msg) 918 } 919 } 920 921 func BenchmarkZapCaller(b *testing.B) { 922 logger := zap.New(zapcore.NewCore( 923 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), 924 zapcore.AddSync(io.Discard), 925 zapcore.InfoLevel), 926 zap.AddCaller(), 927 ).Sugar() 928 for i := 0; i < b.N; i++ { 929 logger.Infow(msg, "rate", "15", "low", 16, "high", 123.2) 930 } 931 } 932 933 func BenchmarkZapInterface(b *testing.B) { 934 logger := zap.New(zapcore.NewCore( 935 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), 936 zapcore.AddSync(io.Discard), 937 zapcore.InfoLevel, 938 )).Sugar() 939 for i := 0; i < b.N; i++ { 940 logger.Infow(msg, "object", &obj) 941 } 942 } 943 944 func BenchmarkZeroLogDisable(b *testing.B) { 945 zerolog.SetGlobalLevel(zerolog.InfoLevel) 946 logger := zerolog.New(io.Discard).With().Timestamp().Logger() 947 for i := 0; i < b.N; i++ { 948 logger.Debug().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg) 949 } 950 } 951 952 func BenchmarkZeroLogNormal(b *testing.B) { 953 logger := zerolog.New(io.Discard).With().Timestamp().Logger() 954 for i := 0; i < b.N; i++ { 955 logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg) 956 } 957 } 958 959 func BenchmarkZeroLogPrintf(b *testing.B) { 960 logger := zerolog.New(io.Discard).With().Timestamp().Logger() 961 for i := 0; i < b.N; i++ { 962 logger.Info().Msgf("rate=%s low=%d high=%f msg=%s", "15", 16, 123.2, msg) 963 } 964 } 965 966 func BenchmarkZeroLogCaller(b *testing.B) { 967 logger := zerolog.New(io.Discard).With().Caller().Timestamp().Logger() 968 for i := 0; i < b.N; i++ { 969 logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg) 970 } 971 } 972 973 func BenchmarkZeroLogInterface(b *testing.B) { 974 logger := zerolog.New(io.Discard).With().Timestamp().Logger() 975 for i := 0; i < b.N; i++ { 976 logger.Info().Interface("object", &obj).Msg(msg) 977 } 978 } 979 980 func BenchmarkPhusLogDisable(b *testing.B) { 981 logger := phuslog.Logger{Level: phuslog.InfoLevel, Writer: phuslog.IOWriter{io.Discard}} 982 for i := 0; i < b.N; i++ { 983 logger.Debug().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg) 984 } 985 } 986 987 func BenchmarkPhusLogNormal(b *testing.B) { 988 logger := phuslog.Logger{Writer: phuslog.IOWriter{io.Discard}} 989 for i := 0; i < b.N; i++ { 990 logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg) 991 } 992 } 993 994 func BenchmarkPhusLogPrintf(b *testing.B) { 995 logger := phuslog.Logger{Writer: phuslog.IOWriter{io.Discard}} 996 for i := 0; i < b.N; i++ { 997 logger.Info().Msgf("rate=%s low=%d high=%f msg=%s", "15", 16, 123.2, msg) 998 } 999 } 1000 1001 func BenchmarkPhusLogCaller(b *testing.B) { 1002 logger := phuslog.Logger{Caller: 1, Writer: phuslog.IOWriter{io.Discard}} 1003 for i := 0; i < b.N; i++ { 1004 logger.Info().Str("rate", "15").Int("low", 16).Float32("high", 123.2).Msg(msg) 1005 } 1006 } 1007 1008 func BenchmarkPhusLogInterface(b *testing.B) { 1009 logger := phuslog.Logger{Writer: phuslog.IOWriter{io.Discard}} 1010 for i := 0; i < b.N; i++ { 1011 logger.Info().Interface("object", &obj).Msg(msg) 1012 } 1013 } 1014 ``` 1015 1016 </details> 1017 1018 A Performance result as below, for daily benchmark results see [github actions][benchmark] 1019 ``` 1020 goos: linux 1021 goarch: amd64 1022 cpu: AMD EPYC 7763 64-Core Processor 1023 1024 BenchmarkSlogDisable-4 1000000000 8.404 ns/op 0 B/op 0 allocs/op 1025 BenchmarkSlogNormal-4 9004674 1332 ns/op 120 B/op 3 allocs/op 1026 BenchmarkSlogPrintf-4 11932806 1011 ns/op 80 B/op 1 allocs/op 1027 BenchmarkSlogCaller-4 5491897 2184 ns/op 688 B/op 9 allocs/op 1028 BenchmarkSlogInterface-4 9200828 1299 ns/op 112 B/op 2 allocs/op 1029 1030 BenchmarkZapDisable-4 1000000000 8.099 ns/op 0 B/op 0 allocs/op 1031 BenchmarkZapNormal-4 13027324 912.5 ns/op 384 B/op 1 allocs/op 1032 BenchmarkZapPrintf-4 12789286 949.7 ns/op 80 B/op 1 allocs/op 1033 BenchmarkZapCaller-4 7093309 1681 ns/op 632 B/op 3 allocs/op 1034 BenchmarkZapInterface-4 11350916 1046 ns/op 224 B/op 2 allocs/op 1035 1036 BenchmarkZeroLogDisable-4 1000000000 9.929 ns/op 0 B/op 0 allocs/op 1037 BenchmarkZeroLogNormal-4 36481122 327.4 ns/op 0 B/op 0 allocs/op 1038 BenchmarkZeroLogPrintf-4 17880010 664.8 ns/op 80 B/op 1 allocs/op 1039 BenchmarkZeroLogCaller-4 9288362 1293 ns/op 304 B/op 4 allocs/op 1040 BenchmarkZeroLogInterface-4 19508446 614.7 ns/op 48 B/op 1 allocs/op 1041 1042 BenchmarkPhusLogDisable-4 1000000000 9.610 ns/op 0 B/op 0 allocs/op 1043 BenchmarkPhusLogNormal-4 51244849 236.8 ns/op 0 B/op 0 allocs/op 1044 BenchmarkPhusLogPrintf-4 23559231 509.0 ns/op 0 B/op 0 allocs/op 1045 BenchmarkPhusLogCaller-4 23906091 508.0 ns/op 0 B/op 0 allocs/op 1046 BenchmarkPhusLogInterface-4 21407361 558.8 ns/op 0 B/op 0 allocs/op 1047 1048 PASS 1049 ok bench 246.926s 1050 ``` 1051 1052 <details> 1053 <summary>As slog handlers, comparing with stdlib/zap/zerolog implementations</summary> 1054 1055 ```go 1056 // go test -v -cpu=1 -run=none -bench=. -benchtime=10s -benchmem bench_test.go 1057 package bench 1058 1059 import ( 1060 "io" 1061 "log/slog" 1062 "testing" 1063 1064 "github.com/phsym/zeroslog" 1065 phuslog "github.com/phuslu/log" 1066 "go.uber.org/zap" 1067 "go.uber.org/zap/exp/zapslog" 1068 "go.uber.org/zap/zapcore" 1069 ) 1070 1071 const msg = "The quick brown fox jumps over the lazy dog" 1072 1073 func BenchmarkSlogNormalStd(b *testing.B) { 1074 logger := slog.New(slog.NewJSONHandler(io.Discard, nil)) 1075 for i := 0; i < b.N; i++ { 1076 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1077 } 1078 } 1079 1080 func BenchmarkSlogGroupStd(b *testing.B) { 1081 logger := slog.New(slog.NewJSONHandler(io.Discard, nil)).With("a", 1).WithGroup("g").With("b", 2) 1082 for i := 0; i < b.N; i++ { 1083 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1084 } 1085 } 1086 1087 func BenchmarkSlogNormalZap(b *testing.B) { 1088 logcore := zapcore.NewCore( 1089 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), 1090 zapcore.AddSync(io.Discard), 1091 zapcore.InfoLevel, 1092 ) 1093 logger := slog.New(zapslog.NewHandler(logcore, nil)) 1094 for i := 0; i < b.N; i++ { 1095 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1096 } 1097 } 1098 1099 func BenchmarkSlogGroupZap(b *testing.B) { 1100 logcore := zapcore.NewCore( 1101 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), 1102 zapcore.AddSync(io.Discard), 1103 zapcore.InfoLevel, 1104 ) 1105 logger := slog.New(zapslog.NewHandler(logcore, nil)).With("a", 1).WithGroup("g").With("b", 2) 1106 for i := 0; i < b.N; i++ { 1107 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1108 } 1109 } 1110 1111 func BenchmarkSlogNormalZerolog(b *testing.B) { 1112 logger := slog.New(zeroslog.NewJsonHandler(io.Discard, &zeroslog.HandlerOptions{Level: slog.LevelInfo})) 1113 for i := 0; i < b.N; i++ { 1114 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1115 } 1116 } 1117 1118 func BenchmarkSlogGroupZerolog(b *testing.B) { 1119 logger := slog.New(zeroslog.NewJsonHandler(io.Discard, &zeroslog.HandlerOptions{Level: slog.LevelInfo})).With("a", 1).WithGroup("g").With("b", 2) 1120 for i := 0; i < b.N; i++ { 1121 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1122 } 1123 } 1124 1125 func BenchmarkSlogNormalPhuslog(b *testing.B) { 1126 logger := slog.New((&phuslog.Logger{Writer: phuslog.IOWriter{io.Discard}}).Slog().Handler()) 1127 for i := 0; i < b.N; i++ { 1128 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1129 } 1130 } 1131 1132 func BenchmarkSlogGroupPhuslog(b *testing.B) { 1133 logger := slog.New((&phuslog.Logger{Writer: phuslog.IOWriter{io.Discard}}).Slog().Handler()).With("a", 1).WithGroup("g").With("b", 2) 1134 for i := 0; i < b.N; i++ { 1135 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1136 } 1137 } 1138 1139 func BenchmarkSlogNormalPhuslogStd(b *testing.B) { 1140 logger := slog.New(phuslog.SlogNewJSONHandler(io.Discard, nil)) 1141 for i := 0; i < b.N; i++ { 1142 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1143 } 1144 } 1145 1146 func BenchmarkSlogGroupPhuslogStd(b *testing.B) { 1147 logger := slog.New(phuslog.SlogNewJSONHandler(io.Discard, nil)).With("a", 1).WithGroup("g").With("b", 2) 1148 for i := 0; i < b.N; i++ { 1149 logger.Info(msg, "rate", "15", "low", 16, "high", 123.2) 1150 } 1151 } 1152 ``` 1153 1154 </details> 1155 1156 A Performance result as below, for daily benchmark results see [github actions][benchmark] 1157 ``` 1158 goos: linux 1159 goarch: amd64 1160 cpu: AMD EPYC 7763 64-Core Processor 1161 1162 BenchmarkSlogNormalStd 4408033 1383 ns/op 120 B/op 3 allocs/op 1163 BenchmarkSlogGroupStd 4298301 1398 ns/op 120 B/op 3 allocs/op 1164 1165 BenchmarkSlogNormalZap 4753046 1273 ns/op 192 B/op 1 allocs/op 1166 BenchmarkSlogGroupZap 4724052 1257 ns/op 192 B/op 1 allocs/op 1167 1168 BenchmarkSlogNormalZerolog 7548705 789.2 ns/op 0 B/op 0 allocs/op 1169 BenchmarkSlogGroupZerolog 5592763 1076 ns/op 288 B/op 1 allocs/op 1170 1171 BenchmarkSlogNormalPhuslog 8318289 720.1 ns/op 0 B/op 0 allocs/op 1172 BenchmarkSlogGroupPhuslog 8175591 737.8 ns/op 0 B/op 0 allocs/op 1173 1174 BenchmarkSlogNormalPhuslogStd 7978171 755.0 ns/op 0 B/op 0 allocs/op 1175 BenchmarkSlogGroupPhuslogStd 7728466 775.4 ns/op 0 B/op 0 allocs/op 1176 1177 PASS 1178 ok bench 70.366s 1179 ``` 1180 1181 In summary, phuslog offers a blend of low latency, minimal memory usage, and efficient logging across various scenarios, making it an excellent option for high-performance logging in Go applications. 1182 1183 ## A Real World Example 1184 1185 The example starts a geoip http server which supports change log level dynamically 1186 ```go 1187 package main 1188 1189 import ( 1190 "encoding/json" 1191 "fmt" 1192 "net" 1193 "net/http" 1194 "os" 1195 1196 "github.com/phuslu/iploc" 1197 "github.com/phuslu/log" 1198 ) 1199 1200 type Config struct { 1201 Listen struct { 1202 Tcp string 1203 } 1204 Log struct { 1205 Level string 1206 Maxsize int64 1207 Backups int 1208 } 1209 } 1210 1211 type Handler struct { 1212 Config *Config 1213 AccessLogger log.Logger 1214 } 1215 1216 func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 1217 reqID := log.NewXID() 1218 remoteIP, _, _ := net.SplitHostPort(req.RemoteAddr) 1219 geo := iploc.Country(net.ParseIP(remoteIP)) 1220 1221 h.AccessLogger.Log(). 1222 Xid("req_id", reqID). 1223 Str("host", req.Host). 1224 Bytes("geo", geo). 1225 Str("remote_ip", remoteIP). 1226 Str("request_uri", req.RequestURI). 1227 Str("user_agent", req.UserAgent()). 1228 Str("referer", req.Referer()). 1229 Msg("access log") 1230 1231 switch req.RequestURI { 1232 case "/debug", "/info", "/warn", "/error": 1233 log.DefaultLogger.SetLevel(log.ParseLevel(req.RequestURI[1:])) 1234 default: 1235 fmt.Fprintf(rw, `{"req_id":"%s","ip":"%s","geo":"%s"}`, reqID, remoteIP, geo) 1236 } 1237 } 1238 1239 func main() { 1240 config := new(Config) 1241 err := json.Unmarshal([]byte(`{ 1242 "listen": { 1243 "tcp": ":8080" 1244 }, 1245 "log": { 1246 "level": "debug", 1247 "maxsize": 1073741824, 1248 "backups": 5 1249 } 1250 }`), config) 1251 if err != nil { 1252 log.Fatal().Msgf("json.Unmarshal error: %+v", err) 1253 } 1254 1255 handler := &Handler{ 1256 Config: config, 1257 AccessLogger: log.Logger{ 1258 Writer: &log.FileWriter{ 1259 Filename: "access.log", 1260 MaxSize: config.Log.Maxsize, 1261 MaxBackups: config.Log.Backups, 1262 LocalTime: true, 1263 }, 1264 }, 1265 } 1266 1267 if log.IsTerminal(os.Stderr.Fd()) { 1268 log.DefaultLogger = log.Logger{ 1269 Level: log.ParseLevel(config.Log.Level), 1270 Caller: 1, 1271 TimeFormat: "15:04:05", 1272 Writer: &log.ConsoleWriter{ 1273 ColorOutput: true, 1274 EndWithMessage: true, 1275 }, 1276 } 1277 handler.AccessLogger = log.DefaultLogger 1278 } else { 1279 log.DefaultLogger = log.Logger{ 1280 Level: log.ParseLevel(config.Log.Level), 1281 Writer: &log.FileWriter{ 1282 Filename: "main.log", 1283 MaxSize: config.Log.Maxsize, 1284 MaxBackups: config.Log.Backups, 1285 LocalTime: true, 1286 }, 1287 } 1288 } 1289 1290 server := &http.Server{ 1291 Addr: config.Listen.Tcp, 1292 ErrorLog: log.DefaultLogger.Std(log.ErrorLevel, nil, "", 0), 1293 Handler: handler, 1294 } 1295 1296 log.Fatal().Err(server.ListenAndServe()).Msg("listen failed") 1297 } 1298 ``` 1299 1300 ## Acknowledgment 1301 This log is heavily inspired by [zerolog][zerolog], [glog][glog], [gjson][gjson] and [lumberjack][lumberjack]. 1302 1303 [godoc-img]: http://img.shields.io/badge/godoc-reference-5272B4.svg 1304 [godoc]: https://pkg.go.dev/github.com/phuslu/log 1305 [report-img]: https://goreportcard.com/badge/github.com/phuslu/log 1306 [report]: https://goreportcard.com/report/github.com/phuslu/log 1307 [build-img]: https://github.com/phuslu/log/workflows/build/badge.svg 1308 [build]: https://github.com/phuslu/log/actions 1309 [stability-img]: https://img.shields.io/badge/stability-stable-green.svg 1310 [high-performance]: https://github.com/phuslu/log?tab=readme-ov-file#high-performance 1311 [play-simple-img]: https://img.shields.io/badge/playground-NGV25aBKmYH-29BEB0?style=flat&logo=go 1312 [play-simple]: https://go.dev/play/p/NGV25aBKmYH 1313 [play-customize-img]: https://img.shields.io/badge/playground-p9ZSSL4--IaK-29BEB0?style=flat&logo=go 1314 [play-customize]: https://go.dev/play/p/p9ZSSL4-IaK 1315 [play-multiio-img]: https://img.shields.io/badge/playground-MH--J3Je--KEq-29BEB0?style=flat&logo=go 1316 [play-multiio]: https://go.dev/play/p/MH-J3Je-KEq 1317 [play-combined-img]: https://img.shields.io/badge/playground-24d4eDIpDeR-29BEB0?style=flat&logo=go 1318 [play-combined]: https://go.dev/play/p/24d4eDIpDeR 1319 [play-file-img]: https://img.shields.io/badge/playground-tjMc97E2EpW-29BEB0?style=flat&logo=go 1320 [play-file]: https://go.dev/play/p/tjMc97E2EpW 1321 [play-pretty-img]: https://img.shields.io/badge/playground-SCcXG33esvI-29BEB0?style=flat&logo=go 1322 [play-pretty]: https://go.dev/play/p/SCcXG33esvI 1323 [pretty-img]: https://user-images.githubusercontent.com/195836/101993218-cda82380-3cf3-11eb-9aa2-b8b1c832a72e.png 1324 [play-glog-img]: https://img.shields.io/badge/playground-oxSyv3ra5W5-29BEB0?style=flat&logo=go 1325 [play-glog]: https://go.dev/play/p/oxSyv3ra5W5 1326 [play-logfmt-img]: https://img.shields.io/badge/playground-8ZsrWnsWBep-29BEB0?style=flat&logo=go 1327 [play-logfmt]: https://go.dev/play/p/8ZsrWnsWBep 1328 [play-context-img]: https://img.shields.io/badge/playground-oAVAo302faf-29BEB0?style=flat&logo=go 1329 [play-context]: https://go.dev/play/p/oAVAo302faf 1330 [play-context-add-img]: https://img.shields.io/badge/playground-LuCghJxMPHI-29BEB0?style=flat&logo=go 1331 [play-context-add]: https://go.dev/play/p/LuCghJxMPHI 1332 [play-marshal-img]: https://img.shields.io/badge/playground-SoQdwQOaQR2-29BEB0?style=flat&logo=go 1333 [play-marshal]: https://go.dev/play/p/SoQdwQOaQR2 1334 [play-stdlog]: https://go.dev/play/p/LU8vQruS7-S 1335 [play-stdlog-img]: https://img.shields.io/badge/playground-LU8vQruS7--S-29BEB0?style=flat&logo=go 1336 [play-slog]: https://go.dev/play/p/JW3Ts6FcB40 1337 [play-slog-img]: https://img.shields.io/badge/playground-JW3Ts6FcB40-29BEB0?style=flat&logo=go 1338 [benchmark]: https://github.com/phuslu/log/actions?query=workflow%3Abenchmark 1339 [zerolog]: https://github.com/rs/zerolog 1340 [glog]: https://github.com/golang/glog 1341 [gjson]: https://github.com/tidwall/gjson 1342 [lumberjack]: https://github.com/natefinch/lumberjack