|
| 1 | +## 概述 |
| 2 | + |
| 3 | +首先同步下项目概况: |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +上篇文章分享了,规划项目目录和参数验证,其中参数验证使用的是 validator.v8 版本,现已更新到 validator.v9 版本,最新代码查看 github 即可。 |
| 8 | + |
| 9 | +这篇文章咱们分享:路由中间件 - 日志记录。 |
| 10 | + |
| 11 | +日志是特别重要的一个东西,方便我们对问题进行排查,这篇文章我们实现将日志记录到文本文件中。 |
| 12 | + |
| 13 | +这是我规划的,需要记录的参数: |
| 14 | + |
| 15 | +``` |
| 16 | +- request 请求数据 |
| 17 | + - request_time |
| 18 | + - request_method |
| 19 | + - request_uri |
| 20 | + - request_proto |
| 21 | + - request_ua |
| 22 | + - request_referer |
| 23 | + - request_post_data |
| 24 | + - request_client_ip |
| 25 | + |
| 26 | +- response 返回数据 |
| 27 | + - response_time |
| 28 | + - response_code |
| 29 | + - response_msg |
| 30 | + - response_data |
| 31 | + |
| 32 | +- cost_time 花费时间 |
| 33 | +``` |
| 34 | +Gin 框架中自带 Logger 中间件,我们了解下框架中自带的 Logger 中间件是否满足我们的需求? |
| 35 | + |
| 36 | +## gin.Logger() |
| 37 | + |
| 38 | +我们先使用 gin.Logger() 看看效果。 |
| 39 | + |
| 40 | +在 route.go SetupRouter 方法中增加代码: |
| 41 | + |
| 42 | +``` |
| 43 | +engine.Use(gin.Logger()) |
| 44 | +``` |
| 45 | + |
| 46 | +运行后多请求几次,日志输出在命令行中: |
| 47 | + |
| 48 | +``` |
| 49 | +[GIN] 2019/08/30 - 21:24:16 | 200 | 178.072µs | ::1 | GET /ping |
| 50 | +[GIN] 2019/08/30 - 21:24:27 | 200 | 367.997µs | ::1 | POST /product |
| 51 | +[GIN] 2019/08/30 - 21:24:28 | 200 | 2.521592ms | ::1 | POST /product |
| 52 | +``` |
| 53 | + |
| 54 | +先解决第一个问题,怎么将日志输出到文本中? |
| 55 | + |
| 56 | +在 route.go SetupRouter 方法中增加代码: |
| 57 | + |
| 58 | +``` |
| 59 | +f, _ := os.Create(config.AppAccessLogName) |
| 60 | +gin.DefaultWriter = io.MultiWriter(f) |
| 61 | +engine.Use(gin.Logger()) |
| 62 | +``` |
| 63 | + |
| 64 | +运行后多请求几次,日志输出在文件中: |
| 65 | + |
| 66 | +``` |
| 67 | +[GIN] 2019/08/30 - 21:36:07 | 200 | 369.023µs | ::1 | GET /ping |
| 68 | +[GIN] 2019/08/30 - 21:36:08 | 200 | 27.585µs | ::1 | GET /ping |
| 69 | +[GIN] 2019/08/30 - 21:36:10 | 200 | 14.302µs | ::1 | POST /product |
| 70 | +``` |
| 71 | + |
| 72 | +虽然记录到文件成功了,但是记录的参数不是我们想要的样子。 |
| 73 | + |
| 74 | +怎么办呢? |
| 75 | + |
| 76 | +我们需要自定义一个日志中间件,按照我们需要的参数进行记录。 |
| 77 | + |
| 78 | +## 自定义 Logger() |
| 79 | + |
| 80 | +**middleware/logger/logger.go** |
| 81 | + |
| 82 | +``` |
| 83 | +package logger |
| 84 | +
|
| 85 | +import ( |
| 86 | +"bytes" |
| 87 | +"encoding/json" |
| 88 | +"fmt" |
| 89 | +"github.com/gin-gonic/gin" |
| 90 | +"go-gin-api/app/config" |
| 91 | +"go-gin-api/app/util" |
| 92 | +"log" |
| 93 | +"os" |
| 94 | +) |
| 95 | +
|
| 96 | +type bodyLogWriter struct { |
| 97 | +gin.ResponseWriter |
| 98 | +body *bytes.Buffer |
| 99 | +} |
| 100 | +func (w bodyLogWriter) Write(b []byte) (int, error) { |
| 101 | +w.body.Write(b) |
| 102 | +return w.ResponseWriter.Write(b) |
| 103 | +} |
| 104 | +func (w bodyLogWriter) WriteString(s string) (int, error) { |
| 105 | +w.body.WriteString(s) |
| 106 | +return w.ResponseWriter.WriteString(s) |
| 107 | +} |
| 108 | +
|
| 109 | +func SetUp() gin.HandlerFunc { |
| 110 | +return func(c *gin.Context) { |
| 111 | +bodyLogWriter := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer} |
| 112 | +c.Writer = bodyLogWriter |
| 113 | +
|
| 114 | +//开始时间 |
| 115 | +startTime := util.GetCurrentMilliTime() |
| 116 | +
|
| 117 | +//处理请求 |
| 118 | +c.Next() |
| 119 | +
|
| 120 | +responseBody := bodyLogWriter.body.String() |
| 121 | +
|
| 122 | +var responseCode int |
| 123 | +var responseMsg string |
| 124 | +var responseData interface{} |
| 125 | +
|
| 126 | +if responseBody != "" { |
| 127 | +response := util.Response{} |
| 128 | +err := json.Unmarshal([]byte(responseBody), &response) |
| 129 | +if err == nil { |
| 130 | +responseCode = response.Code |
| 131 | +responseMsg = response.Message |
| 132 | +responseData = response.Data |
| 133 | +} |
| 134 | +} |
| 135 | +
|
| 136 | +//结束时间 |
| 137 | +endTime := util.GetCurrentMilliTime() |
| 138 | +
|
| 139 | +if c.Request.Method == "POST" { |
| 140 | +c.Request.ParseForm() |
| 141 | +} |
| 142 | +
|
| 143 | +//日志格式 |
| 144 | +accessLogMap := make(map[string]interface{}) |
| 145 | +
|
| 146 | +accessLogMap["request_time"] = startTime |
| 147 | +accessLogMap["request_method"] = c.Request.Method |
| 148 | +accessLogMap["request_uri"] = c.Request.RequestURI |
| 149 | +accessLogMap["request_proto"] = c.Request.Proto |
| 150 | +accessLogMap["request_ua"] = c.Request.UserAgent() |
| 151 | +accessLogMap["request_referer"] = c.Request.Referer() |
| 152 | +accessLogMap["request_post_data"] = c.Request.PostForm.Encode() |
| 153 | +accessLogMap["request_client_ip"] = c.ClientIP() |
| 154 | +
|
| 155 | +accessLogMap["response_time"] = endTime |
| 156 | +accessLogMap["response_code"] = responseCode |
| 157 | +accessLogMap["response_msg"] = responseMsg |
| 158 | +accessLogMap["response_data"] = responseData |
| 159 | +
|
| 160 | +accessLogMap["cost_time"] = fmt.Sprintf("%vms", endTime - startTime) |
| 161 | +
|
| 162 | +accessLogJson, _ := util.JsonEncode(accessLogMap) |
| 163 | +
|
| 164 | +if f, err := os.OpenFile(config.AppAccessLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil { |
| 165 | +log.Println(err) |
| 166 | +} else { |
| 167 | +f.WriteString(accessLogJson + "\n") |
| 168 | +} |
| 169 | +} |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +运行后多请求几次,日志输出在文件中: |
| 174 | + |
| 175 | +``` |
| 176 | +{"cost_time":"0ms","request_client_ip":"::1","request_method":"GET","request_post_data":"","request_proto":"HTTP/1.1","request_referer":"","request_time":1567172568233,"request_ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36","request_uri":"/ping","response_code":1,"response_data":null,"response_msg":"pong","response_time":1567172568233} |
| 177 | +{"cost_time":"0ms","request_client_ip":"::1","request_method":"GET","request_post_data":"","request_proto":"HTTP/1.1","request_referer":"","request_time":1567172569158,"request_ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36","request_uri":"/ping","response_code":1,"response_data":null,"response_msg":"pong","response_time":1567172569158} |
| 178 | +{"cost_time":"0ms","request_client_ip":"::1","request_method":"POST","request_post_data":"name=admin","request_proto":"HTTP/1.1","request_referer":"","request_time":1567172629565,"request_ua":"PostmanRuntime/7.6.0","request_uri":"/product","response_code":-1,"response_data":null,"response_msg":"Key: 'ProductAdd.Name' Error:Field validation for 'Name' failed on the 'NameValid' tag","response_time":1567172629565} |
| 179 | +``` |
| 180 | + |
| 181 | +OK,咱们想要的所有参数全都记录了! |
| 182 | + |
| 183 | +抛出几个问题吧: |
| 184 | + |
| 185 | +1、有没有开源的日志记录工具? |
| 186 | + |
| 187 | +当然有,其中 logrus 是用的最多的,这个工具功能强大,原来我也分享过,可以看下原来的文章[《使用 logrus 进行日志收集》](https://mp.weixin.qq.com/s/gBWEHe20Lv_2wBSlM2WeVA)。 |
| 188 | + |
| 189 | +2、为什么将日志记录到文本中? |
| 190 | + |
| 191 | +因为,日志平台可以使用的是 ELK。 |
| 192 | + |
| 193 | +使用 Logstash 进行收集文本文件,使用 Elasticsearch 引擎进行搜索分析,最终在 Kibana 平台展示出来。 |
| 194 | + |
| 195 | +3、当大量请求过来时,写入文件会不会出问题? |
| 196 | + |
| 197 | +可能会,这块可以使用异步,咱们可以用下 go 的 chan,具体实现看代码吧,我就不贴了。 |
| 198 | + |
| 199 | +## 源码地址 |
| 200 | + |
| 201 | +https://github.com/xinliangnote/go-gin-api |
| 202 | + |
| 203 | +## go-gin-api 系列文章 |
| 204 | + |
| 205 | +- [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A) |
| 206 | +- [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA) |
0 commit comments