概述
上篇文章分享了 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"
|
在项目命令行执行:
这时,在 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"}
|
说明一下:time
、msg
、level
这些参数是 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 很多,大家可以去网上查找。