美文网首页
go.uber.org/zap

go.uber.org/zap

作者: JunChow520 | 来源:发表于2021-12-26 22:30 被阅读0次

    参考资料

    日志组件

    • 日志组件对于计算资源的消耗十分巨大,会导致整个服务成本居高不下。
    • 日志作为代码行为的记录,是程序执行逻辑和异常最直接的反馈。
    • 日志中同样蕴含了大量有价值可以被挖掘的信息

    日志需求

    • 日志记录:将事件记录到文件而非控制台
    • 日志切割:日志能按文件大小、时间、间隔等切割
    • 日志级别:支持不同日志级别
    • 日志答应:能打印基本信息,如调用文件、函数名称、行号、时间等

    设计思路

    • 输入数据应该被良好的组织且易于编码,同时需要具有高效的空间利用率。无论是formator方式还是key-value方式,本质上都是对输入数据的组织形式。格式化数据往往有利于后续分析和处理,比如JSON就是一种易用的日志格式。
    • 日志能够具有不同级别,基本的日志级别种必不可少的如debug/info/warning/error/fatal。除了针对每条日志的级别,还需要日志组件的级别,用来屏蔽某些级别的日志。
    • 组织日志的输出流,首先需要一个高效地编码器来格式化后,再将经过过滤的日志输出。
    • 设计一套简单易用的接口,需要包含logger对象和config。

    解决方案

    • uber-go/zap 高性能但不支持日志切割
    • natefinch/lumberjack 单独实现日志切割

    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 工具库

    日志记录器

    • Zap的基本用法是首先创建一个全局的Logger日志记录器实例,然后在需要使用的位置作为参数传入。
    • Zap自身提供了全局的Logger是zap.S()zap.L()
    • 也可采用zap.ReplaceGlobals()将全局的Logger替换为客制化的Logger。
    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.NewExample()zap.NewProduction()使用JSON格式输出
    • zap.Development()使用行的形式输出

    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"}
    
    • 不记录调用级别消息
    • 始终会将调用者添加到日志文件中
    • 以时间戳格式打印日期
    • 以小写形式打印级别名称
    • ERROR/DPANIC级别的记录会在堆栈中跟踪文件,WARN则不会。

    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
    
    • 从警告级别向上打印到堆栈中来追踪
    • 始终打印包/文件/行(方法)
    • 行尾会添加额外字段作为JSON字符串
    • 以大写形式打印级别名称
    • 以毫秒位单位打印ISO8601格式的时间戳

    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 哪种级别的日志将被写入

    例如:自定义日志记录器

    • 使用ISO8601时间编码器
    • 使用大写字母记录日志级别
    • 采用单行打印而非JSON
    • 采用文件存储而非屏幕输出
    //获取编码器
    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

    相关文章

      网友评论

          本文标题:go.uber.org/zap

          本文链接:https://www.haomeiwen.com/subject/vblsqrtx.html