聊聊golang的zap的NewProduction

671 阅读2分钟

文主要研究一下uber的zap的NewProduction。

golang的log有zaplogrus等,不过logrus现在已经处于维护状态。

实例

SugaredLogger

func main() {
	logger, _ := zap.NewProduction()
	defer logger.Sync() // flushes buffer, if any
	sugar := logger.Sugar()
	url := "https://abc.com"
	sugar.Infow("failed to fetch URL",
		// Structured context as loosely typed key-value pairs.
		"url", url,
		"attempt", 3,
		"backoff", time.Second,
	)
}

SugaredLogger比普通的结构化logging包快4-10倍

输出

{"level":"info","ts":1607095287.638658,"caller":"log-demo/zap_demo.go:14","msg":"failed to fetch URL","url":"https://abc.com","attempt":3,"backoff":1}

Logger

func main() {
	logger, _ := zap.NewProduction()
	defer logger.Sync()
	url := "https://abc.com"
	logger.Info("failed to fetch URL",
		// Structured context as strongly typed Field values.
		zap.String("url", url),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second),
	)
}

当对性能和类型安全都要求比较高的场景就使用Logger,它比SugaredLogger速度更快,但是它只支持结构化logging

输出

{"level":"info","ts":1607095675.5623732,"caller":"log-demo/zap_demo.go:25","msg":"failed to fetch URL","url":"https://abc.com","attempt":3,"backoff":1}

NewProduction

zap@v1.16.0/logger.go

func NewProduction(options ...Option) (*Logger, error) {
	return NewProductionConfig().Build(options...)
}

NewProduction内部执行NewProductionConfig().Build方法

NewProductionConfig

zap@v1.16.0/config.go

func NewProductionConfig() Config {
	return Config{
		Level:       NewAtomicLevelAt(InfoLevel),
		Development: false,
		Sampling: &SamplingConfig{
			Initial:    100,
			Thereafter: 100,
		},
		Encoding:         "json",
		EncoderConfig:    NewProductionEncoderConfig(),
		OutputPaths:      []string{"stderr"},
		ErrorOutputPaths: []string{"stderr"},
	}
}

NewProductionConfig方法按默认参数创建了Config,其中encoding为json,output为std

NewProductionEncoderConfig

zap@v1.16.0/config.go

func NewProductionEncoderConfig() zapcore.EncoderConfig {
	return zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.EpochTimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}
}

NewProductionEncoderConfig这里创建了zapcore.EncoderConfig

Build

zap@v1.16.0/config.go

func (cfg Config) Build(opts ...Option) (*Logger, error) {
	enc, err := cfg.buildEncoder()
	if err != nil {
		return nil, err
	}

	sink, errSink, err := cfg.openSinks()
	if err != nil {
		return nil, err
	}

	if cfg.Level == (AtomicLevel{}) {
		return nil, fmt.Errorf("missing Level")
	}

	log := New(
		zapcore.NewCore(enc, sink, cfg.Level),
		cfg.buildOptions(errSink)...,
	)
	if len(opts) > 0 {
		log = log.WithOptions(opts...)
	}
	return log, nil
}

Build方法执行cfg.buildEncoder()、cfg.openSinks()、zapcore.NewCore及New方法

New

zap@v1.16.0/logger.go

func New(core zapcore.Core, options ...Option) *Logger {
	if core == nil {
		return NewNop()
	}
	log := &Logger{
		core:        core,
		errorOutput: zapcore.Lock(os.Stderr),
		addStack:    zapcore.FatalLevel + 1,
	}
	return log.WithOptions(options...)
}

func NewNop() *Logger {
	return &Logger{
		core:        zapcore.NewNopCore(),
		errorOutput: zapcore.AddSync(ioutil.Discard),
		addStack:    zapcore.FatalLevel + 1,
	}
}

这里对于core为nil的返回的是NewNop,否则实例化Logger;NewNop返回的logger的core为zapcore.NewNopCore()

Logger

zap@v1.16.0/logger.go

type Logger struct {
	core zapcore.Core

	development bool
	name        string
	errorOutput zapcore.WriteSyncer

	addCaller bool
	addStack  zapcore.LevelEnabler

	callerSkip int
	onFatal    zapcore.CheckWriteAction // default is WriteThenFatal
}

Logger定义了core、development、name、errorOutput、addCaller、addStack、callerSkip、onFatal属性

小结

zap.NewProduction()通过创建NewProductionEncoderConfig再Build出Logger,其中Logger的New方法主要设置了core、errorOutput、addStack属性

doc