gin日志记录

概述

上篇文章分享了 Gin 框架的路由配置,这篇文章分享日志记录。

查了很多资料,Go 的日志记录用的最多的还是 github.com/sirupsen/logrus

Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger.

Gin 框架的日志默认只会在控制台输出,咱们利用 Logrus 封装一个中间件,将日志记录到文件中。

这篇文章就是学习和使用 Logrus

日志格式

比如,我们约定日志格式为 Text,包含字段如下:

请求时间日志级别状态码执行时间请求IP请求方式请求路由

接下来,咱们利用 Logrus 实现它。

Logrus 使用

dep 方式进行安装。

Gopkg.toml 文件新增:

1
2
3
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.4.2"

在项目中导入:

1
import "github.com/sirupsen/logrus"

在项目命令行执行:

1
dep ensure

这时,在 vendor/github.com/ 目录中就会看到 sirupsen 目录。

准备上手用了,上手之前咱们先规划一下,将这个功能设置成一个中间件,比如:logger.go

日志可以记录到 File 中,定义一个 LoggerToFile 方法。

日志可以记录到 MongoDB 中,定义一个 LoggerToMongo 方法。

日志可以记录到 ES 中,定义一个 LoggerToES 方法。

日志可以记录到 MQ 中,定义一个 LoggerToMQ 方法。

这次咱们先实现记录到文件, 实现 LoggerToFile 方法,其他的可以根据自己的需求进行实现。

这个 logger 中间件,创建好了,可以任意在其他项目中进行迁移使用。

废话不多说,直接看代码。

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
package middleware

import (
"fmt"
"ginDemo/config"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"os"
"path"
"time"
)

// 日志记录到文件
func LoggerToFile() gin.HandlerFunc {

logFilePath := config.Log_FILE_PATH
logFileName := config.LOG_FILE_NAME

//日志文件
fileName := path.Join(logFilePath, logFileName)

//写入文件
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("err", err)
}

//实例化
logger := logrus.New()

//设置输出
logger.Out = src

//设置日志级别
logger.SetLevel(logrus.DebugLevel)

//设置日志格式
logger.SetFormatter(&logrus.TextFormatter{})

return func(c *gin.Context) {
// 开始时间
startTime := time.Now()

// 处理请求
c.Next()

// 结束时间
endTime := time.Now()

// 执行时间
latencyTime := endTime.Sub(startTime)

// 请求方式
reqMethod := c.Request.Method

// 请求路由
reqUri := c.Request.RequestURI

// 状态码
statusCode := c.Writer.Status()

// 请求IP
clientIP := c.ClientIP()

// 日志格式
logger.Infof("| %3d | %13v | %15s | %s | %s |",
statusCode,
latencyTime,
clientIP,
reqMethod,
reqUri,
)
}
}

// 日志记录到 MongoDB
func LoggerToMongo() gin.HandlerFunc {
return func(c *gin.Context) {

}
}

// 日志记录到 ES
func LoggerToES() gin.HandlerFunc {
return func(c *gin.Context) {

}
}

// 日志记录到 MQ
func LoggerToMQ() gin.HandlerFunc {
return func(c *gin.Context) {

}
}

日志中间件写好了,怎么调用呢?

只需在 main.go 中新增:

1
2
engine := gin.Default() //在这行后新增
engine.Use(middleware.LoggerToFile())

运行一下,看看日志:

1
2
time="2019-07-17T22:10:45+08:00" level=info msg="| 200 |      27.698µs |             ::1 | GET | /v1/product/add?name=a&price=10 |"
time="2019-07-17T22:10:46+08:00" level=info msg="| 200 | 27.239µs | ::1 | GET | /v1/product/add?name=a&price=10 |"

这个 time="2019-07-17T22:10:45+08:00" ,这个时间格式不是咱们想要的,怎么办?

时间需要格式化一下,修改 logger.SetFormatter

1
2
3
4
//设置日志格式
logger.SetFormatter(&logrus.TextFormatter{
TimestampFormat:"2006-01-02 15:04:05",
})

执行以下,再看日志:

1
2
time="2019-07-17 22:15:57" level=info msg="| 200 |     185.027µs |             ::1 | GET | /v1/product/add?name=a&price=10 |"
time="2019-07-17 22:15:58" level=info msg="| 200 | 56.989µs | ::1 | GET | /v1/product/add?name=a&price=10 |"

时间变得正常了。

我不喜欢文本格式,喜欢 JSON 格式,怎么办?

1
2
3
4
//设置日志格式
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat:"2006-01-02 15:04:05",
})

