关于日志:zap源码阅读1

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对象的创立有两种形式:

  1. 通过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
    })
}
  1. 建造者模式 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 buffers

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)} //默认会创立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/…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理