go.uber.org/zap

2021-12-26  本文已影响0人  JunChow520

参考资料

日志组件

日志需求

设计思路

解决方案

uber-go/zap 支持不同日志级别打印信息但不支持日志分割,日志分割官方建议使用natefinch/lumberjack 包,结合这两个库来实现日志功能。

uber-go/zap

Zap是Uber开源的Go高性能日志库,其优势在于实时写结构化日志(Structured Logging)到文件具有很好的性能。结构化日志实际上是指不直接输出日志文本,才是采用JSON或其它编码方式使日志内容结构化,方便后续分析和查找,比如采用ELK(Elasticsearch Logstatash Kibana)。

日志输出中有两种方式字段和消息,字段用来结构化输出错误相关的上下文环境,消息简明扼要的阐述错误本身。Zap跟Logrus以及标准库中的log类似都提倡采用结构化的日志格式,而不是将所有消息放到消息体中。

安装包

$ go get -uv go.uber.org/zap
$ go get -uv github.com/natefinch/lumberjack

设计结构

设计结构
核心库 描述
zapcore 定义低级接口(zap所依赖的核心接口),接口的实现和依赖分离以最大化降低代码之间的耦合度,可直接针对zapcore封装以便于二开。
zapgrpc grpc logger的封装实现,便于grpc 用户添加log。
internal/bufferpool & buffer buffer提供了append field功能,通过append将基础类型添加到buffer中,同时使用sync.Pool提供的对象池技术通过对象复用来减少内存分配。
internal/ztest & zaptest zaptest warp提供了mock接口便于测试,zap代码整体单元测试覆盖到了98.9%。
核心库 描述
zapcore Zap库的核心逻辑,包括field的管理、level的判断、encode编码日志、输出日志等。
logger Zap库的接口层,包含Log对象Level对象、Field对象、config`等基本对象
encoder JSON等编码方式的实现
utils 工具库

日志记录器

type Logger struct {
    core zapcore.Core

    development bool
    name        string
    errorOutput zapcore.WriteSyncer

    addCaller bool
    addStack  zapcore.LevelEnabler

    callerSkip int
}

Zap提供了两种类型的日志记录器:zap.Loggerzap.SugaredLogger

zap.Logger

zap.Logger 在每一微秒和每一次内存分配重要的上下文中使用,比zap.SugaredLogger更快,内存分配次数更少,但只支持强类型的结构化日志。

默认的zap.Logger日志记录器需要结构化标签,对每个标签需使用特定值类型的函数。

创建方式

func New(core zapcore.Core, options ...Option) *Logger
func NewNop() *Logger 
func NewProduction(options ...Option) (*Logger, error) 
func NewDevelopment(options ...Option) (*Logger, error)
func NewExample(options ...Option) *Logger

不同创建方式之间的区别在于记录的信息不同,例如zap.NewProduction()默认会及记录调用函数的信息以及日期时间等。

zap.Example

logger := zap.NewExample()
logger.Info("message")   // {"level":"info","msg":"message"}
logger.Debug("message") // {"level":"debug","msg":"message"}
logger.Warn("message")   // {"level":"warn","msg":"message"}
logger.Error("message") // {"level":"error","msg":"message"}
logger.Panic("message") // {"level":"panic","msg":"message"} panic: message

zap.NewProduction

logger, err := zap.NewProduction()
if err != nil {
    log.Println(err)
}
logger.Info("message")  // {"level":"info","ts":1640526778.9686759,"caller":"test/main.go:63","msg":"message"}
logger.Debug("message") // 无
logger.Warn("message")  // {"level":"warn","ts":1640526778.9686759,"caller":"test/main.go:65","msg":"message"}
logger.Error("message") // {"level":"error","ts":1640526778.9686759,"caller":"test/main.go:66","msg":"message","stacktrace":"main.main\n\tF:/Go/project/test/main.go:66\nruntime.main\n\tF:/Go/root/src/runtime/proc.go:255"}
logger.Panic("message") // {"level":"panic","ts":1640526778.9686759,"caller":"test/main.go:67","msg":"message","stacktrace":"main.main\n\tF:/Go/project/test/main.go:67\nruntime.main\n\tF:/Go/root/src/runtime/proc.go:255"}

zap.Development

logger, err := zap.NewDevelopment()
if err != nil {
    log.Println(err)
}
logger.Info("message")  // 2021-12-26T21:55:56.822+0800 INFO    test/main.go:63 message
logger.Debug("message") // 2021-12-26T21:55:56.848+0800 DEBUG   test/main.go:64 message
logger.Warn("message")  // 2021-12-26T21:55:56.848+0800 WARN    test/main.go:65 message
logger.Error("message") // 2021-12-26T21:55:56.848+0800 ERROR   test/main.go:66 message
logger.Panic("message") // 2021-12-26T21:55:56.848+0800 PANIC   test/main.go:67 message

zap.New

若需定制日志记录器则需使用zap.New()手动传递自定义的配置来创建zap.Logger实例。

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

zapcore.Core

zapcore.Core定义低级接口(zap所依赖的核心接口),接口的实现和依赖分离以最大化降低代码之间的耦合度,可直接封装以便于二开。zapcore.Core是Zap库的核心逻辑,包括field的管理、level的判断、encode编码日志、输出日志等。

zapcore.Core需要三个配置,分别是EncoderWriteSyncerLogLevel

配置项 描述
Encoder 编码器,如何写入日志。
WriteSyncer 指定日志写到哪里
LogLevel 哪种级别的日志将被写入

例如:自定义日志记录器

//获取编码器
func getEncoder() zapcore.Encoder {
    c := zap.NewProductionEncoderConfig()
    c.EncodeTime = zapcore.ISO8601TimeEncoder   //使用ISO8601时间编码器
    c.EncodeLevel = zapcore.CapitalLevelEncoder //使用大写字母记录日志级别
    e := zapcore.NewConsoleEncoder(c)           //打印更符合人类观察的方式
    return e
}

//日志保存位置
func getWriteSyncer(filename string) zapcore.WriteSyncer {
    fp, _ := os.Create(filename)
    return zapcore.AddSync(fp)
}

//自定义日志记录器
func Logger() *zap.Logger {
    filename := "./tmp/log/test.log"
    //创建日志内核实例
    encoder := getEncoder()                 //日志编码方式
    writeSyncer := getWriteSyncer(filename) //日志保存位置
    logLevel := zapcore.DebugLevel          //日志记录等级
    core := zapcore.NewCore(encoder, writeSyncer, logLevel)
    //创建日志记录器实例
    //zap.AddCaller() 添加将调用函数信息记录到日志中功能
    logger := zap.New(core, zap.AddCaller())
    //sugar := logger.Sugar()

    return logger
}

func main() {
    logger := Logger()
    logger.Info("message")
    logger.Debug("message")
    logger.Warn("message")
    logger.Error("message")
    logger.Panic("message")
}
2021-12-26T22:15:30.521+0800    INFO    test/main.go:88 message
2021-12-26T22:15:30.550+0800    DEBUG   test/main.go:89 message
2021-12-26T22:15:30.550+0800    WARN    test/main.go:90 message
2021-12-26T22:15:30.550+0800    ERROR   test/main.go:91 message
2021-12-26T22:15:30.550+0800    PANIC   test/main.go:92 message

zap.SugaredLogger

zap.SugaredLogger 适用于性能很好但不是关键的上下文中,比其它结构化日志记录包快4到10倍,支持结构化和printf风格的日志记录。zap.SugaredLogger基于printf分割的反射类型检测,提供更加简单的语法来添加混合类型的标签。

func (log *Logger) Sugar() *SugaredLogger

zap.Logger默认是不支持格式化输出的,要打印指定值必须采用采用诸如zap.String()zap.Int()等封装方法,因此代码显得很冗长。

zap.SugaredLogger

上一篇下一篇

猜你喜欢

热点阅读