Zap 源码阅读笔记

内容目录表

zap.Logger

日志Logger结构体,以此调用打印日志内容,定义如下:

type Logger struct {
	core zapcore.Core

	development bool
	name        string
	errorOutput zapcore.WriteSyncer

	addCaller bool
	addStack  zapcore.LevelEnabler

	callerSkip int
}

zapcore.Entry

日志主体内容结构体,表示一条具体日志,定义如下:

type Entry struct {
	Level      Level
	Time       time.Time
	LoggerName string
	Message    string
	Caller     EntryCaller
	Stack      string
}

zapcore.CheckedEntry

经过检查之后的日志内容结构体,cores带有具体的打印方式,定义如下:

type CheckedEntry struct {
	Entry
	ErrorOutput WriteSyncer
	dirty       bool // best-effort detection of pool misuse
	should      CheckWriteAction
	cores       []Core
}

CheckedEntry嵌套了Entry,并保存core对象

CheckedEntry的Write方法:(ce *CheckedEntry) Write(fields ...Field)

Write方法中的fields是zap的Field对象参数,一起添加打印到结构化的日志中

zapcore.Core

Core定义了一个Logger实现日志具体要如何打印的一系列接口,其中的LevelEnabler也是一个接口,定义如下:

type Core interface {
	LevelEnabler

	With([]Field) Core
	Check(Entry, *CheckedEntry) *CheckedEntry
	Write(Entry, []Field) error
	Sync() error
}


type LevelEnabler interface {
	Enabled(Level) bool
}

LevelEnabler 接口提供用于判断给定的日志级别是否应该打印该条日志的逻辑判断方法;

With 方法添加结构化的Field内容到Encoder对象中;

Check 方法通过LevelEnabler接口中的 Enabled 方法和其他逻辑判断传入的 Entry 日志对象否应该被打印,

如果应该则添加 Core 对象到 CheckedEntry 的 cores 中,此时Entry 变为带有 Core 对象的 CheckedEntry 并返回 CheckedEntry,调用方必须在调用 Write 方法前使用 Check 方法。

Write 方法将日志对象 Entry 和 Fileds 写到具体实现的目的位置,在通过 CheckedEntry 中的 Write 方法完成日志打印时,CheckedEntry 的 Write 方法中会遍历调用其所有的 Core 对象的 Write 方法(即当前这里的 Write 方法)

Sync 方法,如果需要刷新日志缓冲区,在这儿实现。

通过zapcore.NewCore 可以创建一个Core对象 ioCore,方法定义:

func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core`

需要传入 Encoder 和 WriteSyncer 和 LevelEnabler 三个对象。

在 zapcore 中提供了 NewTee(cores ...Core) Core 方法可以把多个 Core 对象合并为一个 Core,即将传入的 Core 对象添加到 slice 中,在为该 slice 对象实现 Core 接口的方法,合并后的 Core 对象在执行接口方法时,会遍历所有 core 对象的对应方法调用。

zap.Hooks 对 core 对象注册回调函数,通过函数

RegisterHooks(core Core, hooks ...func(Entry) error) Core

注册 hook 方法来生成新的带有 hooks 方法的 hookedCore 对象并在其 Check 方法中先对原始的 core 进行 Check,然后添加到 CheckedEntry 的 cores 列表,再将自己添加cores列表中;

日志写入时除了原始的core的Write方法调用外,还会对 hooked core 的 Write 方法会调用所有传入的 hooks 回调函数。

zap.WrapCore 方法可以对 Core 做一次包装,并替换成 WrapCore 传入的方法所生成的 Core 对象。

zapcore.ioCore

zapcore 提供了 NewCore 方法来创建 core 对象,创建 logger 的时候大部分是通过该方法来生成 core 对象,zapcore 实现了 ioCore 这个对象。

NewCore 定义:

NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core

结构体定义:

type ioCore struct {
	LevelEnabler
	enc Encoder
	out WriteSyncer
}

它实现了Core接口的所有方法,自定义Core可以参考或者复用这里的实现;

Check 方法通过 ioCore 中 LevelEnabler 接口的 Enabled 方法判断日志 Entry 的 Level 是否使用被打印,可以打印则使用 ioCore 对象和 Entry 日志对象构成 CheckedEntry。

Write 方法中使用 ioCore 中的 Encoder 对日志 Entry 和 Fields 先进行 Encoder 的 EncodeEntry 处理,再使用 WriteSyncer 中的 Write 方法将 Encode 之后的内容进行写入。

With 方法调用时,会调用 addFields 将 Fields 添加到 Encoder,对传入的 Fields 进行遍历调用 Field 的 AddTo 方法,将传入的 Field 全部添加到 Encoder 的 buffer 中。

zapcore.Field 定义

type Field struct {
	Key       string
	Type      FieldType
	Integer   int64
	String    string
	Interface interface{}
}

addFields方法定义:

addFields(enc ObjectEncoder, fields []Field)

Encoder

Encoder是一个数据编码接口,定义如下:

type Encoder interface {
	ObjectEncoder

	Clone() Encoder
	EncodeEntry(Entry, []Field) (*buffer.Buffer, error)
}

每一个具体的 Encoder 实现都实现了 ObjectEncoder 接口中的一系列根据具体数据类型进行 Add 或者 Append 的方法;

根据 field 的具体数据类型构造一个 buffer 储存对应的数据格式,例如 json_encoder 在 EncodeEntry 方法中构造出对应的 json 格式保存在 buffer 中返回出来。

zap 提供了 console 和 json 两种 Encoder,可以通过函数

RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error

注册自己的 Encoder 构造函数,该函数通过 EncoderConfig 参数生成对应的 Encoder。

EncoderConfig 结构体定义:

type EncoderConfig struct {
	// Set the keys used for each log entry. If any key is empty, that portion
	// of the entry is omitted.
	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"`
	// Configure the primitive representations of common complex types. For
	// example, some users may want all time.Times serialized as floating-point
	// seconds since epoch, while others may prefer ISO8601 strings.
	EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
	EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
	EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
	EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
	// Unlike the other primitive type encoders, EncodeName is optional. The
	// zero value falls back to FullNameEncoder.
	EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
}

