package logger import ( "os" "strings" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) // Options control zap sinks. File uses lumberjack rotation; Console is for local debug. type Options struct { Level string Encoding string // json | console (stdout only; files are always JSON) Console bool File FileOptions } // FileOptions is lumberjack rotation for a JSON log file. type FileOptions struct { Path string MaxSizeMB int MaxBackups int MaxAgeDays int Compress bool } type closer struct { log *zap.Logger lj *lumberjack.Logger } func (c *closer) Close() error { if c == nil { return nil } if c.log != nil { _ = c.log.Sync() } if c.lj != nil { return c.lj.Close() } return nil } // New builds a zap logger. Close the returned func on shutdown to flush and close the file sink. func New(o *Options) (*zap.Logger, func() error) { if o == nil { o = &Options{} } applyDefaults(o) level := parseLevel(o.Level) var lj *lumberjack.Logger cores := make([]zapcore.Core, 0, 2) if o.Console { cores = append(cores, zapcore.NewCore(stdoutEncoder(o.Encoding), zapcore.AddSync(os.Stdout), level)) } if o.File.Path != "" { lj = newLumberjack(o.File) cores = append(cores, zapcore.NewCore(jsonEncoder(), zapcore.AddSync(lj), level)) } if len(cores) == 0 { cores = append(cores, zapcore.NewCore(jsonEncoder(), zapcore.AddSync(os.Stdout), level)) } log := zap.New(zapcore.NewTee(cores...), zap.AddCaller()) c := &closer{log: log, lj: lj} return log, c.Close } func applyDefaults(o *Options) { if o.Level == "" { o.Level = "info" } if o.Encoding == "" { if strings.EqualFold(o.Level, "debug") { o.Encoding = "console" } else { o.Encoding = "json" } } if !o.Console && o.File.Path == "" { o.Console = true } if o.File.Path != "" { if o.File.MaxSizeMB <= 0 { o.File.MaxSizeMB = 100 } if o.File.MaxBackups <= 0 { o.File.MaxBackups = 7 } if o.File.MaxAgeDays <= 0 { o.File.MaxAgeDays = 7 } } } func parseLevel(s string) zapcore.Level { switch strings.ToLower(s) { case "debug": return zapcore.DebugLevel case "warn", "warning": return zapcore.WarnLevel case "error": return zapcore.ErrorLevel default: return zapcore.InfoLevel } } func jsonEncoder() zapcore.Encoder { cfg := zap.NewProductionEncoderConfig() cfg.TimeKey = "ts" cfg.EncodeTime = zapcore.ISO8601TimeEncoder cfg.MessageKey = "msg" return zapcore.NewJSONEncoder(cfg) } func stdoutEncoder(encoding string) zapcore.Encoder { if strings.EqualFold(encoding, "json") { return jsonEncoder() } cfg := zap.NewDevelopmentEncoderConfig() cfg.EncodeTime = zapcore.ISO8601TimeEncoder return zapcore.NewConsoleEncoder(cfg) } func newLumberjack(f FileOptions) *lumberjack.Logger { return &lumberjack.Logger{ Filename: f.Path, MaxSize: f.MaxSizeMB, MaxBackups: f.MaxBackups, MaxAge: f.MaxAgeDays, Compress: f.Compress, LocalTime: true, } }