go.uber.org/zap

参考资料

日志组件

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

日志需求

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

设计思路

  • 输入数据应该被良好的组织且易于编码,同时需要具有高效的空间利用率。无论是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封装以便于二开。
zapgrpcgrpc logger的封装实现,便于grpc 用户添加log。
internal/bufferpool & bufferbuffer提供了append field功能,通过append将基础类型添加到buffer中,同时使用sync.Pool提供的对象池技术通过对象复用来减少内存分配。
internal/ztest & zaptestzaptest warp提供了mock接口便于测试,zap代码整体单元测试覆盖到了98.9%。
核心库描述
zapcoreZap库的核心逻辑,包括field的管理、level的判断、encode编码日志、输出日志等。
loggerZap库的接口层,包含Log对象Level对象、Field对象、config`等基本对象
encoderJSON等编码方式的实现
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值