github:zap
Hello World
复制 package main
import (
"time"
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
defer logger.Sync() // zap底层 API 可以设置缓存,所以一般使用defer logger.Sync()将缓存同步到文件中
url := "http://example.org/api"
logger.Info("failed to fetch URL",
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
}
由于fmt.Printf
之类的方法大量使用interface{}
和反射,会有不少性能损失,并且增加了内存分配的频次。zap
为了提高性能、减少内存分配次数,没有使用反射,而且默认的Logger
只支持强类型的、结构化的日志。必须使用zap
提供的方法记录字段。
比如上述代码中的:
复制 zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
zap
为 Go 语言中所有的基本类型和其他常见类型都提供了方法。这些方法的名称也比较好记忆,zap.Type
(Type
为bool/int/uint/float64/complex64/time.Time/time.Duration/error
等)就表示该类型的字段,zap.Typep
以p
结尾表示该类型指针的字段,zap.Types
以s
结尾表示该类型切片的字段。如:
zap.Bool(key string, val bool) Field
:bool
字段
zap.Boolp(key string, val *bool) Field
:bool
指针字段
zap.Bools(key string, val []bool) Field
:bool
切片字段
其他特殊类型:
zap.Any(key string, value interface{}) Field
:任意类型的字段
zap.Binary(key string, val []byte) Field
:二进制串的字段
每个字段都使用方法包裹一层比较繁琐,所以zap提供了SugaredLogger
,使用logger.Sugar()
即可创建SugaredLogger
SugaredLogger
sugared
的含义为糖,SugaredLogger
的含义就是对Logger
包装了一层语法糖,内部提供两种后缀的方法:
如下:
复制 package main
import (
"time"
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
defer logger.Sync() // zap底层 API 可以设置缓存,所以一般使用defer logger.Sync()将缓存同步到文件中
sugarLogger := logger.Sugar()
url := "http://example.org/api"
// 字段方式输出
sugarLogger.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second)
// printf 方式输出
sugarLogger.Infof("failed to fetch URL %s, %d, %d", url, 3, time.Second)
}
输出结果:
复制 {"level":"info","msg":"failed to fetch URL","url":"http://example.org/api","attempt":3,"backoff":"1s"}
{"level":"info","msg":"failed to fetch URL http://example.org/api, 3, 1000000000"}
日志级别
zap共提供5中日志级别,分别为:
Fatal
,会打印出堆栈信息,并且调用os.Exit(1)
退出程序
复制 package main
import (
"fmt"
"go.uber.org/zap"
)
func main() {
var logger *zap.Logger
logger, _ = zap.NewProduction()
logger.Debug("i am debug") // 这行不会打印,因为NewProduction默认日志级别是 INFO
logger.Info("i am info") // INFO 级别日志,这个会正常打印
logger.Warn("i am warn") // WARN 级别日志,这个会正常打印
logger.Error("i am error") // ERROR 级别日志,这个会打印,并附带堆栈信息
logger.Fatal("i am fatal") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出
fmt.Println("can i be printed?") // 这行不会打印,因为程序已经退出
}
运行结果:
复制 {"level":"info","ts":1631939779.76289,"caller":"git-knowledge/main.go:13","msg":"i am info"}
{"level":"warn","ts":1631939779.762945,"caller":"git-knowledge/main.go:14","msg":"i am warn"}
{"level":"error","ts":1631939779.762951,"caller":"git-knowledge/main.go:15","msg":"i am error","stacktrace":"main.main\n\t/Users/yangsx/Project/git-knowledge/main.go:15\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:225"}
{"level":"fatal","ts":1631939779.7629771,"caller":"git-knowledge/main.go:16","msg":"i am fatal","stacktrace":"main.main\n\t/Users/yangsx/Project/git-knowledge/main.go:16\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:225"}
预定义配置Logger
zap提供了三个方法,用于快速创建logger
:
zap.NewExample()
:json格式,debug级别,适合用于测试代码中
zap.NewDevelopment()
:console格式,debug级别,人性化显示,用于开发环境
zap.NewProduction()
:json格式,info级别,方便解析,用于生产环境
zap.NewNop()
:不记录任何日志,直接丢弃
选项模式
NewExample()/NewDevelopment()/NewProduction()
这 3 个函数可以传入若干类型为zap.Option
的选项,从而定制Logger
的行为。
option: 输出文件名以及行号
复制 package main
import (
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction(zap.AddCaller()) // 添加caller 调用位置
defer logger.Sync()
// do something
logger.Info("hello world")
}
打印日志:
复制 {"level":"info","ts":1631952102.073369,"caller":"git-knowledge/main.go:11","msg":"hello world"}
这里,字段caller打印的调用位置是main.go:11
行,但是这行一般是打印语句执行的位置,定位到这一行通常没有什么用,需要向前查找。我们可以使用zap.AddCallerSkip(skip int)
向上跳 1 层。
项目中一般会对日志框架调用进行封装,这时候,如果打印的行数是日志的行数,总会带来一定的困惑,所以,可以设置向上跳一层,查看方法调用方的行数,这样比较有用。
option: 输出调用堆栈
复制 func f1() {
f2("hello world")
}
func f2(msg string, fields ...zap.Field) {
zap.L().Warn(msg, fields...)
}
func main() {
logger, _ := zap.NewProduction(zap.AddStacktrace(zapcore.WarnLevel)) // 输出调用堆栈
defer logger.Sync()
zap.ReplaceGlobals(logger)
f1()
}
option: 预设字段
复制 func main() {
logger := zap.NewExample(zap.Fields(
zap.Int("serverId", 90),
zap.String("serverName", "awesome web"),
))
logger.Info("hello world")
}
自定义配置Logger
通常情况下,上面的三种配置往往达不到我们的要求,这时通常自定义Logger。使用zap.Config
结构体创建一个新的Logger配置,并调用他的Build
方法,构建出一个Logger。
zap.Config
支持的字段如下:
复制 type Config struct {
// 日志级别
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"`
// 禁用caller,也就是日志打印的位置
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// 禁用堆栈信息
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// Encoding 日志打印的格式,可以是 json 或者 console
Encoding string `json:"encoding" yaml:"encoding"`
// Encoding 配置
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// 输出路径,可以是多个,可以是标准输出 stdout 或者 文件路径
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// 错误输出路径,可以是多个
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// 每条日志中都会输出这些值,也就是增加额外的字段
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}
EncoderConfig
的具体信息:
复制 type EncoderConfig struct {
// 日志中信息的键名,默认为msg
MessageKey string `json:"messageKey" yaml:"messageKey"`
// 日志中级别的键名,默认为level
LevelKey string `json:"levelKey" yaml:"levelKey"`
// 日志时间的键名,默认为 ts
TimeKey string `json:"timeKey" yaml:"timeKey"`
// 日志名称的键名,默认为 name
NameKey string `json:"nameKey" yaml:"nameKey"`
// 日志调用位置的键名, 默认为 caller
CallerKey string `json:"callerKey" yaml:"callerKey"`
// 堆栈信息的键名, 默认为 stacktrace
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
// 换行符格式,默认是 \n
LineEnding string `json:"lineEnding" yaml:"lineEnding"`
// 日志中级别的格式,默认为小写,如debug/info。 LevelEncoder 是一个闭包函数,以下都是,需要传入相应的实现,来完成encode的操作
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"`
}
此外,针对各种Encoder,zap提供了一些内置的实现:
复制 LevelEncoder:
- zapcore.LowercaseLevelEncoder // 小写
- zapcore.LowercaseColorLevelEncoder // 小写有颜色
- zapcore.CapitalLevelEncoder // 大写
- zapcore.CapitalColorLevelEncoder 大写有颜色
TimeEncoder:
- zapcore.EpochTimeEncoder // unix 时间戳秒
- zapcore.EpochMillisTimeEncoder // unix时间戳毫秒
- zapcore.EpochNanosTimeEncoder // 纳秒
- zapcore.ISO8601TimeEncoder // ISO8601格式显示时间
- zapcore.RFC3339TimeEncoder // RFC3339格式显示时间
- RFC3339NanoTimeEncoder
DurationEncoder:
- SecondsDurationEncoder // 秒数转换
- NanosDurationEncoder
- MillisDurationEncoder
- StringDurationEncoder
CallerEncoder:
- FullCallerEncoder // /full/path/to/package/file:line
- ShortCallerEncoder // package/file:line
NameEncoder:
- FullNameEncoder
全局Logger
为了方便在项目中使用,zap提供了两个全局Logger,分别是
全局的*zap.Logger
对象,可以通过zap.L()
获取
全局的*zap.SugaredLogger
对象,可以通过zap.S()
获取
注意,这两个Logger默认不记录任何日志,因为他们都是调用zap.NewNop()
方法生成的,要想使用全局Logger,需要使用zap.ReplaceGlobals(logger)
函数替换全局Logger:
复制 package main
import (
"go.uber.org/zap"
)
func main() {
zap.L().Info("global Logger before")
zap.S().Info("global SugaredLogger before")
logger := zap.NewExample()
defer logger.Sync()
zap.ReplaceGlobals(logger)
zap.L().Info("global Logger after")
zap.S().Info("global SugaredLogger after")
}
输出:
复制 {"level":"info","msg":"global Logger after"}
{"level":"info","msg":"global SugaredLogger after"}
思考:这里可以使用全局Logger来封装项目的统一Logger。建立一个package,存放全局Logger配置,然后暴露统一的日志打印方法,既可以与日志框架分离,又可以集中控制日志配置