zap库是Uber开源的日志库,好用但是不支持切割归档
package logger
// 调用Init()方法时传入,通过viper读取到的log配置
// Init 初始化zap,通过配置文件中的mode,控制日志的输出地点
func Init(cfg *settings.LogConf, mode string) (err error) {
writeSyncer := getLogWriter(cfg.FileName, cfg.MaxAge, cfg.MaxSize, cfg.MaxBackUps)
encoder := getEncoder()
var l = new(zapcore.Level)
err = l.UnmarshalText([]byte(cfg.Level))
if err != nil {
return
}
var core zapcore.Core
// 当 "dev" 模式时, 日志不仅输出到日志文件,还要打印到控制台
if mode == "dev" {
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// 用zap.NewTee() 配置多个日志输出的地方, zapcore.Lock(os.Stdout) 将标准输出转换为WriteSyncer 类型
core = zapcore.NewTee(
zapcore.NewCore(encoder, writeSyncer, l),
zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel),
)
} else {
core = zapcore.NewCore(encoder, writeSyncer, l)
}
// zap.New() 手动配置zap,而不是直接通过zap.NewProduction() 这样的预制logger
// New() 有两个参数 zapCore.Core Option
// zapCore.Core 有三个参数,encoder, WriteSyncer, LevelEnabler
// encoder 编码器 使用开箱即用的NewJsonEncoder() 并且可以使用预先设置的newProductionEncoderConfig(), writeSyncer 将配置文件写到哪里去,使用zapcore.AddSync()设置地址,
// levelEnabler 表示哪种级别的日志将被写入
logger := zap.New(core, zap.AddCaller())
// 替换zap库中全局变量,可以直接通过zap.L()访问
zap.ReplaceGlobals(logger)
return
}
当我们想要定制logger时,需要调用zap.New(), 而不是zap.NewProduction()这样的预制logger
zap.New() 需要两个参数,zapcore.Core 和 Options
zapcore.Core 需要三个配置,encoder, writerSync, levelEnabler。
// 由于默认的配置打印出的日志时间为时间戳,所以需要修改默认配置
func getEncoder() zapcore.Encoder {
encoderConfig := zapcore.NewProductionEncoder()
// 设置为标准时间
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 设置隔离级别为大写字母表示
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zap.NewJsonEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriterSync {
file, _ := os.Create("./xxx.log")
return zap.AddSync(file)
}
使用Lumberjack来进行日志的切割归档
因为使用了第三方库进行切割归档,所以需要修改一下getLogWriter()
func getLogWriter(filename string, maxSize, maxAge, maxBackups) zapcore.WriterSync {
lumberJack := &lumberjack.Logger {
Filename: filename, // 文件名
MaxSize: maxSize, // 切割前文件最大 MB
MaxAge: maxAge, // 旧文件最大保存天数
MaxBackups: maxBackups, // 旧文件最大备份数
Compress: false, // 是否压缩归档日志文件
}
return zapcore.AddSync(lumberJack)
}
完整代码
func Init(cfg *settings.LogConf) (err error) {
writerSync := getLogWriter(cfg.FileName, cfg.MaxSize, cfg.MaxAge, cfg.MaxBackups)
encoder := getEncoder()
var l = new(zapcore.Level)
// 反序列化读取配置文件中的隔离级别
err = l.UnmarshalText(byte[](cfg.Level))
if err != nil {
return
}
core := zapcore.New(encoder, writerSync, l)
logger := zap.New(core,zap.AddCaller())
// 替换zap库中的全局变量
zap.ReplaceGlobals(logger)
return
}
func getLogWriter(filename string, maxSize, maxAge, maxBackups int) zapcore.WriterSync {
// 使用lumberjack 进行切割归档
lumberJack := &lumberjack.Logger {
FileName: filename,
MaxAge: maxAge,
MaxBackups: maxBackups,
MaxSize: maxSize,
Compress: false,
}
return zapcore.AddSync(lumberJack)
}
func getEncoder() zapcore.Encoder {
encoderConfig = zapcore.NewProductionEncoder()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewJsonEncoder(encoderConfig)
}
利用中间件替换gin默认日志库
// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
// 先去执行其余的中间件
c.Next()
cost := time.Since(start)
zap.L().Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}
// GinRecovery recover掉项目可能出现的panic
func GinRecovery(stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
zap.L().Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
if stack {
zap.L().Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
zap.L().Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}