一、缘由

最近在尝试用golang写简单的项目,但是之前一直忽略了一个重要的问题:==golang的日志包常用的是哪个?==

网上找了一些资料也问了一些目前做golang开发的朋友,目前使用最多的就是uber开源的一个日志模块:https://github.com/uber-go/zap start数量已经高达7k多,这篇文章主要就是整理如何使用这个包

二、安装

go get go.uber.org/zap

三、基础知识

关于Logger

zap 包中有两种类型的logger:

SugaredLogger:在性能要求不是很严格的情况下,可以使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并支持结构化和printf样式的日志记录。

Logger:如果对性能要求非常高的话,可以用Logger,它比SugaredLogger更快,更节省资源

提示:默认情况下Logger是无缓冲的。 但是,由于zap的低级API允许缓冲,所以最好在退出进程之前调用Sync

内置的三种构建Logger的方法:NewExample,NewProduction和NewDevelopment。 通过这三种方法我们可以很方便的就构建一个logger,如下面例子:

    logger,err := zap.NewProduction()
    if err != nil {
        log.Fatalf("can't initialize zap logger:%v\n",err)
    }
    defer logger.Sync()
    logger.Info("hello golang")

Config

Config 的结构体如下:

type Config struct {
    // Level is the minimum enabled logging level. Note that this is a dynamic
    // level, so calling Config.Level.SetLevel will atomically change the log
    // level of all loggers descended from this config.
    Level AtomicLevel `json:"level" yaml:"level"`
    // Development puts the logger in development mode, which changes the
    // behavior of DPanicLevel and takes stacktraces more liberally.
    Development bool `json:"development" yaml:"development"`
    // DisableCaller stops annotating logs with the calling function's file
    // name and line number. By default, all logs are annotated.
    DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // DisableStacktrace completely disables automatic stacktrace capturing. By
    // default, stacktraces are captured for WarnLevel and above logs in
    // development and ErrorLevel and above in production.
    DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
    Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // Encoding sets the logger's encoding. Valid values are "json" and
    // "console", as well as any third-party encodings registered via
    // RegisterEncoder.
    Encoding string `json:"encoding" yaml:"encoding"`
    // EncoderConfig sets options for the chosen encoder. See
    // zapcore.EncoderConfig for details.
    EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // OutputPaths is a list of URLs or file paths to write logging output to.
    // See Open for details.
    OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // ErrorOutputPaths is a list of URLs to write internal logger errors to.
    // The default is standard error.
    //
    // Note that this setting only affects internal errors; for sample code that
    // sends error-level logs to a different location from info- and debug-level
    // logs, see the package-level AdvancedConfiguration example.
    ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // InitialFields is a collection of fields to add to the root logger.
    InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

Config 提供了非常方便的方式让我们构造一个Logger,关于该结构体的说明:

Level: 这是一个AtomicLevel类型的数据,AtomicLevel 是原子可动态更改日志级别,这里需要注意:必须使用NewAtomicLevel构造函数创建AtomicLevels才能分配其内部原子指针。常用的方法有: NewAtomicLevel 和 NewAtomicLevelAt , NewAtomicLevel创建一个启用了InfoLevel及更高级别日志记录的AtomicLevel。NewAtomicLevelAt 是可以创建一个我们指定的日志级别。

Development: 用于设置logger是否是开发模式

DisableCaller:是否显示调用函数的文件名和行号。 默认情况下是显示的,即默认值是false

DisableStacktrace: 是否禁用堆栈跟踪捕获,默认情况下,将为development中的Warn Level,和更高的日志以及production中的Error Level捕获堆栈信息

Encoding: 设置logger的编码,这里有可以设置的参数有:“json” 和 “console” 以及通过RegisterEncoder注册的任何第三方编码。

EncoderConfig: 在下面会详细整理,主要是用于更灵活的做一些自定义配置

OutputPaths: 是一个要写日志的urls或者文件路径的列表

ErrorOutputPaths: 和OutputPaths类似,不过只影响内部错误

InitialFields: 初始字典设置,可以为日志输出的内容中添加一些初始字段

关于Config配置的一个简单例子:

atom := zap.NewAtomicLevelAt(zap.DebugLevel)
config := zap.Config{
    Level:atom,
    Development:false,
    DisableCaller:true,
    Encoding: "json",
    EncoderConfig: encoderConfig,
    InitialFields:map[string]interface{}{"service":"BlogService"},
    OutputPaths:[]string{"stdout","./logs/blog.log"},
    ErrorOutputPaths: []string{"stdout","./logs/blog.log"},
}

EncoderConfig

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"`
}

