zap——从示例开始看源码

zap是uber开发的一个基于go语言的log模块,主打就是快、结构化、日志级别。

使用

官方示例

1.追求性能,类型要求不严格的使用SugaredLogger

logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
  // Structured context as loosely typed key-value pairs.
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

2.性能与类型安全都重要的使用Logger,Logger甚至比SugaredLogger更快,但只支持结构化日志

logger, _ := zap.NewProduction()
defer logger.Sync()
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),
)

从以上示例中我们大致了解了zap使用涉及的过程,具体如下:

logger的创建

Logger

Logger的结构如下,核心为core。根据描述,其所有方法为协程安全的,因此并发时可以放心使用。

// A Logger provides fast, leveled, structured logging. All methods are safe
// for concurrent use.
//
// The Logger is designed for contexts in which every microsecond and every
// allocation matters, so its API intentionally favors performance and type
// safety over brevity. For most applications, the SugaredLogger strikes a
// better balance between performance and ergonomics.
type Logger struct {
    core zapcore.Core

    development bool
    name        string
    errorOutput zapcore.WriteSyncer

    addCaller bool
    addStack  zapcore.LevelEnabler

    callerSkip int
}

logger的创建通过New实现,如未设定core,则使用时不会进行任何日志的保存等操作。正常情况下,我们无需通过此方法来创建实例,通过config来创建即可。此New一般仅作为自定义时使用,后续研究自定义时会说明。

// New constructs a new Logger from the provided zapcore.Core and Options. If
// the passed zapcore.Core is nil, it falls back to using a no-op
// implementation.
//
// This is the most flexible way to construct a Logger, but also the most
// verbose. For typical use cases, the highly-opinionated presets
// (NewProduction, NewDevelopment, and NewExample) or the Config struct are
// more convenient.
//
// For sample code, see the package-level AdvancedConfiguration example.
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...)
}

通过config build logger

以NewProduction为例说明:

// NewProduction builds a sensible production Logger that writes InfoLevel and
// above logs to standard error as JSON.
//
// It's a shortcut for NewProductionConfig().Build(...Option).
func NewProduction(options ...Option) (*Logger, error) {
    return NewProductionConfig().Build(options...)
}

内建的NewProductionConfig如下,默认级别为Info,默认编码为json,输出及错误路径均为stderr等,我们可以发现,Config可以自定义的。后续我们学习下如何自定义Config。

// NewProductionConfig is a reasonable production logging configuration.
// Logging is enabled at InfoLevel and above.
//
// It uses a JSON encoder, writes to standard error, and enables sampling.
// Stacktraces are automatically included on logs of ErrorLevel and above.
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"},
    }
}

Build通过对config内的参数解析、封装,最后调用New方法来创建Logger。

// Build constructs a logger from the Config and Options.
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
    }

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

core

NewCore获得的是ioCore,具体信息的Write及Sync均在此中实现。

// NewCore creates a Core that writes logs to a WriteSyncer.
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
    return &ioCore{
        LevelEnabler: enab,
        enc:          enc,
        out:          ws,
    }
}

write log

以Info为例

func (log *Logger) Info(msg string, fields ...Field) {
    if ce := log.check(InfoLevel, msg); ce != nil {
        ce.Write(fields...)
    }
}

日志级别

注意: ErrorLevel之上级别会造成程序的panic(DPanicLevel/PanicLevel)或者退出(FatalLevel),所以使用时一定要根据自身的需求合理使用,避免影响正常的业务逻辑。

// A Level is a logging priority. Higher levels are more important.
type Level int8

