Gin 自定义中间件记录请求日志
自定义日志中间件比 Gin 默认中间件更灵活,可根据业务需求定制日志内容。
基础请求日志中间件
简单实现
Go
func RequestLogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
startTime := time.Now()
// 记录请求信息
logger.WithFields(logrus.Fields{
"method": c.Request.Method,
"path": c.Request.URL.Path,
"query": c.Request.URL.RawQuery,
"client_ip": c.ClientIP(),
"user_agent": c.Request.UserAgent(),
}).Info("请求开始")
c.Next()
// 记录响应信息
latency := time.Since(startTime)
logger.WithFields(logrus.Fields{
"method": c.Request.Method,
"path": c.Request.URL.Path,
"status": c.Writer.Status(),
"latency_ms": latency.Milliseconds(),
"response_size": c.Writer.Size(),
}).Info("请求完成")
}
}
func main() {
r := gin.New()
r.Use(RequestLogMiddleware())
r.Run(":8080")
}
详细请求日志
Go
func DetailedRequestLogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
startTime := time.Now()
// 请求基本信息
fields := logrus.Fields{
"request_id": uuid.New().String(),
"method": c.Request.Method,
"path": c.Request.URL.Path,
"query": c.Request.URL.RawQuery,
"client_ip": c.ClientIP(),
"user_agent": c.Request.UserAgent(),
"content_type": c.GetHeader("Content-Type"),
"referer": c.GetHeader("Referer"),
}
// 记录请求体(可选)
if c.Request.Method != "GET" && c.Request.ContentLength > 0 && c.Request.ContentLength < 1024 {
bodyBytes, _ := io.ReadAll(c.Request.Body)
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 过滤敏感字段
filteredBody := filterSensitiveFields(string(bodyBytes))
fields["body"] = filteredBody
}
c.Set("request_id", fields["request_id"])
c.Set("start_time", startTime)
logger.WithFields(fields).Info("请求开始")
c.Next()
// 响应信息
latency := time.Since(startTime)
fields["status"] = c.Writer.Status()
fields["latency_ms"] = latency.Milliseconds()
fields["response_size"] = c.Writer.Size()
// 根据状态码分级记录
if c.Writer.Status() >= 500 {
logger.WithFields(fields).Error("请求失败")
} else if c.Writer.Status() >= 400 {
logger.WithFields(fields).Warn("客户端错误")
} else {
logger.WithFields(fields).Info("请求完成")
}
}
}
敏感信息过滤
参数过滤
Go
var sensitiveParams = []string{"password", "token", "secret", "api_key", "credit_card"}
func filterSensitiveFields(data string) string {
var result string
jsonData := make(map[string]interface{})
if err := json.Unmarshal([]byte(data), &jsonData); err == nil {
for k, v := range jsonData {
if isSensitiveField(k) {
jsonData[k] = "[FILTERED]"
}
}
filtered, _ := json.Marshal(jsonData)
result = string(filtered)
} else {
// 非 JSON 格式,简单替换
result = data
for _, field := range sensitiveParams {
// 简化处理:替换常见格式
result = regexp.MustCompile(field+`=\w+`).
ReplaceAllString(result, field+"=[FILTERED]")
}
}
return result
}
func isSensitiveField(field string) bool {
fieldLower := strings.ToLower(field)
for _, sensitive := range sensitiveParams {
if strings.Contains(fieldLower, sensitive) {
return true
}
}
return false
}
Query 参数过滤
Go
func filterQueryParams(query string) string {
if query == "" {
return query
}
params := strings.Split(query, "&")
filtered := make([]string, 0)
for _, param := range params {
parts := strings.SplitN(param, "=", 2)
if len(parts) == 2 && isSensitiveField(parts[0]) {
filtered = append(filtered, parts[0]+"=[FILTERED]")
} else {
filtered = append(filtered, param)
}
}
return strings.Join(filtered, "&")
}
响应内容记录
响应体捕获
Go
type responseWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w *responseWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
func ResponseLogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 包装 ResponseWriter
writer := &responseWriter{
ResponseWriter: c.Writer,
body: bytes.NewBufferString(""),
}
c.Writer = writer
startTime := time.Now()
c.Next()
latency := time.Since(startTime)
// 记录响应信息
responseBody := writer.body.String()
// 限制响应体长度
if len(responseBody) > 500 {
responseBody = responseBody[:500] + "...[truncated]"
}
logger.WithFields(logrus.Fields{
"request_id": c.GetString("request_id"),
"status": c.Writer.Status(),
"latency_ms": latency.Milliseconds(),
"response_size": c.Writer.Size(),
"response_body": responseBody,
}).Info("响应内容")
}
}
慢请求记录
Go
func SlowRequestLogMiddleware(thresholdMs int64) gin.HandlerFunc {
return func(c *gin.Context) {
startTime := time.Now()
c.Next()
latency := time.Since(startTime).Milliseconds()
// 慢请求告警
if latency > thresholdMs {
logger.WithFields(logrus.Fields{
"request_id": c.GetString("request_id"),
"method": c.Request.Method,
"path": c.Request.URL.Path,
"latency_ms": latency,
"threshold_ms": thresholdMs,
"client_ip": c.ClientIP(),
}).Warn("慢请求警告")
}
}
}
func main() {
r := gin.New()
r.Use(RequestIDMiddleware())
r.Use(RequestLogMiddleware())
r.Use(SlowRequestLogMiddleware(500)) // 超过 500ms 告警
}
错误请求记录
Go
func ErrorRequestLogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// 检查是否有错误
if len(c.Errors) > 0 {
requestID := c.GetString("request_id")
for _, err := range c.Errors {
logger.WithFields(logrus.Fields{
"request_id": requestID,
"error_type": err.Type,
"error_msg": err.Error(),
"error_meta": err.Meta,
"path": c.Request.URL.Path,
"method": c.Request.Method,
"client_ip": c.ClientIP(),
}).Error("请求错误")
}
}
// 4xx/5xx 错误记录
status := c.Writer.Status()
if status >= 400 {
logger.WithFields(logrus.Fields{
"request_id": c.GetString("request_id"),
"status": status,
"path": c.Request.URL.Path,
"method": c.Request.Method,
}).Error("HTTP错误响应")
}
}
}
请求统计
Go
type RequestStats struct {
TotalRequests int64
SuccessRequests int64
ErrorRequests int64
AvgLatencyMs int64
PathStats map[string]*PathStat
}
type PathStat struct {
Count int64
AvgLatency int64
MaxLatency int64
Errors int64
}
var stats = &RequestStats{
PathStats: make(map[string]*PathStat),
}
func StatsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
startTime := time.Now()
path := c.Request.URL.Path
c.Next()
latency := time.Since(startTime).Milliseconds()
status := c.Writer.Status()
// 更新统计
stats.TotalRequests++
if status < 400 {
stats.SuccessRequests++
} else {
stats.ErrorRequests++
}
// 路径统计
pathStat, exists := stats.PathStats[path]
if !exists {
pathStat = &PathStat{}
stats.PathStats[path] = pathStat
}
pathStat.Count++
pathStat.AvgLatency = (pathStat.AvgLatency*(pathStat.Count-1) + latency) / pathStat.Count
if latency > pathStat.MaxLatency {
pathStat.MaxLatency = latency
}
if status >= 400 {
pathStat.Errors++
}
}
}
// 统计接口
func GetStatsHandler(c *gin.Context) {
c.JSON(200, stats)
}
完整日志中间件配置
Go
func SetupLogMiddleware(r *gin.Engine) {
// 请求 ID
r.Use(RequestIDMiddleware())
// 基础请求日志
r.Use(RequestLogMiddleware())
// 慢请求告警(根据业务调整阈值)
r.Use(SlowRequestLogMiddleware(1000))
// 错误请求记录
r.Use(ErrorRequestLogMiddleware())
// 请求统计
r.Use(StatsMiddleware())
// 管理接口(查看统计)
r.GET("/admin/stats", AuthMiddleware(), GetStatsHandler)
}
日志字段对比
| 字段 | 记录时机 | 用途 |
|---|---|---|
| request_id | 请求开始 | 链路追踪 |
| method | 请求开始 | 区分操作类型 |
| path | 请求开始 | 资源定位 |
| client_ip | 请求开始 | 来源追踪 |
| latency_ms | 请求结束 | 性能分析 |
| status | 请求结束 | 成功/失败判断 |
| error_msg | 异常时 | 问题定位 |
注意:敏感参数必须过滤,避免泄露用户密码、token 等。
要点总结
- 请求信息:method、path、query、client_ip、user_agent
- 响应信息:status、latency、response_size
- 敏感过滤:password、token 等字段替换为 [FILTERED]
- 慢请求告警:超过阈值自动记录告警
- 错误记录:4xx/5xx 错误详细记录
- 请求统计:路径级别统计便于分析
📝 发现内容有误?点击此处直接编辑