uber-go/zap 是 Go 中的快速、结构化、分级日志记录方案。
介绍
- 性能优异,比标准库更快,相关数据参考
- 支持结构化日志记录和 printf 风格的日志
- Zap 提供了两种类型的日志记录器
Sugared Logger
要求性能很好但不是很关键的上下文中使用,比其他结构化日志记录包快 4-10 倍,支持结构化日志记录和 printf 风格的日志
Logger
在每一微秒和每一次内存分配都很重要的上下文中,只支持强类型的结构化日志记录
使用
基本使用
示例一:
package main
import (
"net/http"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var logger *zap.Logger
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test.log", // 日志文件的位置
MaxSize: 10, // 在进行切割之前,日志文件的最大大小(以MB为单位)
MaxBackups: 5, // 保留旧文件的最大个数
MaxAge: 30, // 保留旧文件的最大天数
Compress: false, // 是否压缩/归档旧文件
}
return zapcore.AddSync(lumberJackLogger)
}
func InitLogger() {
core := zapcore.NewCore(getEncoder(), getLogWriter(), zapcore.DebugLevel)
logger = zap.New(core, zap.AddCaller())
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error(
"Error fetching url..",
zap.String("url", url),
zap.Error(err))
} else {
logger.Info("Success..",
zap.String("statusCode", resp.Status), // zapcore.Field 本质是一组键值对参数
zap.String("url", url))
resp.Body.Close()
}
}
func main() {
InitLogger()
defer logger.Sync()
simpleHttpGet("www.xiexianbin.cn")
simpleHttpGet("https://www.xiexianbin.cn")
}
示例二:
package main
import (
"net/http"
"go.uber.org/zap"
)
var logger *zap.Logger
var sugarLogger *zap.SugaredLogger
func InitLogger() {
logger, _ = zap.NewProduction()
sugarLogger = logger.Sugar().With(zap.String("type", "sugar"))
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error(
"Error fetching url..",
zap.String("url", url),
zap.Error(err))
sugarLogger.With(zap.String("url", url), zap.Error(err)).
Error("Error fetching url..")
} else {
logger.Info("Success..",
zap.String("statusCode", resp.Status), // zapcore.Field 本质是一组键值对参数
zap.String("url", url))
sugarLogger.With(zap.String("statusCode", resp.Status), zap.String("url", url)).
Info("Success..")
resp.Body.Close()
}
}
func main() {
InitLogger()
defer logger.Sync()
simpleHttpGet("www.xiexianbin.cn")
simpleHttpGet("https://www.xiexianbin.cn")
}
输出:
{"level":"error","ts":1734856179.3081212,"caller":"zap/demo1.go:20","msg":"Error fetching url..","url":"www.xiexianbin.cn","error":"Get \"www.xiexianbin.cn\": unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/xiexianbin/workspace/code/github.com/xiexianbin/note/static/code/golang/zap/demo1.go:20\nmain.main\n\t/Users/xiexianbin/workspace/code/github.com/xiexianbin/note/static/code/golang/zap/demo1.go:39\nruntime.main\n\t/usr/local/Cellar/go/1.23.2/libexec/src/runtime/proc.go:272"}
{"level":"error","ts":1734856179.308209,"caller":"zap/demo1.go:25","msg":"Error fetching url..","type":"sugar","url":"www.xiexianbin.cn","error":"Get \"www.xiexianbin.cn\": unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/xiexianbin/workspace/code/github.com/xiexianbin/note/static/code/golang/zap/demo1.go:25\nmain.main\n\t/Users/xiexianbin/workspace/code/github.com/xiexianbin/note/static/code/golang/zap/demo1.go:39\nruntime.main\n\t/usr/local/Cellar/go/1.23.2/libexec/src/runtime/proc.go:272"}
{"level":"info","ts":1734856179.342197,"caller":"zap/demo1.go:27","msg":"Success..","statusCode":"200 OK","url":"https://www.xiexianbin.cn"}
{"level":"info","ts":1734856179.3422499,"caller":"zap/demo1.go:31","msg":"Success..","type":"sugar","statusCode":"200 OK","url":"https://www.xiexianbin.cn"}
定制日志级别、输出文件、打印代码的位置和格式
package main
import (
"net/http"
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var logger *zap.Logger
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
func InitLogger() {
logger, _ = zap.NewProduction()
encoder := getEncoder()
// test.log记录全量日志
logF, _ := os.Create("./test.log")
// 写到多个位置
// file, _ := os.Create("./test.log")
// logF := io.MultiWriter(file, os.Stdout)
c1 := zapcore.NewCore(encoder, zapcore.AddSync(logF), zapcore.DebugLevel)
// test.err.log记录ERROR级别的日志
errF, _ := os.Create("./test.err.log")
c2 := zapcore.NewCore(encoder, zapcore.AddSync(errF), zap.ErrorLevel)
// 使用NewTee将c1和c2合并到core
core := zapcore.NewTee(c1, c2)
logger = zap.New(core, zap.AddCaller())
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error(
"Error fetching url..",
zap.String("url", url),
zap.Error(err))
} else {
logger.Info("Success..",
zap.String("statusCode", resp.Status), // zapcore.Field 本质是一组键值对参数
zap.String("url", url))
resp.Body.Close()
}
}
func main() {
InitLogger()
defer logger.Sync()
simpleHttpGet("www.xiexianbin.cn")
simpleHttpGet("https://www.xiexianbin.cn")
}
输出:
$ cat test.log
2024-12-22T16:33:12.346+0800 ERROR zap/demo2.go:38 Error fetching url.. {"url": "www.xiexianbin.cn", "error": "Get \"www.xiexianbin.cn\": unsupported protocol scheme \"\""}
2024-12-22T16:33:12.381+0800 INFO zap/demo2.go:43 Success.. {"statusCode": "200 OK", "url": "https://www.xiexianbin.cn"}
$ cat test.err.log
2024-12-22T16:33:12.346+0800 ERROR zap/demo2.go:38 Error fetching url.. {"url": "www.xiexianbin.cn", "error": "Get \"www.xiexianbin.cn\": unsupported protocol scheme \"\""}
说明:
- 封装 zap 日志后,可以使用
zap.AddCallerSkip(1)
获取准确的代码位置
Lumberjack 分割日志文件
package main
import (
"net/http"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var logger *zap.Logger
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test.log", // 日志文件的位置
MaxSize: 10, // 在进行切割之前,日志文件的最大大小(以MB为单位)
MaxBackups: 5, // 保留旧文件的最大个数
MaxAge: 30, // 保留旧文件的最大天数
Compress: false, // 是否压缩/归档旧文件
}
return zapcore.AddSync(lumberJackLogger)
}
func InitLogger() {
core := zapcore.NewCore(getEncoder(), getLogWriter(), zapcore.DebugLevel)
logger = zap.New(core, zap.AddCaller())
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error(
"Error fetching url..",
zap.String("url", url),
zap.Error(err))
} else {
logger.Info("Success..",
zap.String("statusCode", resp.Status), // zapcore.Field 本质是一组键值对参数
zap.String("url", url))
resp.Body.Close()
}
}
func main() {
InitLogger()
defer logger.Sync()
simpleHttpGet("www.xiexianbin.cn")
simpleHttpGet("https://www.xiexianbin.cn")
}
配置
zap.NewExample()
- 构建一个测试使用的 Logger,DebugLevel 及以上日志,JSON 格式
// https://github.com/uber-go/zap/blob/v1.27.0/logger.go#L125C1-L141C1
// NewExample builds a Logger that's designed for use in zap's testable
// examples. It writes DebugLevel and above logs to standard out as JSON, but
// omits the timestamp and calling function to keep example output
// short and deterministic.
func NewExample(options ...Option) *Logger {
encoderCfg := zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "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...)
}
zap.NewDevelopment()
- 构建一个开发使用的 Logger,DebugLevel 及以上人性化的格式
// https://github.com/uber-go/zap/blob/v1.27.0/logger.go#L104C1-L110C2
// NewDevelopment builds a development Logger that writes DebugLevel and above
// logs to standard error in a human-friendly format.
//
// It's a shortcut for NewDevelopmentConfig().Build(...Option).
func NewDevelopment(options ...Option) (*Logger, error) {
return NewDevelopmentConfig().Build(options...)
}
zap.NewProduction()
- 构建一个开发使用的生产使用呢的 Logger,InfoLevel 及以上日志,JSON 格式
// https://github.com/uber-go/zap/blob/v1.27.0/logger.go#L96C1-L102C2
// NewProduction builds a sensible production Logger that writes InfoLevel and
// above logs to standard error as JSON.
//
// It's a shortcut for NewProductionConfig().Build(...Option).
func NewProduction(options ...Option) (*Logger, error) {
return NewProductionConfig().Build(options...)
}
配置 Option 示例
logger, _ := zap.NewProduction(zap.Fields(
zap.String("log_name", "testlog"),
zap.String("log_author", "prometheus"),
))
defer logger.Sync()
配置 Hook 示例
logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {
fmt.Println("zap test Hooks")
return nil
}))
defer logger.Sync()
package main
import (
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
defer logger.Sync()
logger.Info("some message",
zap.Namespace("shop"),
zap.String("name", "LiLei"),
zap.String("grade", "No2"),
)
logger.Error("some error message",
zap.Namespace("shop"),
zap.String("name", "LiLei"),
zap.String("grade", "No3"),
)
}
全局 Logger
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()
// 全局logger,zap.L() 和 zap.S() 需要调用 ReplaceGlobals 函数才会记录日志信息
zap.ReplaceGlobals(logger)
zap.L().Info("log info")
zap.S().Info("log info [sugared]")
}
重定向标准日志库 log
package main
import (
"log"
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
defer logger.Sync()
// RedirectStdLogAt 可以添加日志级别
undo := zap.RedirectStdLog(logger)
log.Print("redirected standard library")
undo()
log.Print("this zap logger")
}
输出调用堆栈
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func Hello() {
Warn("hello", zap.String("h", "world"), zap.Int("c", 1))
}
func Warn(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)
Hello()
}
输出文件名和行号
package main
import (
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction(zap.AddCaller())
defer logger.Sync()
logger.Info("AddCaller:line No and filename")
}