引言
zap日志库是uber开源的一款高性能日志库,支持在 Golang 中进行快速、结构化、分级的日志记录。 zap的高性能主要优化:
- 避免GC:对象复用 zap通过sync.pool提供的对象池,复用了大量可以复用的对象,避开了GC,防止过多碎片化的内存分配与回收。
- 内建的Encoder:避免反射 标准库的json.Marshaler提供的是基于类型反射的拼接方式,可能会导致额外的性能开销,zap自己实现json Encoder;通过明确的类型调用,直接拼接字符串,最小化性能开销。
- 避免竞态 zap选择写时复制机制,抽象对象与操作进行绑定,经过判断的对象才能进入后续的逻辑操作,减少资源竞争,避免多余的竞态。
zap不支持日志的分割,可以使用 lumberjack 也是 zap 官方推荐用于日志分割。
安装
go get -u go.uber.org/zap
go-kratos 引入
go-kratos 官方定义了两个层面的抽象,Logger统一了日志的接入方式,Helper接口统一的日志库的调用方式。 引入zap只需进行简单的封装即可使用。
实现
package zaplog
import (
"fmt"
"os"
"github.com/go-kratos/kratos/v2/log"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var _ log.Logger = (*ZapLogger)(nil)
type ZapLogger struct {
log *zap.Logger
Sync func() error
}
// Logger
func Logger() log.Logger {
encoder := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stack",
EncodeTime: zapcore.ISO8601TimeEncoder,
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
}
return NewZapLogger(
encoder,
zap.NewAtomicLevelAt(zapcore.DebugLevel),
zap.AddStacktrace(
zap.NewAtomicLevelAt(zapcore.ErrorLevel)),
zap.AddCaller(),
zap.AddCallerSkip(2),
zap.Development(),
)
}
// NewZapLogger
func NewZapLogger(encoder zapcore.EncoderConfig, level zap.AtomicLevel, opts ...zap.Option) *ZapLogger {
level.SetLevel(zap.InfoLevel) // 设置日志级别
var core zapcore.Core
core = zapcore.NewCore(
zapcore.NewJSONEncoder(encoder), // 编码器配置
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), // 打印到控制台
level, // 日志级别
)
zapLogger := zap.New(core, opts...)
return &ZapLogger{log: zapLogger, Sync: zapLogger.Sync}
}
// Log
func (l *ZapLogger) Log(level log.Level, keyvals ...interface{}) error {
if len(keyvals) == 0 || len(keyvals)%2 != 0 {
l.log.Warn(fmt.Sprint("Keyvalues must appear in pairs: ", keyvals))
return nil
}
var data []zap.Field
for i := 0; i < len(keyvals); i += 2 {
data = append(data, zap.Any(fmt.Sprint(keyvals[i]), keyvals[i+1]))
}
switch level {
case log.LevelDebug:
l.log.Debug("", data...)
case log.LevelInfo:
l.log.Info("", data...)
case log.LevelWarn:
l.log.Warn("", data...)
case log.LevelError:
l.log.Error("", data...)
case log.LevelFatal:
l.log.Fatal("", data...)
}
return nil
}
如需保存至日志文件,可进行日志保存
import "github.com/natefinch/lumberjack"
// 日志自动切割,采用 lumberjack 实现的
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "../../log/zap.log", // 指定日志存储位置
MaxSize: 10, // 日志的最大大小(M)
MaxBackups: 5, // 日志的最大保存数量
MaxAge: 30, // 日志文件存储最大天数
Compress: false, // 是否执行压缩
}
return zapcore.AddSync(lumberJackLogger)
}
func NewZapLogger(encoder zapcore.EncoderConfig, level zap.AtomicLevel, opts ...zap.Option) *ZapLogger {
writeSyncer := getLogWriter() // 日志切割
level.SetLevel(zap.InfoLevel) // 设置日志级别
var core zapcore.Core
core = zapcore.NewCore(
zapcore.NewJSONEncoder(encoder), // 编码器配置
zapcore.NewMultiWriteSyncer(zapcore.AddSync(writeSyncer)), // 打印到文件
level, // 日志级别
)
zapLogger := zap.New(core, opts...)
return &ZapLogger{log: zapLogger, Sync: zapLogger.Sync}
}
中间件引用
var opts = []http.ServerOption{
http.Middleware(
ratelimit.Server(),
recovery.Recovery(),
logging.Server(logger),
),
}
var opts = []grpc.ServerOption{
grpc.Middleware(
ratelimit.Server(),
recovery.Recovery(),
logging.Server(logger),
),
}
go-kratos框架默认日志替换至zap
app, cleanup, err := wireApp(bc.Server, bc.Data, bc.System, zaplog.Logger())
if err != nil {
panic(err)
}
defer cleanup()