目录

golang zaplog使用

zap

zap使用

uber开源,zap可以在控制台、文件甚至发送数据到其他系统中,以此来记录日志。我们可以指定日志的级别,支持json结构化,方便查询。

和logrus类似,简单来讲,日志有两个概念:字段和消息。字段用来结构化输出错误相关的上下文环境,而消息简明扼要的阐述错误本身。

开发模式下是普通文本的结构:

package main

import (
    "go.uber.org/zap"
    "time"
)

func main() {
    // zap.NewDevelopment 开发模式,格式化输出
    logger, _ := zap.NewDevelopment() 
     // zap.NewProduction 生产模式json序列化输出
    // logger, _ := zap.NewProduction()
    defer logger.Sync()
    logger.Info("无法获取网址",
        zap.String("url", "http://www.baidu.com"),
        zap.Int("attempt", 3),
        zap.Duration("backoff", time.Second),
    )
}

自定义配置:

func main() {
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "time",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,  // 小写编码器
        EncodeTime:     zapcore.ISO8601TimeEncoder,     // ISO8601 UTC 时间格式
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller:   zapcore.FullCallerEncoder,      // 全路径编码器
    }

    // 设置日志级别
    atom := zap.NewAtomicLevelAt(zap.DebugLevel)

    config := zap.Config{
        Level:            atom,                                                // 日志级别
        Development:      true,                                                // 开发模式,堆栈跟踪
        Encoding:         "json",                                              // 输出格式 console 或 json
        EncoderConfig:    encoderConfig,                                       // 编码器配置
        InitialFields:    map[string]interface{}{"serviceName": "spikeProxy"}, // 初始化字段,如:添加一个服务器名称
        OutputPaths:      []string{"stdout", "./logs/spikeProxy.log"},         // 输出到指定文件 stdout(标准输出,正常颜色) stderr(错误输出,红色)
        ErrorOutputPaths: []string{"stderr"},
    }

    // 构建日志
    logger, err := config.Build()
    if err != nil {
        panic(fmt.Sprintf("log 初始化失败: %v", err))
    }
    logger.Info("log 初始化成功")

    logger.Info("无法获取网址",
        zap.String("url", "http://www.baidu.com"),
        zap.Int("attempt", 3),
        zap.Duration("backoff", time.Second),
    )
}

lumberjack归档

日志文件越来越大,我们根据大小、日期进行归档。 zap可以写入文件,但是并没有归档的功能。借助于lumberjack第三方库,利用hook进行归档。

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "time"
    "gopkg.in/natefinch/lumberjack.v2"
    "os"
)

func main() {
    hook := lumberjack.Logger{
        Filename:   "./logs/spikeProxy1.log", // 日志文件路径
        MaxSize:    128,                      // 每个日志文件保存的最大尺寸 单位:M
        MaxBackups: 30,                       // 日志文件最多保存多少个备份
        MaxAge:     7,                        // 文件最多保存多少天
        Compress:   true,                     // 是否压缩
    }

    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "time",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "linenum",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,  // 小写编码器
        EncodeTime:     zapcore.ISO8601TimeEncoder,     // ISO8601 UTC 时间格式
        EncodeDuration: zapcore.SecondsDurationEncoder, //
        EncodeCaller:   zapcore.FullCallerEncoder,      // 全路径编码器
        EncodeName:     zapcore.FullNameEncoder,
    }

    // 设置日志级别
    atomicLevel := zap.NewAtomicLevel()
    atomicLevel.SetLevel(zap.InfoLevel)

    core := zapcore.NewCore(
        zapcore.NewJSONEncoder(encoderConfig),                                           // 编码器配置
        zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制台和文件
        atomicLevel,                                                                     // 日志级别
    )

    // 开启开发模式,堆栈跟踪
    caller := zap.AddCaller()
    // 开启文件及行号
    development := zap.Development()
    // 设置初始化字段
    filed := zap.Fields(zap.String("serviceName", "serviceName"))
    // 构造日志
    logger := zap.New(core, caller, development, filed)

    logger.Info("log 初始化成功")
    logger.Info("无法获取网址",
        zap.String("url", "http://www.baidu.com"),
        zap.Int("attempt", 3),
        zap.Duration("backoff", time.Second))
}

zap优势及原理

标准log没有日志分级。seelog可分级,支持归档,比较灵活,但是利用反射,效率低。

避免 GC: 对象复用

zap 通过 sync.Pool 提供的对象池,复用了大量可以复用的对象,避开了 gc 这个大麻烦。

内建的 Encoder: 避免反射

标准库中的 json.Marshaler 提供的是基于类型反射的拼接方式,代价是高昂的。

zap 选择了自己实现 json Encoder。 通过明确的类型调用,直接拼接字符串,最小化性能开销。

level handler

level handler 是 zap 提供的一种 level 的处理方式,通过 http 请求动态改变日志组件级别。

zap logger 提供的 utils

zap 还在 logger 这层提供了丰富的工具包,这让整个 zap 库更加的易用: grpc logger:封装 zap logger 可以直接提供给 grpc 使用,对于大多数的 Go 分布式程序,grpc 都是默认的 rpc 方案,grpc 提供了 SetLogger 的接口。 zap 提供了对这个接口的封装。 hook:作为 zap。Core 的实现,zap 提供了 hook。 使用方实现 hook 然后注册到 logger,zap在合适的时机将日志进行后续的处理,例如写 kafka,统计日志错误率 等等。 std Logger: zap 提供了将标准库提供的 logger 对象重定向到 zap logger 中的能力,也提供了封装 zap 作为标准库 logger 输出的能力。 整体上十分易用。 sublog: 通过创建 绑定了 field 的子logger,实现了更加易用的功能。