go-kratos框架引入zap日志库

1,064 阅读2分钟

引言

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