

上篇文章分享了 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 实现它。
用dep 方式进行安装。
在Gopkg.toml 文件新增:
[[constraint]] name = "github.com/sirupsen/logrus" version = "1.4.2"在项目中导入:
import "github.com/sirupsen/logrus"在项目命令行执行:
dep ensure这时,在vendor/github.com/ 目录中就会看到sirupsen 目录。
准备上手用了,上手之前咱们先规划一下,将这个功能设置成一个中间件,比如:logger.go。
日志可以记录到 File 中,定义一个LoggerToFile 方法。
日志可以记录到 MongoDB 中,定义一个LoggerToMongo 方法。
日志可以记录到 ES 中,定义一个LoggerToES 方法。
日志可以记录到 MQ 中,定义一个LoggerToMQ 方法。
...
这次咱们先实现记录到文件, 实现LoggerToFile 方法,其他的可以根据自己的需求进行实现。
这个logger 中间件,创建好了,可以任意在其他项目中进行迁移使用。
废话不多说,直接看代码。
package middlewareimport ("fmt""ginDemo/config""github.com/gin-gonic/gin""github.com/sirupsen/logrus""os""path""time")// 日志记录到文件func LoggerToFile() gin.HandlerFunc {logFilePath := config.Log_FILE_PATHlogFileName := 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()// 请求IPclientIP := c.ClientIP()// 日志格式logger.Infof("| %3d | %13v | %15s | %s | %s |",statusCode,latencyTime,clientIP,reqMethod,reqUri,)}}// 日志记录到 MongoDBfunc LoggerToMongo() gin.HandlerFunc {return func(c *gin.Context) {}}// 日志记录到 ESfunc LoggerToES() gin.HandlerFunc {return func(c *gin.Context) {}}// 日志记录到 MQfunc LoggerToMQ() gin.HandlerFunc {return func(c *gin.Context) {}}日志中间件写好了,怎么调用呢?
只需在 main.go 中新增:
engine := gin.Default() //在这行后新增engine.Use(middleware.LoggerToFile())运行一下,看看日志:
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
//设置日志格式logger.SetFormatter(&logrus.TextFormatter{TimestampFormat:"2006-01-02 15:04:05",})执行以下,再看日志:
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 格式,怎么办?
//设置日志格式logger.SetFormatter(&logrus.JSONFormatter{TimestampFormat:"2006-01-02 15:04:05",})执行以下,再看日志:
{"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 信息太多,不方便看,怎么办?
// 日志格式logger.WithFields(logrus.Fields{"status_code" : statusCode,"latency_time" : latencyTime,"client_ip" : clientIP,"req_method" : reqMethod,"req_uri" : reqUri,}).Info()执行以下,再看日志:
{"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
奉上完整代码:
package middlewareimport ("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_PATHlogFileName := 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)// 设置 rotatelogslogWriter, 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",})// 新增 Hooklogger.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()// 请求IPclientIP := c.ClientIP()// 日志格式logger.WithFields(logrus.Fields{"status_code" : statusCode,"latency_time" : latencyTime,"client_ip" : clientIP,"req_method" : reqMethod,"req_uri" : reqUri,}).Info()}}// 日志记录到 MongoDBfunc LoggerToMongo() gin.HandlerFunc {return func(c *gin.Context) {}}// 日志记录到 ESfunc LoggerToES() gin.HandlerFunc {return func(c *gin.Context) {}}// 日志记录到 MQfunc LoggerToMQ() gin.HandlerFunc {return func(c *gin.Context) {}}这时会新生成一个文件system.log.20190717.log,日志内容与上面的格式一致。
最后,logrus 可扩展的 Hook 很多,大家可以去网上查找。
有些读者建议,手机上看代码不方便,建议更新到 GitHub 上。
现已更新,地址如下:
https://github.com/xinliangnote/Go

Gin 框架
基础篇
本文欢迎转发,转发请注明作者和出处,谢谢!

作者:新亮笔记(关注公众号,可申请添加微信好友)
出处:https://www.cnblogs.com/xinliangcoder
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。