您现在的位置是:网站首页> 编程资料编程资料

Go日志框架zap增强及源码解读_Golang_

2023-05-26 413人已围观

简介 Go日志框架zap增强及源码解读_Golang_

正文

本文包括两部分,一部分是源码解读,另一部分是对zap的增强。

由于zap是一个log库,所以从两方面来深入阅读zap的源码,一个是初始化logger的流程,一个是打一条log的流程。

初始化Logger

zap的Logger是一般通过一个Config结构体初始化的,首先看下这个结构体有哪些字段

type Config struct { // 日志Level,因为可以动态更改,所以是atomic类型的,毕竟比锁的性能好 Level AtomicLevel `json:"level" yaml:"level"` // dev模式,启用后会更改在某些使用情形下的行为,后面源码解读模块会具体看到有什么作用 Development bool `json:"development" yaml:"development"` // 禁用caller,caller就是会在打的log里加一条属性,表示这条日志是在哪里打的,例如"httpd/svc.go:123" DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` // 是否要在log里加上调用栈,dev模式下只有WarnLevel模式以上有调用栈,prod模式下只有ErrorLevel以上有调用栈 DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` // 采样策略,控制打log的速率,也可以做一些其他自定义的操作,不难理解,具体看下面的SamplingConfig Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` // log格式,自带的有json和console两种格式,可以通过使用RegisterEncoder来自定义log格式 Encoding string `json:"encoding" yaml:"encoding"` // log格式具体配置,详细看下面的EncoderConfig EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` // log输出路径,看结构表示可以有多个输出路径 OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` // 内部错误输出路径,默认是stderr ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` // 每条log都会加上InitialFields里的内容,顾名思义 InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` } 
// 采样策略配置,大致的逻辑是每秒超过Thereafter个相同msg的log会执行自定义的Hook函数(第二个参数为一个标志,LogDropped),具体逻辑可以看下面的源码解析 type SamplingConfig struct { Initial int `json:"initial" yaml:"initial"` Thereafter int `json:"thereafter" yaml:"thereafter"` Hook func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"` } const ( _numLevels = _maxLevel - _minLevel + 1 _countersPerLevel = 4096 ) // 用来记录日志打了多少条 type counter struct { resetAt atomic.Int64 counter atomic.Uint64 } type counters [_numLevels][_countersPerLevel]counter // 这里可以看到sampler就是Core外面包了一层Wrapper type sampler struct { Core counts *counters tick time.Duration // 这里的tick在初始化Logger的时候已经写死了是time.Second,也就是1秒 first, thereafter uint64 hook func(Entry, SamplingDecision) } // 所有的Core.Check都会先走sampler.Check,然后再走Core.Check func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { if !s.Enabled(ent.Level) { return ce } if ent.Level >= _minLevel && ent.Level <= _maxLevel { // 根据Message获取counter,也就是这个Message打过几次日志了 counter := s.counts.get(ent.Level, ent.Message) // 打一条Message就会记录一次到counters里,不过每秒会重置一次counter,具体看IncCheckReset里的逻辑 n := counter.IncCheckReset(ent.Time, s.tick) // first表示最初的first条日志调用hook时第二个参数传LogSampled,超过first的日志,每threrafter条日志第二个参数传LogSampled,否则传LogDropped // 假设first是100,thereafter是50,表示同一个Message的log,最初的100条全都会记录,之后的log在每秒钟内,每50条记录一次 if n > s.first && (s.thereafter == 0 || (n-s.first)%s.thereafter != 0) { s.hook(ent, LogDropped) return ce } s.hook(ent, LogSampled) } return s.Core.Check(ent, ce) } // 这里可能会出现意想不到的情况 // 因为_countersPerLevel写死了是4096,那么必然会存在不同的key做完hash后取模会路由到相同的counter里 // 那么就会有概率丢弃掉没有达到丢弃阈值的log // 假设abc和def的hash值一样,first是0,thereafter是10,表示每秒钟每种log每10条才会记录1次,那么abc和def这两种log就会共享同一个counter,这就是问题所在 func (cs *counters) get(lvl Level, key string) *counter { i := lvl - _minLevel // fnv32a是一个hash函数 // _countersPerLevel固定是4096 j := fnv32a(key) % _countersPerLevel return &cs[i][j] } func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { tn := t.UnixNano() resetAfter := c.resetAt.Load() if resetAfter > tn { return c.counter.Inc() } c.counter.Store(1) newResetAfter := tn + tick.Nanoseconds() if !c.resetAt.CAS(resetAfter, newResetAfter) { return c.counter.Inc() } return 1 } 
// log格式的详细设置 type EncoderConfig struct { // 设置log内容里的一些属性的key 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"` FunctionKey string `json:"functionKey" yaml:"functionKey"` StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` // 顾名思义不解释 SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"` LineEnding string `json:"lineEnding" yaml:"lineEnding"` // Configure the primitive representations of common complex types. For // example, some users may want all time.Times serialized as floating-point // seconds since epoch, while others may prefer ISO8601 strings. // 自定义一些属性的格式,例如指定Time字段格式化为2022-05-23 16:16:16 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"` // 用于interface类型的encoder,可以自定义,默认为jsonEncoder NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"` // console格式的分隔符,默认是tab ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` } 

Config里的大部分字段都有tag,可以通过UnmarshalJson或者UnmarshalYaml来配置,可以在全局的config文件来配置,非常方便。

通过以上的config就可以初始化一个logger,下面贴代码

// 通过Config结构体Build出一个Logger func (cfg Config) Build(opts ...Option) (*Logger, error) { // 核心函数buildEncoder enc, err := cfg.buildEncoder() if err != nil { return nil, err } // 核心函数openSinks sink, errSink, err := cfg.openSinks() if err != nil { return nil, err } if cfg.Level == (AtomicLevel{}) { return nil, fmt.Errorf("missing Level") } // 核心函数New log := New( // 核心函数NewCore zapcore.NewCore(enc, sink, cfg.Level), cfg.buildOptions(errSink)..., ) if len(opts) > 0 { log = log.WithOptions(opts...) } return log, nil } 
// 核心函数buildEncoder func (cfg Config) buildEncoder() (zapcore.Encoder, error) { return newEncoder(cfg.Encoding, cfg.EncoderConfig) } // _encoderNameToConstructor是一个map[string]constructor,plugin式写法,可以通过RegisterEncoder函数注册自定义的Encoder,默认只有console和json _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 }, } 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 } // 通过name,也就是Config.Encoding来决定使用哪种encoder constructor, ok := _encoderNameToConstructor[name] if !ok { return nil, fmt.Errorf("no encoder registered for name %q", name) } return constructor(encoderConfig) } // 这里只展示jsonEncoder的逻辑,consoleEncoder和jsonEncoder差别不大 func NewJSONEncoder(cfg EncoderConfig) Encoder { return newJSONEncoder(cfg, false) } func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { if cfg.SkipLineEnding { cfg.LineEnding = "" } else if cfg.LineEnding == "" { cfg.LineEnding = DefaultLineEnding } // If no EncoderConfig.NewReflectedEncoder is provided by the user, then use default if cfg.NewReflectedEncoder == nil { cfg.NewReflectedEncoder = defaultReflectedEncoder } return &jsonEncoder{ EncoderConfig: &cfg, // 这个buf是高性能的关键之一,使用了简化的bytesBuffer和sync.Pool,代码贴在下面 buf: bufferpool.Get(), spaced: spaced, } } 
type 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)} }, }} } // 从Pool里拿一个Buffer,初始化里面的[]byte func (p Pool) Get() *Buffer { buf := p.p.Get().(*Buffer) buf.Reset() // 这里赋值pool为当前Pool,用于使用完Buffer后把Buffer后放回pool里,也就是下面的put函数 buf.pool = p return buf } func (p Pool) put(buf *Buffer) { p.p.Put(buf) } 
// 核心函数openSinks 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() 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 { // 核心函数newSink 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 } // 这里也是plugin式写法,可以通过RegisterSink来自定义sink,比如自定义一个支持http协议的sink,在文章的尾部会实现一个自定义的sink _sinkFactories = map[string]func(*url.URL) (Sink, error){ schemeFile: newFileSink, } func newSink(rawURL string) (Sink, error) { // 通过rawURL判断初始化哪种sink,实际上zap只支持file,看上面的_sinkFactories u, err := u
                
                

-六神源码网