1

本文主要研究一下golang的zap的global.go

global.go

zap@v1.16.0/global.go

var (
    _globalMu sync.RWMutex
    _globalL  = NewNop()
    _globalS  = _globalL.Sugar()
)

// L returns the global Logger, which can be reconfigured with ReplaceGlobals.
// It's safe for concurrent use.
func L() *Logger {
    _globalMu.RLock()
    l := _globalL
    _globalMu.RUnlock()
    return l
}

// S returns the global SugaredLogger, which can be reconfigured with
// ReplaceGlobals. It's safe for concurrent use.
func S() *SugaredLogger {
    _globalMu.RLock()
    s := _globalS
    _globalMu.RUnlock()
    return s
}

// ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a
// function to restore the original values. It's safe for concurrent use.
func ReplaceGlobals(logger *Logger) func() {
    _globalMu.Lock()
    prev := _globalL
    _globalL = logger
    _globalS = logger.Sugar()
    _globalMu.Unlock()
    return func() { ReplaceGlobals(prev) }
}
global定义了_globalL及_globalS,L()方法通过加读锁的方式返回_globalL,S()通过加读锁的方式返回_globalS,之所以要加读锁是因为ReplaceGlobals方法会修改_globalL及_globalS

StdLog

zap@v1.16.0/global.go

// NewStdLog returns a *log.Logger which writes to the supplied zap Logger at
// InfoLevel. To redirect the standard library's package-global logging
// functions, use RedirectStdLog instead.
func NewStdLog(l *Logger) *log.Logger {
    logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
    f := logger.Info
    return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */)
}

// NewStdLogAt returns *log.Logger which writes to supplied zap logger at
// required level.
func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) {
    logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
    logFunc, err := levelToFunc(logger, level)
    if err != nil {
        return nil, err
    }
    return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil
}

func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) {
    switch lvl {
    case DebugLevel:
        return logger.Debug, nil
    case InfoLevel:
        return logger.Info, nil
    case WarnLevel:
        return logger.Warn, nil
    case ErrorLevel:
        return logger.Error, nil
    case DPanicLevel:
        return logger.DPanic, nil
    case PanicLevel:
        return logger.Panic, nil
    case FatalLevel:
        return logger.Fatal, nil
    }
    return nil, fmt.Errorf("unrecognized level: %q", lvl)
}

type loggerWriter struct {
    logFunc func(msg string, fields ...Field)
}

func (l *loggerWriter) Write(p []byte) (int, error) {
    p = bytes.TrimSpace(p)
    l.logFunc(string(p))
    return len(p), nil
}
NewStdLog返回使用info级别的log.Logger;NewStdLogAt方法返回使用指定级别的log.Logger;levelToFunc用于返回指定level对应的zap.Logger的func;这里使用loggerWriter来包装zap.Logger到标准库的log.Logger

RedirectStdLog

// RedirectStdLog redirects output from the standard library's package-global
// logger to the supplied logger at InfoLevel. Since zap already handles caller
// annotations, timestamps, etc., it automatically disables the standard
// library's annotations and prefixing.
//
// It returns a function to restore the original prefix and flags and reset the
// standard library's output to os.Stderr.
func RedirectStdLog(l *Logger) func() {
    f, err := redirectStdLogAt(l, InfoLevel)
    if err != nil {
        // Can't get here, since passing InfoLevel to redirectStdLogAt always
        // works.
        panic(fmt.Sprintf(_programmerErrorTemplate, err))
    }
    return f
}

// RedirectStdLogAt redirects output from the standard library's package-global
// logger to the supplied logger at the specified level. Since zap already
// handles caller annotations, timestamps, etc., it automatically disables the
// standard library's annotations and prefixing.
//
// It returns a function to restore the original prefix and flags and reset the
// standard library's output to os.Stderr.
func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) {
    return redirectStdLogAt(l, level)
}

func redirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) {
    flags := log.Flags()
    prefix := log.Prefix()
    log.SetFlags(0)
    log.SetPrefix("")
    logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth))
    logFunc, err := levelToFunc(logger, level)
    if err != nil {
        return nil, err
    }
    log.SetOutput(&loggerWriter{logFunc})
    return func() {
        log.SetFlags(flags)
        log.SetPrefix(prefix)
        log.SetOutput(os.Stderr)
    }, nil
}
RedirectStdLog把标准库的log.Logger的info级别的输出重定向到zap.Logger;RedirectStdLogAt把标准库的log.Logger的指定级别的输出重定向到zap.Logger

实例

func globalDemo() {
    logger, _ := zap.NewProduction()
    defer logger.Sync() // flushes buffer, if any
    zap.ReplaceGlobals(logger)
    zap.S().Info("hello")

    stdLog := zap.NewStdLog(logger)
    stdLog.Println("this is standard logger but log with output to zap logger")

    undo := zap.RedirectStdLog(logger)
    log.Println("standard log will redirect to zap.Logger")
    undo()
    log.Println("standard log with original output")
}

输出

{"level":"info","ts":1607860586.171433,"caller":"zap/zap_demo.go:23","msg":"hello"}
{"level":"info","ts":1607860586.171505,"caller":"zap/zap_demo.go:26","msg":"this is standard logger but log with output to zap logger"}
{"level":"info","ts":1607860586.1715178,"caller":"zap/zap_demo.go:29","msg":"standard log will redirect to zap.Logger"}
2020/12/13 19:56:26 standard log with original output

小结

global.go提供了ReplaceGlobals方法用于注册全局的单例的logger;提供了NewStdLog方法用于返回标准库的log.Logger,然后使用该logger的输出都会通过zap.Logger来输出;提供了RedirectStdLog方法用于改变全局的标准库的log的输出,将其通过zap.Logger来输出,该方法返回一个func来撤销这种重定向。

doc


codecraft
11.9k 声望2k 粉丝

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下...