zap是 Uber 开发的一个高性能、强类型、分 level 的 go 语言日志库
1.对象创立
1.1 Logger 构造体
type Logger struct { core zapcore.Core // 外围接口 development bool addCaller bool onFatal zapcore.CheckWriteAction // default is WriteThenFatal name string errorOutput zapcore.WriteSyncer addStack zapcore.LevelEnabler callerSkip int clock Clock}
Logger对象的创立有两种形式:
- 通过New 间接创立
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, clock: _systemClock, } return log.WithOptions(options...)}
函数WithOptions应用的是函数式抉择模式, 和ants源码浏览(https://segmentfault.com/a/11... 中应用的办法统一。
func (log *Logger) WithOptions(opts ...Option) *Logger { c := log.clone() for _, opt := range opts { opt.apply(c) } return c}
Option 构造
type Option interface { apply(*Logger)}// optionFunc wraps a func so it satisfies the Option interface.type optionFunc func(*Logger)func (f optionFunc) apply(log *Logger) { f(log)}func WrapCore(f func(zapcore.Core) zapcore.Core) Option { return optionFunc(func(log *Logger) { log.core = f(log.core) })}func AddStacktrace(lvl zapcore.LevelEnabler) Option { return optionFunc(func(log *Logger) { log.addStack = lvl })}
- 建造者模式 Build 创立
建造者模式应用场景: 当一个类的结构函数参数个数超过4个,而且这些参数有些是可选的参数,思考应用建造者模式。
func (cfg Config) Build(opts ...Option) (*Logger, error) { enc, err := cfg.buildEncoder() // 创立Encoder对象, json or console 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)..., ) // 创立log对象 if len(opts) > 0 { log = log.WithOptions(opts...) // 依据传入的配置重置log属性 } return log, nil}
1.2 Config构造体解析
type Config struct { Level AtomicLevel `json:"level" yaml:"level"` //Level是用来配置日志级别的,即日志的最低输入级别 Development bool `json:"development" yaml:"development"` // 这个字段的含意是用来标记是否为开发者模式,在开发者模式下,日志输入的一些行为会和生产环境上不同。 DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` // 用来标记是否开启行号和文件名显示性能。 DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` //标记是否开启调用栈追踪能力,即在打印异样日志时,是否打印调用栈。 Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` // Sampling实现了日志的流控性能,或者叫采样配置,次要有两个配置参数,Initial和Thereafter,实现的成果是在1s的工夫单位内,如果某个日志级别下同样内容的日志输入数量超过了Initial的数量,那么超过之后,每隔Thereafter的数量,才会再输入一次。是一个对日志输入的爱护性能。 Encoding string `json:"encoding" yaml:"encoding"` // json or console EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` // 日志输入门路 ErrorOutputPaths []string `json:"errorOutputPaths" // 谬误日志输入门路 yaml:"errorOutputPaths"` InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` // 初始化Fields}
EncoderConfig 构造
type EncoderConfig struct { MessageKey string `json:"messageKey" yaml:"messageKey"` LevelKey string `json:"levelKey" yaml:"levelKey"` TimeKey string `json:"timeKey" yaml:"timeKey"` NameKey string `json:"nameKey" yaml:"nameKey"` CallerKey string `json:"callerKey" yaml:"callerKey"` StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` LineEnding string `json:"lineEnding" yaml:"lineEnding"` // 配置行分隔符 EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`}
1.3 详解Build
1.3.1 buildEncoder
buildEncoder --> newEncoder --> zapcore.Encoder 接口
newEncoder
func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { if encoderConfig.TimeKey != "" && encoderConfig.EncodeTime == nil { return nil, fmt.Errorf("missing EncodeTime in EncoderConfig") } _encoderMutex.RLock() defer _encoderMutex.RUnlock() //应用读写锁 if name == "" { return nil, errNoEncoderNameSpecified } constructor, ok := _encoderNameToConstructor[name] // 返回Encoder对象 if !ok { return nil, fmt.Errorf("no encoder registered for name %q", name) } return constructor(encoderConfig)}
map中存储函数对象
_encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){ "console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { return zapcore.NewConsoleEncoder(encoderConfig), nil }, "json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { return zapcore.NewJSONEncoder(encoderConfig), nil }, }
NewConsoleEncoder 创立的consoleEncoder构造体的底层构造是jsonEncoder, 只是 consoleEncoder 的spaced 为true, NewJSONEncoder的spaced 为false.
jsonEncoder 构造体:
type jsonEncoder struct { *EncoderConfig buf *buffer.Buffer spaced bool // include spaces after colons and commas openNamespaces int // for encoding generic values by reflection reflectBuf *buffer.Buffer reflectEnc *json.Encoder}func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { return &jsonEncoder{ EncoderConfig: &cfg, buf: bufferpool.Get(), spaced: spaced, }}
Buffer 构造体
const _size = 1024 // by default, create 1 KiB bufferstype Buffer struct { bs []byte pool Pool}type Pool struct { p *sync.Pool}func NewPool() Pool { return Pool{p: &sync.Pool{ New: func() interface{} { return &Buffer{bs: make([]byte, 0, _size)} //默认会创立1k的buffer }, }}}
1.3.2 openSinks
创立output, 和errorput 文件
调用程序: Open-->open--> newSink
func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { sink, closeOut, err := Open(cfg.OutputPaths...) if err != nil { return nil, nil, err } errSink, _, err := Open(cfg.ErrorOutputPaths...) if err != nil { closeOut() // 下文中的close在这里被调用 return nil, nil, err } return sink, errSink, nil}func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { writers, close, err := open(paths) if err != nil { return nil, nil, err } writer := CombineWriteSyncers(writers...) return writer, close, nil}func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { writers := make([]zapcore.WriteSyncer, 0, len(paths)) closers := make([]io.Closer, 0, len(paths)) close := func() { // 返回一个回调函数,应用奇妙 for _, c := range closers { c.Close() } } var openErr error for _, path := range paths { sink, err := newSink(path) if err != nil { openErr = multierr.Append(openErr, fmt.Errorf("couldn't open sink %q: %v", path, err)) continue } writers = append(writers, sink) // 追加多个写入的门路 closers = append(closers, sink) // 追加多个写入的门路用来敞开文件 } if openErr != nil { close() return writers, nil, openErr } return writers, close, nil}func newSink(rawURL string) (Sink, error) { u, err := url.Parse(rawURL) // 因为门路还能够是http的url,失常的path 赋值到u.Path 上。 if err != nil { return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err) } if u.Scheme == "" { u.Scheme = schemeFile } _sinkMutex.RLock() factory, ok := _sinkFactories[u.Scheme] _sinkMutex.RUnlock() if !ok { return nil, &errSinkNotFound{u.Scheme} } return factory(u)}
_sinkFactories 是个map构造,存储的是回调函数。
_sinkFactories = map[string]func(*url.URL) (Sink, error){ schemeFile: newFileSink, } func newFileSink(u *url.URL) (Sink, error) { if u.User != nil { return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) } if u.Fragment != "" { return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u) } if u.RawQuery != "" { return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u) } // Error messages are better if we check hostname and port separately. if u.Port() != "" { return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u) } if hn := u.Hostname(); hn != "" && hn != "localhost" { return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) } fmt.Println("u.Path: ", u.Path) switch u.Path { case "stdout": return nopCloserSink{os.Stdout}, nil // stdout case "stderr": return nopCloserSink{os.Stderr}, nil } // 带有具体的输入文件名 return os.OpenFile(u.Path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)}
2.如何应用
以 Info 为例:
func (log *Logger) Info(msg string, fields ...Field) { if ce := log.check(InfoLevel, msg); ce != nil { ce.Write(fields...) }}
check 函数
func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { // check must always be called directly by a method in the Logger interface // (e.g., Check, Info, Fatal). const callerSkipOffset = 2 // Check the level first to reduce the cost of disabled log calls. // Since Panic and higher may exit, we skip the optimization for those levels. if lvl < zapcore.DPanicLevel && !log.core.Enabled(lvl) { return nil } // Create basic checked entry thru the core; this will be non-nil if the // log message will actually be written somewhere. ent := zapcore.Entry{ LoggerName: log.name, Time: log.clock.Now(), Level: lvl, Message: msg, } ce := log.core.Check(ent, nil) // 满足达到日志等级才新建CheckedEntry. 该checkedEntry 应用sync.Pool 创立。 willWrite := ce != nil // Set up any required terminal behavior. switch ent.Level { case zapcore.PanicLevel: ce = ce.Should(ent, zapcore.WriteThenPanic) case zapcore.FatalLevel: onFatal := log.onFatal // Noop is the default value for CheckWriteAction, and it leads to // continued execution after a Fatal which is unexpected. if onFatal == zapcore.WriteThenNoop { onFatal = zapcore.WriteThenFatal } ce = ce.Should(ent, onFatal) case zapcore.DPanicLevel: if log.development { ce = ce.Should(ent, zapcore.WriteThenPanic) } } // Only do further annotation if we're going to write this message; checked // entries that exist only for terminal behavior don't benefit from // annotation. if !willWrite { return ce } // 其余省略}
Write 函数
func (ce *CheckedEntry) Write(fields ...Field) { if ce == nil { return } if ce.dirty { if ce.ErrorOutput != nil { // Make a best effort to detect unsafe re-use of this CheckedEntry. // If the entry is dirty, log an internal error; because the // CheckedEntry is being used after it was returned to the pool, // the message may be an amalgamation from multiple call sites. fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", ce.Time, ce.Entry) ce.ErrorOutput.Sync() } return } ce.dirty = true var err error for i := range ce.cores { err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields)) // 外围,写入格式化数据 } if ce.ErrorOutput != nil { if err != nil { fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", ce.Time, err) ce.ErrorOutput.Sync() } } should, msg := ce.should, ce.Message putCheckedEntry(ce) switch should { case WriteThenPanic: panic(msg) case WriteThenFatal: exit.Exit() case WriteThenGoexit: runtime.Goexit() }}
外围写数据 writeContext
func (c *ioCore) Write(ent Entry, fields []Field) error { buf, err := c.enc.EncodeEntry(ent, fields) // 把数据写入缓存 if err != nil { return err } _, err = c.out.Write(buf.Bytes()) // 写入数据到文件 buf.Free() if err != nil { return err } if ent.Level > ErrorLevel { // Since we may be crashing the program, sync the output. Ignore Sync // errors, pending a clean solution to issue #370. c.Sync() } return nil}func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) { context := c.jsonEncoder.Clone().(*jsonEncoder) defer func() { // putJSONEncoder assumes the buffer is still used, but we write out the buffer so // we can free it. context.buf.Free() putJSONEncoder(context) }() addFields(context, extra) // 把 Fields 写入到buffer中 context.closeOpenNamespaces() if context.buf.Len() == 0 { return } c.addSeparatorIfNecessary(line) line.AppendByte('{') line.Write(context.buf.Bytes()) line.AppendByte('}')}
参考: 1. https://zhuanlan.zhihu.com/p/...