阿小信大人的头像
Where there is a Python, there is a way. 阿小信大人

zap源码阅读笔记2020-01-16 17:49

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对象到CheckedEntrycores中,此时Entry变为带有Core对象的CheckedEntry并返回CheckedEntry,调用方必须在调用Write方法前使用Check方法
  • Write方法将日志对象EntryFileds写到具体实现的目的位置,在通过CheckedEntry中的Write方法完成日志打印时,CheckedEntry的Write方法中会遍历调用其所有的Core对象的Write方法(即当前这里的Write方法)
  • Sync方法,如果需要刷新日志缓冲区,在这儿实现

通过zapcore.NewCore可以创建一个Core对象ioCore,方法定义:func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core,需要传入EncoderWriteSyncerLevelEnabler三个对象。

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这个core对象

NewCore定义: NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core

结构体定义:

type ioCore struct { LevelEnabler enc Encoder out WriteSyncer }

它实现了Core接口的所有方法,自定义Core可以参考或者复用这里的实现, Check方法通过ioCoreLevelEnabler接口的Enabled方法判断日志Entry的Level是否使用被打印,可以打印则使用ioCore对象和Entry日志对象构成CheckedEntryWrite方法中使用ioCore中的Encoder对日志Entry和Fields先进行EncoderEncodeEntry处理,再使用WriteSyncer中的Write方法将Encode之后的内容进行写入。 With方法调用时,会调用addFieldsFields添加到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提供了consolejson两种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.CloserWriteSyncer接口的接口,

Sink也是一种WriteSyncer,它是一个包含了io.Writerio.CloserSync方法的接口,定义如下:

type Sink interface { zapcore.WriteSyncer io.Closer }

通过Sink对象的Write方法进行日志的具体输出。 在通过ConfigBuild方法创建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方法完成输出。

如果您觉得从我的分享中得到了帮助,并且希望我的博客持续发展下去,请点击支付宝捐赠,谢谢!

若非特别声明,文章均为阿小信的个人笔记,转载请注明出处。文章如有侵权内容,请联系我,我会及时删除。

#Golang#  
分享到:
阅读[668] 评论[0]

你可能也感兴趣的文章推荐

本文最近访客

网友66.*.*.77[Mountain View]2020-05-31 12:19
网友66.*.*.73[火星]2020-05-31 12:02
网友46.*.*.140[火星]2020-05-31 11:50
网友46.*.*.135[火星]2020-05-31 11:49

发表评论