Golang Zap 日志配置

Golang 日志库 Zap 和 官方日志库改造

安装依赖

go get gopkg.in/natefinch/lumberjack.v2
go get go.uber.org/zap
go get go.uber.org/zap/zapcore

示例使用了viper来读取配置文件

go get github.com/spf13/viper

使用方式

  • 拷贝配置文件到项目中,新建一个 go 文件存放,package 名称可以自定义
  • 在项目启动位置或者调用日志对象之前执行NewLogger函数,来创建日志对象

下面给出在 gin 框架中的使用方式。

var (
	engine *gin.Engine
)

func init() {

	// 配置文件初始化
	viper.SetConfigName("config")
	viper.SetConfigType("json")
	viper.AddConfigPath("./debug")
	viper.AddConfigPath(".")
	err := viper.ReadInConfig()
	if err != nil {
		fmt.Println("配置文件读取失败: " + err.Error())
		os.Exit(1)
	}

	// 日志配置
	err := os.Setenv("GIN_DEBUG", "true")
	if err != nil {
		panic(err)
	}
	logger.InitLogger()

	gin.SetMode(gin.ReleaseMode)
	engine = gin.Default()
}

func main() {
	listenPort := viper.GetInt("server.port")
	logger.Info("服务器运行中...  端口: " + strconv.Itoa(listenPort))
	if err := engine.Run(":" + strconv.Itoa(listenPort)); err != nil {
		logger.Fatal("服务器运行错误: " + err.Error())
	}
}

配置内容

package logger

/*
系统环境变量:
	 需要在 main 包的 init 函数中使用 os.Setenv() 设置环境变量
   GIN_LOG_PATH: 设置文件路径及其名称,例如: ./log/server.log 或者 /var/log/demo_server.log
   GIN_DEBUG: 设置是否开启开发模式,开启后会输出堆栈跟踪信息

依赖:
	gopkg.in/natefinch/lumberjack.v2
	go.uber.org/zap
*/

import (
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
	"sync"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var (
	_logger *zap.Logger
	once    sync.Once
)

var (
	Debug func(msg string, fields ...zap.Field)
	Info  func(msg string, fields ...zap.Field)
	Warn  func(msg string, fields ...zap.Field)
	Error func(msg string, fields ...zap.Field)
	Fatal func(msg string, fields ...zap.Field)
)

func InitLogger() {
	once.Do(func() {
		_logger = logConfig()
	})

	Debug = _logger.Debug
	Info = _logger.Info
	Warn = _logger.Warn
	Error = _logger.Error
	Fatal = _logger.Fatal
}

// LogConfig 日志配置
func logConfig() *zap.Logger {

	filePath := os.Getenv("GIN_LOG_PATH")
	if filePath == "" {
		filePath = "./logs/server.log"
	}

	loggerFileWriter := lumberjack.Logger{
		Filename:   filePath, // 日志文件路径
		MaxSize:    10,       // 每个日志文件保存的最大尺寸 单位:M
		MaxBackups: 5,        // 日志文件最多保存多少个备份
		MaxAge:     7,        // 文件最多保存多少天
		Compress:   true,     // 是否压缩
	}

	// 日志文件输出配置
	fileEncoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "line",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.CapitalLevelEncoder,                        // 全大写日志等级标识
		EncodeTime:     zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"), // 时间格式
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
		EncodeName:     zapcore.FullNameEncoder,
	}
	// 终端输出配置
	stdEncoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "line",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.CapitalColorLevelEncoder,
		EncodeTime:     zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"),
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
		EncodeName:     zapcore.FullNameEncoder,
	}

	fileEncoder := zapcore.NewJSONEncoder(fileEncoderConfig)  // 设置日志文件内容编码格式:json格式
	stdEncoder := zapcore.NewConsoleEncoder(stdEncoderConfig) // 终端格式

	// 创建写入的目标 writer
	fileWriter := zapcore.NewMultiWriteSyncer(zapcore.AddSync(&loggerFileWriter))
	stdWriter := zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout))

	// 设置同时写入文件和终端
	// logWriter := zapcore.NewMultiWriteSyncer(zapcore.AddSync(&hook), zapcore.AddSync(os.Stdout))

	// 定义日志级别
	debugLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
		return level >= zapcore.DebugLevel
	})

	infoLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
		return level >= zapcore.InfoLevel
	})

	// 组合所有配置,分别设置写入文件的参数和显示到终端的参数
	core := zapcore.NewTee(
		zapcore.NewCore(fileEncoder, fileWriter, infoLevel),
		zapcore.NewCore(stdEncoder, stdWriter, debugLevel),
	)

	if os.Getenv("GIN_DEBUG") == "true" {
		// 开启开发模式,堆栈跟踪
		caller := zap.AddCaller()
		// // 开启文件及行号
		development := zap.Development()
		// 构造日志对象
		return zap.New(core, caller, development)
	} else {
		return zap.New(core)
	}
}

