bitbucket.org/ai69/amoy@v0.2.3/logger.go (about)

     1  package amoy
     2  
     3  import (
     4  	"os"
     5  	"strings"
     6  
     7  	"github.com/1set/gut/yos"
     8  	"go.uber.org/zap"
     9  	"go.uber.org/zap/zapcore"
    10  	lj "gopkg.in/natefinch/lumberjack.v2"
    11  )
    12  
    13  // Logger is a wrapper of uber/zap logger with dynamic log level.
    14  type Logger struct {
    15  	logL     *zap.Logger
    16  	logS     *zap.SugaredLogger
    17  	minLevel *zap.AtomicLevel
    18  }
    19  
    20  // LogConfig stands for config of logging.
    21  type LogConfig struct {
    22  	ConsoleFormat string
    23  	FileFormat    string
    24  	Monochrome    bool
    25  	MaxFileSizeMB int
    26  	MaxBackups    int
    27  	CompressFile  bool
    28  }
    29  
    30  var (
    31  	getEncoderConfig = func(lvlEnc zapcore.LevelEncoder) *zapcore.EncoderConfig {
    32  		return &zapcore.EncoderConfig{
    33  			TimeKey:        "time",
    34  			LevelKey:       "level",
    35  			NameKey:        "logger",
    36  			CallerKey:      "caller",
    37  			MessageKey:     "msg",
    38  			StacktraceKey:  "stacktrace",
    39  			LineEnding:     zapcore.DefaultLineEnding,
    40  			EncodeLevel:    lvlEnc,
    41  			EncodeTime:     zapcore.ISO8601TimeEncoder,
    42  			EncodeDuration: zapcore.StringDurationEncoder,
    43  			EncodeCaller:   zapcore.ShortCallerEncoder,
    44  		}
    45  	}
    46  	// check if the level is greater than or equal to error
    47  	allLevelEnabler = zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
    48  		return true
    49  	})
    50  	// log encoders
    51  	consoleColorEncoder = zapcore.NewConsoleEncoder(*getEncoderConfig(zapcore.CapitalColorLevelEncoder))
    52  	consoleMonoEncoder  = zapcore.NewConsoleEncoder(*getEncoderConfig(zapcore.CapitalLevelEncoder))
    53  	jsonEncoder         = zapcore.NewJSONEncoder(*getEncoderConfig(zapcore.LowercaseLevelEncoder))
    54  	// std log writers
    55  	writeStdout = zapcore.AddSync(os.Stdout)
    56  	writeStderr = zapcore.AddSync(os.Stderr)
    57  )
    58  
    59  // NewLogger returns a Logger with given log path and debug mode.
    60  func NewLogger(fileName string, debug bool, cfgs ...LogConfig) *Logger {
    61  	// log enablers
    62  	minLevel := zap.NewAtomicLevelAt(zap.DebugLevel)
    63  	customLevelEnabler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
    64  		return lvl >= minLevel.Level()
    65  	})
    66  	normalLevelEnabler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
    67  		return lvl < zapcore.ErrorLevel && lvl >= minLevel.Level()
    68  	})
    69  	errorLevelEnabler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
    70  		return lvl >= zapcore.ErrorLevel && lvl >= minLevel.Level()
    71  	})
    72  
    73  	cfg := LogConfig{
    74  		ConsoleFormat: "console",
    75  		FileFormat:    "json",
    76  		Monochrome:    false,
    77  		MaxFileSizeMB: 100,
    78  		CompressFile:  true,
    79  	}
    80  	if len(cfgs) > 0 {
    81  		cfg = cfgs[0]
    82  	}
    83  
    84  	// log level encoder
    85  	consoleEncoder := consoleColorEncoder
    86  	if yos.IsOnWindows() || cfg.Monochrome {
    87  		consoleEncoder = consoleMonoEncoder
    88  	}
    89  
    90  	// combine core for logger
    91  	var cores []zapcore.Core
    92  	switch strings.ToLower(cfg.ConsoleFormat) {
    93  	case "console":
    94  		cores = []zapcore.Core{
    95  			zapcore.NewCore(consoleEncoder, writeStdout, normalLevelEnabler),
    96  			zapcore.NewCore(consoleEncoder, writeStderr, errorLevelEnabler),
    97  		}
    98  	case "json":
    99  		fallthrough
   100  	default:
   101  		cores = []zapcore.Core{
   102  			zapcore.NewCore(jsonEncoder, writeStdout, normalLevelEnabler),
   103  			zapcore.NewCore(jsonEncoder, writeStderr, errorLevelEnabler),
   104  		}
   105  	}
   106  
   107  	// set log file path as per parameters
   108  	if logPath := strings.TrimSpace(fileName); len(logPath) > 0 {
   109  		logFile := zapcore.AddSync(&lj.Logger{
   110  			Filename: logPath,
   111  			MaxSize:  cfg.MaxFileSizeMB,
   112  			Compress: cfg.CompressFile,
   113  		})
   114  
   115  		switch strings.ToLower(cfg.FileFormat) {
   116  		case "console":
   117  			cores = append(cores, zapcore.NewCore(consoleEncoder, logFile, customLevelEnabler))
   118  		case "json":
   119  			fallthrough
   120  		default:
   121  			cores = append(cores, zapcore.NewCore(jsonEncoder, logFile, customLevelEnabler))
   122  		}
   123  	}
   124  
   125  	// combine option for logger
   126  	options := []zap.Option{
   127  		zap.AddCaller(),
   128  		zap.AddStacktrace(zap.ErrorLevel),
   129  	}
   130  	// set debug mode as per parameters
   131  	if debug {
   132  		options = append(options, zap.Development())
   133  	}
   134  
   135  	// build the logger
   136  	logL := zap.New(zapcore.NewTee(cores...), options...)
   137  	return &Logger{
   138  		logL:     logL,
   139  		logS:     logL.Sugar(),
   140  		minLevel: &minLevel,
   141  	}
   142  }
   143  
   144  // Logger returns a zap logger inside the wrapper.
   145  func (l *Logger) Logger() *zap.Logger {
   146  	return l.logL
   147  }
   148  
   149  // LoggerSugared returns a sugared zap logger inside the wrapper.
   150  func (l *Logger) LoggerSugared() *zap.SugaredLogger {
   151  	return l.logS
   152  }
   153  
   154  // SetLogLevel sets the log level of loggers inside the wrapper.
   155  func (l *Logger) SetLogLevel(level string) {
   156  	if err := l.minLevel.UnmarshalText([]byte(level)); err != nil {
   157  		l.logL.DPanic("fail to set log level", zap.Error(err))
   158  	}
   159  }
   160  
   161  // GetLogLevel returns the log level of loggers inside the wrapper.
   162  func (l *Logger) GetLogLevel() zapcore.Level {
   163  	return l.minLevel.Level()
   164  }