const (
    // DebugLevel logs are typically voluminous, and are usually disabled in
    // production.
    DebugLevel Level = iota - 1
    // InfoLevel is the default logging priority.
    InfoLevel
    // WarnLevel logs are more important than Info, but don't need individual
    // human review.
    WarnLevel
    // ErrorLevel logs are high-priority. If an application is running smoothly,
    // it shouldn't generate any error-level logs.
    ErrorLevel
    // DPanicLevel logs are particularly important errors. In development the
    // logger panics after writing the message.
    DPanicLevel
    // PanicLevel logs a message, then panics.
    PanicLevel
    // FatalLevel logs a message, then calls os.Exit(1).
    FatalLevel

    _minLevel = DebugLevel
    _
    maxLevel = FatalLevel
)

日志级别检查

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

    // 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:       time.Now(),
        Level:      lvl,
        Message:    msg,
    }
    //此处检查日志级别,决定是否log
    ce := log.core.Check(ent, nil)
    willWrite := ce != nil

    // Set up any required terminal behavior.
    switch ent.Level {
    case zapcore.PanicLevel:
        ce = ce.Should(ent, zapcore.WriteThenPanic)
    case zapcore.FatalLevel:
        ce = ce.Should(ent, zapcore.WriteThenFatal)
    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
    }

    // Thread the error output through to the CheckedEntry.
    ce.ErrorOutput = log.errorOutput
    if log.addCaller {
        ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(log.callerSkip + callerSkipOffset))
        if !ce.Entry.Caller.Defined {
            fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC())
            log.errorOutput.Sync()
        }
    }
    if log.addStack.Enabled(ce.Entry.Level) {
        ce.Entry.Stack = Stack("").String
    }

    return ce
}
type ioCore struct {
    LevelEnabler
    enc Encoder
    out WriteSyncer
}

func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry {
    if c.Enabled(ent.Level) {
        return ce.AddCore(ent, c)
    }
    return ce
}

// Enabled returns true if the given level is at or above this level.
// Enabled是判断日志级别是否log的具体实现,和我们理解的一致,高于(包含)设定的日志级别即可log
func (l Level) Enabled(lvl Level) bool {
    return lvl >= l
}

// LevelEnabler decides whether a given logging level is enabled when logging a
// message.
//
// Enablers are intended to be used to implement deterministic filters;
// concerns like sampling are better implemented as a Core.
//
// Each concrete Level value implements a static LevelEnabler which returns
// true for itself and all higher logging levels. For example WarnLevel.Enabled()
// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
// FatalLevel, but return false for InfoLevel and DebugLevel.
type LevelEnabler interface {
    Enabled(Level) bool
}


日志信息的写入

// Write writes the entry to the stored Cores, returns any errors, and returns
// the CheckedEntry reference to a pool for immediate re-use. Finally, it
// executes any required CheckWriteAction.
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", time.Now(), 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", time.Now(), err)
            ce.ErrorOutput.Sync()
        }
    }

    should, msg := ce.should, ce.Message
    putCheckedEntry(ce)

    switch should {
    case WriteThenPanic:
        panic(msg)//此处对应DPanicLevel,PanicLevel
    case WriteThenFatal:
        exit.Exit()//此处对应FatalLevel
    }
}
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
    }
    //由于ErrorLevel级别之上的日志,会发生panic或者退出,因此退出前需要调用Sync
    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
}

Sync

sync是最终是调用系统层的Sync方法将缓存中的信息(如果缓存中有信息的话),刷新至文件中,避免信息丢失。Sync一定要在退出前使用

注意:如对同一文件持续写入日志,只需要退出前调用Sync即可。不建议每次log是都使用Sync,正常情况下,Write会将buffer内的信息写入到文件中,再次使用Sync可能会增加文件的读写次数,导致性能下降。

// Sync calls the underlying Core's Sync method, flushing any buffered log
// entries. Applications should take care to call Sync before exiting.
// 应用在退出前注意调用Sync。
func (log *Logger) Sync() error {
    return log.core.Sync()
}

总结

本篇只是对常用的几个方法进行初步的追踪,对部分问题做了些初步说明,并对使用提出一些建议,从而对使用增加了解,后续篇章会详细分析具体的过程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章