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