WriteSyncer

WriteSyncer 是一个带有 Sync 方法的 io.Writer,定义如下:

type WriteSyncer interface {
	io.Writer
	Sync() error
}

这里的 Sink 是一个包含了 io.Closer 和 WriteSyncer 接口的接口,

即 Sink 也是一种 WriteSyncer,它是一个包含了 io.Writer、io.Closer 和 Sync 方法的接口,定义如下:

type Sink interface {
	zapcore.WriteSyncer
	io.Closer
}

通过 Sink 对象的 Write 方法进行日志的具体输出。

在通过 Config 的 Build 方法创建 Logger 的时候, Build 方法通过 buildEncoder 使用 Config 中的 Encoding 名称获取到对应 Encoder 的构造函数,然后通过构造函数和配置项生成具体的 Encoder。

同时会通过 openSinks 方法生成 WriteSyncer ,其中根据配置对象中的 OutputPaths 参数通过 Open 方法根据给定的输出位置生成各种对应的 Sink 对象,用于日志的输出实现。

Open 方法会遍历所有给定的输出路径,根据每个路径使用 newSink 创建出对应的 WriteSyncer 即 sink 对象,然后通过 CombineWriteSyncers 方法将所有 sink 对象合并为一个 sink 对象,即全部添加到 slice 中,该 slice 实现了 WriteSyncer 接口, Write 方法遍历调用全部 WriteSyncer 的 Write 方法写入日志。

newSink 方法在处理输出路径时的规则是按 URL 来解析,将解析出的 scheme 作为 key(xxx://yyy中xxx就是对应的scheme)从一个map对象中取出对应的sink对象的factory方法,然后使用该工厂方法生成对应的sink。 如果输出路径没有解析出url的scheme,则表示file类型的sink,file类型的sink是zap默认实现的sink对象,其中为判断文件路径,如果是特定的stdoutstderr则直接输出到终端而非文本文件。 如果需要自定义sink,可以使用RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error 方法注册自己实现的sink工厂方法,就可以通过OutputPaths中的特定scheme来获取对应的sink然后将日志输出到对应的位置。

日志打印流程

Logger结构中包含了zapcore的Core接口对象,打印日志就是通过实现了Core接口的该对象完成的。

zap通过创建Logger结构体来打印日志,创建新Logger提供了以下方法:

  • New(core zapcore.Core, options ...Option) *Logger New方法通过传入的Core接口对象和Options构造Logger
  • NewNop() *Logger NewNop返回一个不会真正打印日志的logger
  • NewProduction(options ...Option) (*Logger, error) NewProduction返回一个日志级别为Info,以json形式输出日志到stderr的Logger
  • NewDevelopment(options ...Option) (*Logger, error) NewDevelopment返回一个日志级别为Level,以json形式输出日志到stderr的Logger
  • NewExample(options ...Option) *Logger NewExample返回用于测试的Logger,缺少了一些字段并且以debug级别输出到stdout
  • (cfg Config) Build(opts ...Option) (*Logger, error) 通过配置信息结构体的Build方法生成Logger

然后通过NewCore方法使用Encoder和WriteSyncer生成Core对象构造具体Logger

按日志级别提供了以下打印日志的方法:

  • (log *Logger) Debug(msg string, fields ...Field)
  • (log *Logger) Info(msg string, fields ...Field)
  • (log *Logger) Warn(msg string, fields ...Field)
  • (log *Logger) Error(msg string, fields ...Field)
  • (log *Logger) DPanic(msg string, fields ...Field)
  • (log *Logger) Panic(msg string, fields ...Field)
  • (log *Logger) Fatal(msg string, fields ...Field)

每个打印方法都会调用logger.go中的check方法完成打印日志。

打印日志方法中的check方法:(log *Logger) check(lvl zapcore.Level, msg string)

打印日志执行流程:通过调用Logger具体的打印日志方法每打一条日志都会调用check方法,check方法通过传入的lvlmsg参数构造zapcore.Entry结构体,Entry即为要打印的日志信息对象, 然后通过Core接口对象的Check方法对该Entry进行判断是否应该打印这条日志并把Core对象添加到CheckedEntry中并返回, 再通过返回的CheckedEntry的Write方法遍历调用CheckedEntry中所有添加的Core对象的Write方法写入msgFields完成实际的日志输出。 而Core对象的Write方法实际是调用的WriteSyncer/Sink对象中的Write方法完成输出。


也可以看看


全国大流量卡免费领

19元月租ㆍ超值优惠ㆍ长期套餐ㆍ免费包邮ㆍ官方正品