zap日志Core接口与CheckedEntry
1. Core 接口zapcore/core.go:25-45type Core interface { LevelEnabler // Enabled(Level) bool —— 我关不关心这个级别 With([]Field) Core // 带上下文派生新 Core不可变 Check(Entry, *CheckedEntry) *CheckedEntry // 预检愿意写就把自己登记进 ce Write(Entry, []Field) error // 真正写出被调用即必须写不要重复判断级别 Sync() error // 刷盘 }接口契约注释原文的要点Callers must use Check before calling Write35 行——zap 包的 Logger 保证了这个顺序Write should always log; it should not replicate the logic of Check40-41 行——职责单一这个接口是整个 zap 的插件总线采样、多路、钩子、过滤、延迟……全部实现 Core。2. ioCore最小实现core.go:58-123总共 60 行type ioCore struct { LevelEnabler // 内嵌级别判断 enc Encoder // 编码器 out WriteSyncer // 输出 } func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core { return ioCore{LevelEnabler: enab, enc: enc, out: ws} }2.1 五个方法逐个看// Enabled来自内嵌的 LevelEnabler静态 Level / AtomicLevel / 任意函数 // With81-85克隆并预编码字段 func (c *ioCore) With(fields []Field) Core { clone : c.clone() addFields(clone.enc, fields) // 把字段写进新 encoder 的内部 buf return clone } func (c *ioCore) clone() *ioCore { // 116-122 return ioCore{ LevelEnabler: c.LevelEnabler, // 共享所以 AtomicLevel 动态调级对所有子 logger 生效 enc: c.enc.Clone(), // ★ 克隆编码器深拷贝上下文 buffer out: c.out, // 共享 } } // Check87-92 func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { if c.Enabled(ent.Level) { return ce.AddCore(ent, c) // 登记稍后 Write 会调到我 } return ce } // Write94-110 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 { // Fatal/Panic马上要死先刷盘 _ c.Sync() } return nil } // Sync112-114透传 out.Sync()With 的精妙字段在 With 时就被AddTo进 clone 出的 EncoderjsonEncoder 会把它们编码成字节存进自己的 buf。之后每条日志EncodeEntry直接把这段字节拷贝进输出json_encoder.go:416-419——上下文字段每条日志只花一次内存拷贝不再走编码逻辑。3. Entry 与 CheckedEntry3.1 Entry一条日志的元数据zapcore/entry.go:143-150type Entry struct { Level Level Time time.Time LoggerName string Message string Caller EntryCaller // PC/File/Line/Function/Defined Stack string // 已格式化的堆栈字符串 }注意Entry 不含结构化字段——字段是 Field 指令只在 Write 时由 Encoder 消费。3.2 CheckedEntry预检凭证 协调者zapcore/entry.go:218-225type CheckedEntry struct { Entry // 内嵌日志元数据 ErrorOutput WriteSyncer // 内部错误输出口Logger.check 注入 dirty bool // 池复用检测 after CheckWriteHook // 写完后的终止动作 cores []Core // ★ 同意写这条日志的所有 Core before []CheckPreWriteHook // 写前的变换钩子 }3.3 对象池35-53var _cePool pool.New(func() *CheckedEntry { return CheckedEntry{ cores: make([]Core, 4), // 预分 4 个槽绝大多数场景单/双 core } }) func getCheckedEntry() *CheckedEntry { ce : _cePool.Get(); ce.reset(); return ce } func putCheckedEntry(ce *CheckedEntry) { if ce ! nil { _cePool.Put(ce) } }reset()227-241很有讲究cores/before切片逐元素置 nil 再截断——既清引用帮 GC又保留容量下次免分配。3.4 AddCore / After / Beforenil 安全的链式构造func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { if ce nil { // 从 nil 开始第一个同意的 core 负责从池里取 ce getCheckedEntry() ce.Entry ent } ce.cores append(ce.cores, core) return ce } // After331-338设置写后钩子Before319-326加写前变换钩子同样 nil 安全这个nil receiver 即构造的模式让 Check 链可以写成ce coreA.Check(ent, ce)无脑串接tee 就是这么干的。3.5 Write协调的最后一棒zapcore/entry.go:246-293func (ce *CheckedEntry) Write(fields ...Field) { if ce nil { return } if ce.dirty { // ─┐ 池滥用检测 // 打 Unsafe CheckedEntry re-use 内部错误并返回 │ 同一个 ce 被写两次 return // ─┘ 用户持有 ce 跨 goroutine 复用等 } ce.dirty true ent : ce.Entry for i : range ce.before { // ① 写前变换钩子可改 Entry/Fields ent, fields ce.before[i](ent, fields) } var err error for i : range ce.cores { // ② 逐 core 写出错误累积multierr err multierr.Append(err, ce.cores[i].Write(ent, fields)) } if err ! nil ce.ErrorOutput ! nil { // ③ 写失败 → 内部错误口 fmt.Fprintf(ce.ErrorOutput, %v write error: %v\n, ce.Time, err) ce.ErrorOutput.Sync() } hook : ce.after if hook ! nil { hook.OnWrite(ce, fields) // ④ 终止动作panic/exit/goexit } putCheckedEntry(ce) // ⑤ 归还池 }⚠️ 文档注释216-217明确Write 后不得持有 ce 引用——它已回池。dirty标记是 best-effort 的误用检测不是并发安全保证。4. 装饰器五兄弟全部是包一个 Core 覆写个别方法的套路对照着读非常快4.1 teemultiCore扇出tee.go:37-46构造0 个Nop、1 个原样func (mc multiCore) Enabled(lvl) bool { 任一分支启用即 true } // 66-73 func (mc multiCore) Check(ent, ce) { for 每个分支 ce mc[i].Check(ent, ce) } // 75-80 // → 各分支的 ioCore 各自 AddCore → 一条日志被多个 core 写 func (mc multiCore) Write(ent, fields) { 逐个写错误 multierr 合并 } // 82-88 func (mc multiCore) Level() { 取所有分支最低级别 } // 56-644.2 hooked写后回调hook.go:40-46func (h *hooked) Check(ent, ce) *CheckedEntry { if downstream : h.Core.Check(ent, ce); downstream ! nil { return downstream.AddCore(ent, h) // ★ 把自己也登记进去 } return ce } func (h *hooked) Write(ent Entry, _ []Field) error { for i : range h.funcs { err multierr.Append(err, h.funcs[i](ent)) } return err // 注意不用再调内层 Core——它在 Check 时已单独登记会各自收到 Write }精妙点hooked 不包装内层的 Write而是与内层并列登记——链式 Check 的设计让叠加副作用不需要嵌套调用。这就是zap.Hooks选项的实现options.go:55-59。4.3 sampler按消息计数采样sampler.go:214-229详见 15 篇func (s *sampler) Check(ent, ce) *CheckedEntry { if !s.Enabled(ent.Level) { return ce } if ent.Level 在合法范围 { counter : s.counts.get(ent.Level, ent.Message) // fnv32a 定位计数器 n : counter.IncCheckReset(ent.Time, s.tick) // 原子计数 周期重置 if n s.first (thereafter 0 || (n-first)%thereafter ! 0) { s.hook(ent, LogDropped) return ce // ★ 不 AddCore 这条日志被丢弃 } s.hook(ent, LogSampled) } return s.Core.Check(ent, ce) }4.4 levelFilterCore只升不降increase_level.go:39-47func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error) { for l : _maxLevel; l _minLevel; l-- { if !core.Enabled(l) level.Enabled(l) { return nil, fmt.Errorf(invalid increase level...) // 试图降级 → 报错 } } return levelFilterCore{core, level}, nil } // Enabled/Check 用新的更严的 levelWrite/Sync 透传4.5 lazyWithCore延迟字段求值lazy_with.go:34-4042-46func NewLazyWith(core Core, fields []Field) Core { return lazyWithCore{core: nil, originalCore: core, fields: fields} } func (d *lazyWithCore) initOnce() { d.Do(func() { d.core d.originalCore.With(d.fields) }) } // Enabled直接问 originalCore不触发 init——级别判断很便宜别为它付初始化代价 // Check/Write/With/Sync先 initOnce() 再转发sync.Once保证第一次真正用时才做With的字段编码。5. NopCore 与 LevelOf// core.go:47-55全空转NewNop 的内核Enabled 恒 false // level.go:98-110 LevelOf(enabler) // ① 若实现 Level() LevelleveledEnabler→ 直接问 // ② 否则从 Debug 到 Fatal 逐级试 Enabled返回第一个启用的 // ③ 都不启用 → InvalidLevel FatalLevel1 // ioCore/sampler/hooked/multiCore 都实现了 Level()走①快6. 全景一条日志经过的 Core 链生产预设logger.Info(...) └─ check → core 链 sampler(取样判定) ← WrapCore 注入config.go:284-297 └─ ioCore(jsonEncoder lockedStderr, AtomicLevel) Check: Enabled? → AddCore(ioCore) └─ ce.Write ├─ ioCore.Write: EncodeEntry → stderr.Write └─ (Fatal 时) after 钩子 → os.Exit(1)