Files
CamTalk/backend/internal/trace/gin_logger.go
hhs c17798ec67 feat: 添加 HTTP 请求日志中间件(Phase 4)
实现内容:
- 创建 trace/gin_logger.go,实现 GinLogger 和 GinRecovery 中间件
- GinLogger 自动记录所有 HTTP 请求的 method/path/status/latency/client_ip
- GinRecovery 使用 zap 记录 panic 恢复信息,替代 gin.Recovery()
- 修改 main.go 注册三层中间件:TraceMiddleware -> GinLogger -> GinRecovery
- 所有日志自动附加 trace_id 和 request_id 字段

日志示例:
{
  "level": "info",
  "ts": "2026-06-21T22:26:07.610+0800",
  "msg": "request completed",
  "trace_id": "01KVN964KSY6BFDKB9S932NXB8",
  "request_id": "01KVN964KSY6BFDKB9S932NXB8",
  "method": "GET",
  "path": "/api/health",
  "status": 200,
  "latency_ms": 0,
  "client_ip": "::1"
}

测试:已验证健康检查接口日志正常输出
2026-06-21 22:26:43 +08:00

64 lines
1.4 KiB
Go

package trace
import (
"time"
"github.com/gin-gonic/gin"
)
// GinLogger 记录每个 HTTP 请求的 method/path/status/latency
func GinLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
latency := time.Since(start).Milliseconds()
status := c.Writer.Status()
log := FromContext(c.Request.Context())
fields := []interface{}{
"method", c.Request.Method,
"path", path,
"status", status,
"latency_ms", latency,
"client_ip", c.ClientIP(),
}
if query != "" {
fields = append(fields, "query", query)
}
if errStr := c.Errors.String(); errStr != "" {
fields = append(fields, "errors", errStr)
}
switch {
case status >= 500:
log.Errorw("request completed", fields...)
case status >= 400:
log.Warnw("request completed", fields...)
default:
log.Infow("request completed", fields...)
}
}
}
// GinRecovery 自定义 panic 恢复中间件,使用 zap 记录
func GinRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
log := FromContext(c.Request.Context())
log.Errorw("panic recovered",
"error", err,
"path", c.Request.URL.Path,
"method", c.Request.Method,
"client_ip", c.ClientIP())
c.AbortWithStatus(500)
}
}()
c.Next()
}
}