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()
}

總結

本篇只是對常用的幾個方法進行初步的追蹤,對部分問題做了些初步說明,並對使用提出一些建議,從而對使用增加了解,後續篇章會詳細分析具體的過程。

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