feat: 实现日志追踪链路 #185
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/hhs/camtalk/internal/ratelimit"
|
"github.com/hhs/camtalk/internal/ratelimit"
|
||||||
"github.com/hhs/camtalk/internal/session"
|
"github.com/hhs/camtalk/internal/session"
|
||||||
"github.com/hhs/camtalk/internal/store"
|
"github.com/hhs/camtalk/internal/store"
|
||||||
|
"github.com/hhs/camtalk/internal/trace"
|
||||||
"github.com/hhs/camtalk/internal/ws"
|
"github.com/hhs/camtalk/internal/ws"
|
||||||
migrations "github.com/hhs/camtalk/migrations"
|
migrations "github.com/hhs/camtalk/migrations"
|
||||||
)
|
)
|
||||||
@@ -226,7 +227,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(gin.Recovery())
|
r.Use(trace.TraceMiddleware()) // 第一层:生成 trace ID
|
||||||
|
r.Use(trace.GinLogger()) // 第二层:记录请求
|
||||||
|
r.Use(trace.GinRecovery()) // 第三层:panic 恢复
|
||||||
|
|
||||||
// REST API
|
// REST API
|
||||||
apiGroup := r.Group("/api")
|
apiGroup := r.Group("/api")
|
||||||
|
|||||||
63
backend/internal/trace/gin_logger.go
Normal file
63
backend/internal/trace/gin_logger.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user