通过 EncoderConfig 我们可以更灵活的做一些自定义配置,可以设置日志输出是对应字段的名字以及格式,下面对一些重要字段的进行说明:

LineEnding: 默认的参数是DefaultLineEnding 也就是”\n”,即默认以换行符为一行日志的结束

EncodeLevel: 通常会设置为: LowercaseLevelEncoder ,将Level序列化为小写字符串。 例如,InfoLevel被序列化为”info”。

EncodeTime: 设置时间格式,通常设置为zapcore.ISO8601TimeEncoder

EncodeCaller: 设置caller的显示的内容,有两个参数可以设置:ShortCallerEncoder和 FullCallerEncoder,FullCallerEncoder显示完成的调用路径,ShortCallerEncoder显示的是包级别调用路径

一个使用例子如下:

encoderConfig := zapcore.EncoderConfig{
    TimeKey:"Time",
    LevelKey:"Level",
    NameKey: "Logger",
    CallerKey: "Caller",
    MessageKey:"Msg",
    LineEnding: zapcore.DefaultLineEnding,
    EncodeLevel: zapcore.LowercaseLevelEncoder,
    EncodeTime: zapcore.ISO8601TimeEncoder,
    EncodeDuration: zapcore.SecondsDurationEncoder,
    EncodeCaller: zapcore.FullCallerEncoder,
}

完整例子

package main

import (
    "fmt"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func main() {
      encoderConfig := zapcore.EncoderConfig{
        TimeKey:"Time",
        LevelKey:"Level",
        NameKey: "Logger",
        CallerKey: "Caller",
        MessageKey:"Msg",
        LineEnding: zapcore.DefaultLineEnding,
        EncodeLevel: zapcore.LowercaseLevelEncoder,
        EncodeTime: zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller: zapcore.FullCallerEncoder,
    }
    atom := zap.NewAtomicLevelAt(zap.DebugLevel)
    config := zap.Config{
        Level:atom,
        Development:false,
        DisableCaller:false,
        Encoding: "json",
        EncoderConfig: encoderConfig,
        InitialFields:map[string]interface{}{"service":"BlogService"},
        OutputPaths:[]string{"stdout","./logs/blog.log"},
        ErrorOutputPaths: []string{"stdout","./logs/blog.log"},
    }
    logger, err := config.Build()
    if err != nil {
        panic(fmt.Sprintf("logger init failed,error:%v",err))
    }
    logger.Info("logger init success")
    logger.Error("connect db failed",
        zap.String("dbAddress","127.0.0.1"),
        zap.Int("dbPort",10021,
    ))

}

四、日志切割

gopkg.in/natefinch/lumberjack.v2 可以对zap写日志进行更好的控制,包括了日志的切割,历史日志的压缩,保留日期等

下面是一个完整的和zap包结合使用的例子:

package main

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

func main() {
    hook := lumberjack.Logger{
        Filename:"./logs/test.log",  //日志文件路径
        MaxSize: 20,// 每个日志的大小,单位是M
        MaxAge: 7, // 文件被保存的天数
        Compress:true, // 是否压缩
        MaxBackups:10, // 保存多少个文件备份
    }
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:"Time",
        LevelKey:"Level",
        NameKey: "Logger",
        CallerKey: "Caller",
        MessageKey:"Msg",
        LineEnding: zapcore.DefaultLineEnding,
        EncodeLevel: zapcore.LowercaseLevelEncoder,
        EncodeTime: zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller: zapcore.ShortCallerEncoder,
    }
    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("service","blog"))
    logger := zap.New(core,caller, development,filed)
    logger.Info("logger init success")
    logger.Info("connect db success")
    logger.Error("connect redis error")


}

五、相关链接

https://godoc.org/go.uber.org/zap

https://godoc.org/go.uber.org/zap/zapcore

https://github.com/natefinch/lumberjack/tree/v2.1