Files
sub2api/backend/internal/server/middleware/logger.go
yangjianbo b6aaee01ce fix(logging): 修复 warn 级别下系统日志空白问题
- 新增 logger.WriteSinkEvent,支持旁路写入 sink,不受全局级别门控影响\n- 在 http.access 中间件中,当 info 被门控时补写 sink,保障 Ops 系统日志可索引\n- 增加 level=warn 场景回归测试,验证访问日志仍可入库
2026-02-12 19:19:11 +08:00

97 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package middleware
import (
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// Logger 请求日志中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
// 开始时间
startTime := time.Now()
// 请求路径
path := c.Request.URL.Path
// 处理请求
c.Next()
// 跳过健康检查等高频探针路径的日志
if path == "/health" || path == "/setup/status" {
return
}
endTime := time.Now()
latency := endTime.Sub(startTime)
method := c.Request.Method
statusCode := c.Writer.Status()
clientIP := c.ClientIP()
protocol := c.Request.Proto
accountID, hasAccountID := c.Request.Context().Value(ctxkey.AccountID).(int64)
platform, _ := c.Request.Context().Value(ctxkey.Platform).(string)
model, _ := c.Request.Context().Value(ctxkey.Model).(string)
fields := []zap.Field{
zap.String("component", "http.access"),
zap.Int("status_code", statusCode),
zap.Int64("latency_ms", latency.Milliseconds()),
zap.String("client_ip", clientIP),
zap.String("protocol", protocol),
zap.String("method", method),
zap.String("path", path),
}
if hasAccountID && accountID > 0 {
fields = append(fields, zap.Int64("account_id", accountID))
}
if platform != "" {
fields = append(fields, zap.String("platform", platform))
}
if model != "" {
fields = append(fields, zap.String("model", model))
}
l := logger.FromContext(c.Request.Context()).With(fields...)
l.Info("http request completed", zap.Time("completed_at", endTime))
// 当全局日志级别高于 info如 warn/erroraccess info 不会进入 zap core
// 这里补写一次 sink保证 ops 系统日志仍可索引关键访问轨迹。
if !logger.L().Core().Enabled(logger.LevelInfo) {
sinkFields := map[string]any{
"component": "http.access",
"status_code": statusCode,
"latency_ms": latency.Milliseconds(),
"client_ip": clientIP,
"protocol": protocol,
"method": method,
"path": path,
"completed_at": endTime,
}
if requestID, ok := c.Request.Context().Value(ctxkey.RequestID).(string); ok && requestID != "" {
sinkFields["request_id"] = requestID
}
if clientRequestID, ok := c.Request.Context().Value(ctxkey.ClientRequestID).(string); ok && clientRequestID != "" {
sinkFields["client_request_id"] = clientRequestID
}
if hasAccountID && accountID > 0 {
sinkFields["account_id"] = accountID
}
if platform != "" {
sinkFields["platform"] = platform
}
if model != "" {
sinkFields["model"] = model
}
logger.WriteSinkEvent("info", "http.access", "http request completed", sinkFields)
}
if len(c.Errors) > 0 {
l.Warn("http request contains gin errors", zap.String("errors", c.Errors.String()))
}
}
}