Gin 日志中间件

func ginLog() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		st := time.Now()
		ctx.Next()
		et := time.Now()
		latency := et.Sub(st)
		status := ctx.Writer.Status()
		clientIP := ctx.ClientIP()
		method := ctx.Request.Method
		path := ctx.Request.URL.Path
		// userAgent := ctx.Request.UserAgent()
		if status >= 400 {
			logger.Error(fmt.Sprintf("[GIN] %15s %7s [%d] %s  %s", clientIP, method, status, path, latency))
		} else {
			logger.Info(fmt.Sprintf("[GIN] %15s %7s [%d] %s  %s", clientIP, method, status, path, latency))
		}
	}
}

官方日志库

简单加上日志滚动,并给 gin 单独一个前缀显示

package logger

import (
	"fmt"
	"io"
	"log"
	"os"

	"github.com/spf13/viper"
	"gopkg.in/natefinch/lumberjack.v2"
)

var (
	Info    *log.Logger
	Warning *log.Logger
	Error   *log.Logger
	Debug   *log.Logger
	Gin     *log.Logger
)

func Init() {
	filePath := fmt.Sprintf("%s%s", viper.GetString("log.dir"), viper.GetString("log.filename"))

	loggerFileWriter := &lumberjack.Logger{
		Filename:   filePath, // 日志文件路径
		MaxSize:    5,        // 每个日志文件保存的最大尺寸 单位:M
		MaxBackups: 3,        // 日志文件最多保存多少个备份
		MaxAge:     0,        // 文件最多保存多少天,设置为 0 不删除任何超过时间范围的文件
		Compress:   true,     // 是否压缩
	}

	lw := io.MultiWriter(os.Stdout, loggerFileWriter)

	Info = log.New(lw, "[INFO] ", log.Ldate|log.Ltime|log.Lshortfile)
	Warning = log.New(lw, "[WARNING] ", log.Ldate|log.Ltime|log.Lshortfile)
	Error = log.New(lw, "[ERROR] ", log.Ldate|log.Ltime|log.Llongfile)
	Debug = log.New(lw, "[DEBUG] ", log.Ldate|log.Ltime|log.Lshortfile)
	Gin = log.New(lw, "[GIN] ", log.Ldate|log.Ltime)
}

gin 的日志中间件设置


func Init(engine *gin.Engine) {
	engine.Use(
		ginLog(),
		gin.Recovery(),
	)
}

func ginLog() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		st := time.Now()
		ctx.Next()
		et := time.Now()
		latency := et.Sub(st)
		status := ctx.Writer.Status()
		clientIP := ctx.ClientIP()
		method := ctx.Request.Method
		path := ctx.Request.URL.Path
		// userAgent := ctx.Request.UserAgent()
		if status >= 400 {
			logger.Gin.Printf("%15s %7s [%d] %s  %s", clientIP, method, status, path, latency)
		} else {
			logger.Gin.Printf("%15s %7s [%d] %s  %s", clientIP, method, status, path, latency)
		}
	}
}

效果如下

[GIN-debug] Listening and serving HTTP on :59780
[GIN] 2022/10/08 17:11:28       127.0.0.1     GET [200] /ping  54.958µs
[GIN] 2022/10/08 17:11:34       127.0.0.1     GET [200] /ping  70.542µs
[GIN] 2022/10/08 17:11:34       127.0.0.1     GET [200] /ping  34.583µs
[GIN] 2022/10/08 17:11:36       127.0.0.1     GET [200] /ping  36.042µs
[GIN] 2022/10/08 17:11:42       127.0.0.1     GET [404] /piasd  1.417µs