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()
	}
}