Golang Zap 日志配置

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

安装依赖

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

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

1
go get github.com/spf13/viper

使用方式

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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())
}
}

配置内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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 日志中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 单独一个前缀显示

2024-03-07 15:25 更新:go 1.21 版本标准库正式提供结构化日志包 log/slog 包。推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 的日志中间件设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

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

效果如下

1
2
3
4
5
6
[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