在许多Go语言项目中,我们需要一个好的日志记录器能够提供下面这些功能:
- 能够将事件记录到文件中,而不是应用程序控制台;
- 日志切割-能够根据文件大小、时间或间隔等来切割日志文件;
- 支持不同的日志级别。例如INFO,DEBUG,ERROR等;
- 能够打印基本信息,如调用文件/函数名和行号,日志时间等;
- 日志分级:有 Debug,Info,Warn,Error,DPanic,Panic,Fatal 等
- 日志记录结构化:日志内容记录是结构化的,比如 json 格式输出
- 自定义格式:用户可以自定义输出的日志格式
- 自定义公共字段:用户可以自定义公共字段,大家输出的日志内容就共同拥有了这些字段
- 调试:可以打印文件名、函数名、行号、日志时间等,便于调试程序
- 自定义调用栈级别:可以根据日志级别输出它的调用栈信息
- Namespace:日志命名空间。定义命名空间后,所有日志内容就在这个命名空间下。命名空间相当于一个文件夹
- 支持 hook 操作
安装
go get -u go.uber.org/zap
基本使用
package main
import "go.uber.org/zap"
var logger *zap.Logger
func main() {
logger, _ = zap.NewProduction() // 使用生产环境
// logger, _ := zap.NewDevelopment() // 使用开发环境
defer logger.Sync() // 刷新缓存
suger := logger.Sugar() // 开启日志语法糖
suger.Debug("我是Debug级别的日志")
suger.Debugw("我是Debug级别的日志", "key1", "value1", "key2", "value2")
suger.Debugf("我是Debug级别的日志%s", "value")
suger.Warn("我是Warn级别的日志")
suger.Warnw("我是Warn级别的日志", "key1", "value1", "key2", "value2")
suger.Warnf("我是Warn级别的日志%s", "value")
suger.Info("我是Info级别的日志")
suger.Infow("我是Info级别的日志", "key1", "value1", "key2", "value2")
suger.Infof("我是Info级别的日志 %s", "info")
suger.Error("我是Error级别的日志")
suger.Errorw("我是Error级别的日志", "key1", "value1", "key2", "value2")
suger.Errorf("我是Error级别的日志 %s", "value")
suger.Panic("我是Panic级别的日志")
suger.Panicw("我是Panic级别的日志", "key1", "value1", "key2", "value2")
suger.Panicf("我是Panic级别的日志 %s", "value")
// 使用不带语法糖的日志
logger.Debug("我是Debug级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Warn("我是Warn级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Info("我是Info级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Error("我是Error级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Panic("我是Panic级别的日志", zap.String("key", "value"), zap.Int("num", 10))
}
NewExample/NewDevelopment/NewProduction使用
zap 为我们提供了三种快速创建 logger 的方法: zap.NewProduction()
,zap.NewDevelopment()
,zap.NewExample()
Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中
NewExample()使用
NewExample 构建一个 logger,专门为在 zap 的测试示例使用。它将 DebugLevel 及以上日志用 JSON 格式标准输出,但它省略了时间戳和调用函数,以保持示例输出的简短和确定性
因为在这个方法里,zap 已经定义好了日志配置项部分默认值
// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127
func NewExample(options ...Option) *Logger {
encoderCfg := zapcore.EncoderConfig{
MessageKey: "msg", // 日志内容key:val, 前面的key设为msg
LevelKey: "level", // 日志级别的key设为level
NameKey: "logger", // 日志名
EncodeLevel: zapcore.LowercaseLevelEncoder, //日志级别,默认小写
EncodeTime: zapcore.ISO8601TimeEncoder, // 日志时间
EncodeDuration: zapcore.StringDurationEncoder,
}
core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel)
return New(core).WithOptions(options...)
}
使用例子
package main
import (
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
logger.Debug("this is debug message")
}
NewDevelopment()使用
NewDevelopment() 构建一个开发使用的 Logger
使用例子
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewDevelopment()
defer logger.Sync()
logger.Info("failed to fetch url",
// 强类型字段
zap.String("url", "http://example.com"),
zap.Int("attempt", 3),
zap.Duration("duration", time.Second),
)
logger.With(
// 强类型字段
zap.String("url", "http://development.com"),
zap.Int("attempt", 4),
zap.Duration("duration", time.Second*5),
).Info("[With] failed to fetch url")
}
NewProduction()使用
NewProduction() 构建了一个合理的 Prouction 日志记录器,它将 info 及以上的日志内容以 JSON 格式记写入标准错误里
使用例子
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
url := "http://zap.uber.io"
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"time", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)
// 或更简洁 Sugar() 使用
// sugar := zap.NewProduction().Sugar()
// defer sugar.Sync()
}
传入配置项
在这 3 个函数中,可以传入一些配置项。
func NewExample(options …Option) *Logger
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
file, _ := os.OpenFile("./log.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
syncFile := zapcore.AddSync(file)
syncConsole := zapcore.AddSync(os.Stderr)
sync := zapcore.NewMultiWriteSyncer(syncConsole, syncFile)
core := zapcore.NewCore(encoder, sync, zapcore.InfoLevel)
logger := zap.New(core)
logger.Info("info 日志", zap.Int("line", 1))
}
zap.Hook() 添加回调函数
Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {
fmt.Println("[zap.Hooks]test Hooks")
return nil
}))
defer logger.Sync()
logger.Info("test output")
logger.Warn("warn info")
}
自定义配置项
快速构建 logger 日志记录器最简单的方法就是用 zap 预定义了配置的方法:NewExample(), NewProduction()
和NewDevelopment()
,这 3 个方法通过单个函数调用就可以构建一个日志计记录器,也可以简单配置
Config 配置项源码
// zap v1.24.0
type Config struct {
// 动态改变日志级别,在运行时你可以安全改变日志级别
Level AtomicLevel `json:"level" yaml:"level"`
// 将日志记录器设置为开发模式,在 WarnLevel 及以上级别日志会包含堆栈跟踪信息
Development bool `json:"development" yaml:"development"`
// 在日志中停止调用函数所在文件名、行数
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// 完全禁止自动堆栈跟踪。默认情况下,在 development 中,warnlevel及以上日志级别会自动捕获堆栈跟踪信息
// 在 production 中,ErrorLevel 及以上也会自动捕获堆栈信息
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// 设置采样策略。没有 SamplingConfing 将禁止采样
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// 设置日志编码。可以设置为 console 和 json。也可以通过 RegisterEncoder 设置第三方编码格式
Encoding string `json:"encoding" yaml:"encoding"`
// 为encoder编码器设置选项。详细设置信息在 zapcore.zapcore.EncoderConfig
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// 日志输出地址可以是一个 URLs 地址或文件路径,可以设置多个
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// 错误日志输出地址。默认输出标准错误信息
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// 可以添加自定义的字段信息到 root logger 中。也就是每条日志都会携带这些字段信息,公共字段
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}
EncoderConfig 结构源码,它里面也有很多配置选项
// zapcore@v1.24.0
type EncoderConfig struct {
// 为log entry设置key。如果 key 为空,那么在日志中的这部分信息也会省略
MessageKey string `json:"messageKey" yaml:"messageKey"`//日志信息的健名,默认为msg
LevelKey string `json:"levelKey" yaml:"levelKey"`//日志级别的健名,默认为level
TimeKey string `json:"timeKey" yaml:"timeKey"`//记录日志时间的健名,默认为time
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKey string `json:"functionKey" yaml:"functionKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"`
LineEnding string `json:"lineEnding" yaml:"lineEnding"`
// 日志编码的一些设置项
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
// 与其它编码器不同, 这个编码器可选
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// 配置 interface{} 类型编码器。如果没设置,将用 json.Encoder 进行编码
NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
// 配置 console 中字段分隔符。默认使用 tab
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}
type Entry struct {
Level Level
Time time.Time
LoggerName string
Message string
Caller EntryCaller
Stack string
}zap 提供了 2 种日志记录器:`SugaredLogger` 和 `Logger`
SugaredLogger日志
在需要性能但不是很重要的情况下,使用 SugaredLogger 较合适。它比其它结构化日志包快 4-10 倍,包括 结构化日志和 printf 风格的 API
使用: suger.[日志级别]x
w 支持键值对方式传入日志
f 支持%s 方式插值
suger := logger.Sugar() // 开启日志语法糖
suger.Debug("我是Debug级别的日志")
suger.Debugw("我是Debug级别的日志", "key1", "value1", "key2", "value2")
suger.Debugf("我是Debug级别的日志%s", "value")
suger.Warn("我是Warn级别的日志")
suger.Warnw("我是Warn级别的日志", "key1", "value1", "key2", "value2")
suger.Warnf("我是Warn级别的日志%s", "value")
suger.Info("我是Info级别的日志")
suger.Infow("我是Info级别的日志", "key1", "value1", "key2", "value2")
suger.Infof("我是Info级别的日志 %s", "info")
suger.Error("我是Error级别的日志")
suger.Errorw("我是Error级别的日志", "key1", "value1", "key2", "value2")
suger.Errorf("我是Error级别的日志 %s", "value")
suger.Panic("我是Panic级别的日志")
suger.Panicw("我是Panic级别的日志", "key1", "value1", "key2", "value2")
suger.Panicf("我是Panic级别的日志 %s", "value")
logger日志
当性能和类型安全很重要时,请使用 Logger。它比 SugaredLogger 更快,分配的资源更少,但它只支持结构化日志和强类型字段
第一个参数打印日志名,后边支持传入可变参, …zapcore.Field类型
zap.String(“key”, “value”) 表示打印key为字符串 value为字符串的 map
logger.Debug("我是Debug级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Warn("我是Warn级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Info("我是Info级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Error("我是Error级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Panic("我是Panic级别的日志", zap.String("key", "value"), zap.Int("num", 10))
输出日志到文件
cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{
"./OUTPUT.log", "stderr", "stdout",
}
logger, _ = cfg.Build()
logger.Debug("打印日志到文件")
日志切割归档
lumberjack 这个库是按照日志大小切割日志文件。
go get -u github.com/natefinch/lumberjack@v2
log.SetOutput(&lumberjack.Logger{
Filename: "/var/log/myapp/foo.log", // 文件位置
MaxSize: 500, // megabytes,M 为单位,达到这个设置数后就进行日志切割
MaxBackups: 3, // 保留旧文件最大份数
MaxAge: 28, //days , 旧文件最大保存天数
Compress: true, // disabled by default,是否压缩日志归档,默认不压缩
})
参照它的文档和结合上面自定义配置
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
lumberjacklogger := &lumberjack.Logger{
Filename: "./log-rotate-test.json",
MaxSize: 1, // megabytes
MaxBackups: 3,
MaxAge: 28, //days
Compress: true, // disabled by default
}
defer lumberjacklogger.Close()
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.ISO8601TimeEncoder // 设置时间格式
fileEncoder := zapcore.NewJSONEncoder(config)
core := zapcore.NewCore(
fileEncoder, //编码设置
zapcore.AddSync(lumberjacklogger), //输出到文件
zap.InfoLevel, //日志等级
)
logger := zap.New(core)
defer logger.Sync()
// 测试分割日志
for i := 0; i < 8000; i++ {
logger.With(
zap.String("url", fmt.Sprintf("www.test%d.com", i)),
zap.String("name", "jimmmyr"),
zap.Int("age", 23),
zap.String("agradege", "no111-000222"),
).Info("test info ")
}
}
全局logger
zap提供了 2 种全局 Logger,一个是 zap.Logger,调用 zap.L() 获取;
另外一个是 zap.SugaredLogger ,调用 zap.S() 获取
直接调用 zap.L() 或 zap.S() 记录日志的话,它是不会记录任何日志信息。需要调用 ReplaceGlobals() 函数将它设置为全局 Logger。
ReplaceGlobals 替换全局 Logger 和 SugaredLogger,并返回一个函数来恢复原始值
简单使用例子
package main
import (
"go.uber.org/zap"
)
func main() {
// 直接调用是不会记录日志信息的,所以下面日志信息不会输出
zap.L().Info("no log info")
zap.S().Info("no log info [sugared]")
logger := zap.NewExample()
defer logger.Sync()
zap.ReplaceGlobals(logger) // 全局logger,zap.L() 和 zap.S() 需要调用 ReplaceGlobals 函数才会记录日志信息
zap.L().Info("log info")
zap.S().Info("log info [sugared]")
}