Files

129 lines
3.7 KiB
Go

package middleware
import (
"net/http"
"strings"
"time"
"hfb_sys/backend/pkg/response"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// 查询请求默认静默;成功写操作、慢请求、服务端错误、限流和有诊断价值的认证失败会输出。
const slowRequestThresholdMs = 500
func RequestLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
latencyMs := float64(time.Since(start).Microseconds()) / 1000
status := c.Writer.Status()
path := c.Request.URL.Path
route := c.FullPath()
authFailure := meaningfulAuthFailure(c)
operation := meaningfulOperation(c.Request.Method, path, status)
if shouldSkipHTTPLog(path, route, status, latencyMs) && !authFailure && !operation {
return
}
fields := []zap.Field{
zap.String("request_id", GetRequestID(c)),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("route", route),
zap.Int("status", status),
zap.String("code", response.CodeFromContext(c)),
zap.Float64("duration_ms", latencyMs),
zap.String("client_ip", c.ClientIP()),
}
if userID, ok := c.Get(ContextUserID); ok {
fields = append(fields, zap.Any("user_id", userID))
}
if adminID, ok := c.Get(ContextAdminID); ok {
fields = append(fields, zap.Any("admin_id", adminID))
}
if reason, ok := c.Get(ContextAuthFailureReason); ok {
fields = append(fields, zap.Any("auth_failure_reason", reason))
}
if source, ok := c.Get(ContextAuthTokenSource); ok {
fields = append(fields, zap.Any("auth_token_source", source))
}
if detail, ok := c.Get(ContextAuthFailureDetail); ok {
fields = append(fields, zap.Any("auth_failure_detail", detail))
}
if tokenVersion, ok := c.Get(ContextAuthTokenVersion); ok {
fields = append(fields, zap.Any("auth_token_version", tokenVersion))
}
if currentVersion, ok := c.Get(ContextAuthCurrentVersion); ok {
fields = append(fields, zap.Any("auth_current_token_version", currentVersion))
}
if len(c.Errors) > 0 {
fields = append(fields,
zap.String("error", c.Errors.Last().Err.Error()),
zap.Int("error_count", len(c.Errors)),
)
}
if stack, ok := c.Get(contextPanicStack); ok {
fields = append(fields, zap.Any("stack", stack))
}
switch {
case status >= 500:
logger.Error("HTTP 请求失败", fields...)
case status == http.StatusTooManyRequests:
logger.Warn("HTTP 请求被限流", fields...)
case authFailure:
logger.Warn("后台认证失败", fields...)
case latencyMs >= slowRequestThresholdMs:
logger.Warn("HTTP 慢请求", fields...)
case operation:
logger.Info("HTTP 操作完成", fields...)
}
}
}
func meaningfulOperation(method, path string, status int) bool {
if status < 200 || status >= 400 {
return false
}
// 支付回调已有更完整的业务结果日志,不再重复记录一条访问日志。
if isPaymentNotifyPath(path) {
return false
}
switch method {
case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
return true
default:
return false
}
}
func isPaymentNotifyPath(path string) bool {
return (strings.Contains(path, "/payment/") || strings.Contains(path, "/payments/")) &&
strings.HasSuffix(strings.TrimSuffix(path, "/"), "/notify")
}
// shouldSkipHTTPLog 判断是否为无需记录的普通请求。
func shouldSkipHTTPLog(_, _ string, status int, latencyMs float64) bool {
if status >= 500 || status == http.StatusTooManyRequests {
return false
}
if latencyMs >= slowRequestThresholdMs {
return false
}
return true
}
func meaningfulAuthFailure(c *gin.Context) bool {
value, ok := c.Get(ContextAuthFailureReason)
if !ok {
return false
}
reason, _ := value.(string)
return reason != "" && reason != "missing"
}