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