github.com/oarkflow/log@v1.0.78/README.md (about)

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