执行以下,再看日志:

1
2
{"level":"info","msg":"| 200 |       24.78µs |             ::1 | GET | /v1/product/add?name=a\u0026price=10 |","time":"2019-07-17 22:23:55"}
{"level":"info","msg":"| 200 | 26.946µs | ::1 | GET | /v1/product/add?name=a\u0026price=10 |","time":"2019-07-17 22:23:56"}

msg 信息太多,不方便看,怎么办?

1
2
3
4
5
6
7
8
// 日志格式
logger.WithFields(logrus.Fields{
"status_code" : statusCode,
"latency_time" : latencyTime,
"client_ip" : clientIP,
"req_method" : reqMethod,
"req_uri" : reqUri,
}).Info()

执行以下,再看日志:

1
2
{"client_ip":"::1","latency_time":26681,"level":"info","msg":"","req_method":"GET","req_uri":"/v1/product/add?name=a\u0026price=10","status_code":200,"time":"2019-07-17 22:37:54"}
{"client_ip":"::1","latency_time":24315,"level":"info","msg":"","req_method":"GET","req_uri":"/v1/product/add?name=a\u0026price=10","status_code":200,"time":"2019-07-17 22:37:55"}

说明一下:timemsglevel 这些参数是 logrus 自动加上的。

logrus 支持输出文件名和行号吗?

不支持,作者的回复是太耗性能。

不过网上也有人通过 Hook 的方式实现了,选择在生产环境使用的时候,记得做性能测试。

logrus 支持日志分割吗?

不支持,但有办法实现它。

1、可以利用 Linux logrotate,统一由运维进行处理。

2、可以利用 file-rotatelogs 实现。

需要导入包:

github.com/lestrrat-go/file-rotatelogs

github.com/rifflock/lfshook

奉上完整代码:

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
package middleware

import (
"fmt"
"ginDemo/config"
"github.com/gin-gonic/gin"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"os"
"path"
"time"
)

// 日志记录到文件
func LoggerToFile() gin.HandlerFunc {

logFilePath := config.Log_FILE_PATH
logFileName := config.LOG_FILE_NAME

// 日志文件
fileName := path.Join(logFilePath, logFileName)

// 写入文件
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("err", err)
}

// 实例化
logger := logrus.New()

// 设置输出
logger.Out = src

// 设置日志级别
logger.SetLevel(logrus.DebugLevel)

// 设置 rotatelogs
logWriter, err := rotatelogs.New(
// 分割后的文件名称
fileName + ".%Y%m%d.log",

// 生成软链,指向最新日志文件
rotatelogs.WithLinkName(fileName),

// 设置最大保存时间(7天)
rotatelogs.WithMaxAge(7*24*time.Hour),

// 设置日志切割时间间隔(1天)
rotatelogs.WithRotationTime(24*time.Hour),
)

writeMap := lfshook.WriterMap{
logrus.InfoLevel: logWriter,
logrus.FatalLevel: logWriter,
logrus.DebugLevel: logWriter,
logrus.WarnLevel: logWriter,
logrus.ErrorLevel: logWriter,
logrus.PanicLevel: logWriter,
}

lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{
TimestampFormat:"2006-01-02 15:04:05",
})

// 新增 Hook
logger.AddHook(lfHook)

return func(c *gin.Context) {
// 开始时间
startTime := time.Now()

// 处理请求
c.Next()

// 结束时间
endTime := time.Now()

// 执行时间
latencyTime := endTime.Sub(startTime)

// 请求方式
reqMethod := c.Request.Method

// 请求路由
reqUri := c.Request.RequestURI

// 状态码
statusCode := c.Writer.Status()

// 请求IP
clientIP := c.ClientIP()

// 日志格式
logger.WithFields(logrus.Fields{
"status_code" : statusCode,
"latency_time" : latencyTime,
"client_ip" : clientIP,
"req_method" : reqMethod,
"req_uri" : reqUri,
}).Info()
}
}

// 日志记录到 MongoDB
func LoggerToMongo() gin.HandlerFunc {
return func(c *gin.Context) {

}
}

// 日志记录到 ES
func LoggerToES() gin.HandlerFunc {
return func(c *gin.Context) {

}
}

// 日志记录到 MQ
func LoggerToMQ() gin.HandlerFunc {
return func(c *gin.Context) {

}
}

这时会新生成一个文件 system.log.20190717.log,日志内容与上面的格式一致。

最后,logrus 可扩展的 Hook 很多,大家可以去网上查找。