
Web项目组件zap日志库
1.zap日志库的使用
Zap是非常快的、结构化的,分日志级别的Go日志库
go get -u go.uber.org/zap
- 基本使用 --> 结构化的日志
- 如何配置日志归档
- console格式和json格式日志配置
- 时间戳的格式
- 配置文件中日志level的识别
- 如何将日志输出到多个位置
2.日志记录器
Zap提供了两种类型的日志记录器,Sugared Logger 和 Logger。
在性能很好但不是很关键的上下文中,使用 SugaredLogger 。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf⻛风格的⽇日志记录。
在每一微秒和每一次内存分配都很重要的上下文中,使用 Logger 。它甚至比 SugaredLogger 更快,内存分配次数也更少,但它只支持强类型的结构化日志记录
func InitLogger() { writeSyncer := getLogWriter() encoder := getEncoder() core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) logger := zap.New(core) sugarLogger = logger.Sugar() } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder return zapcore.NewConsoleEncoder(encoderConfig) } func getLogWriter() zapcore.WriteSyncer { file, _ := os.Create("./test.log") return zapcore.AddSync(file) }
3.配置日志归档
zap本身不提供日志归档功能,为了添加日志切割归档功能,我们将使用第三⽅方库Lumberjack来实现。
安装go get -u github.com/natefinch/lumberjack要在zap中加入Lumberjack支持,我们需要修改 WriteSyncer 代码。我们将按照下面的代码修改 getLogWriter() 函数
func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./test.log", MaxSize: 10, MaxBackups: 5, MaxAge: 30, Compress: false, } return zapcore.AddSync(lumberJackLogger) }Lumberjack Logger采用以下属性作为输入:
- Filename: 日志文件的位置
- MaxSize:在进行切割之前,日志文件的最大大小(以MB为单位)
- MaxBackups:保留旧文件的最大个数
- MaxAges:保留旧文件的最大天数
- Compress:是否压缩/归档旧文件
4.gin框架中使用zap
我们在使用 gin.Default() 的同时是用到了gin框架内的两个默认中间件 Logger() 和 Recovery()
其中 Logger() 是把gin框架本身的日志输出到标准输出(我们本地开发调试时在终端输出的那些日志就是它的功劳),而 Recovery() 是在程序出现panic的时候恢复现场并写入500响应的。
我们可以模仿 Logger() 和 Recovery() 的实现,使用我们的日志库来接收gin框架默认输出的日志。
这里以zap为例,我们实现两个中间件如下
// GinLogger 接收gin框架默认的⽇日志 func GinLogger(logger *zap.Logger) 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) logger.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(logger *zap.Logger, 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 { logger.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 { logger.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { logger.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } }