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