部分重构
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// LogResponseWriter 自定义ResponseWriter以捕获响应内容
|
||||
type LogResponseWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *LogResponseWriter) Write(b []byte) (int, error) {
|
||||
if w.body != nil {
|
||||
w.body.Write(b)
|
||||
}
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (w *LogResponseWriter) WriteString(s string) (int, error) {
|
||||
if w.body != nil {
|
||||
w.body.WriteString(s)
|
||||
}
|
||||
return w.ResponseWriter.WriteString(s)
|
||||
}
|
||||
|
||||
// Logger 请求日志中间件
|
||||
func Logger(logger *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 开始时间
|
||||
startTime := time.Now()
|
||||
|
||||
// 请求方法
|
||||
httpMethod := c.Request.Method
|
||||
|
||||
// 请求路径
|
||||
reqUri := c.Request.RequestURI
|
||||
|
||||
// 客户端IP
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
// 用户代理
|
||||
userAgent := c.Request.UserAgent()
|
||||
|
||||
// 请求ID
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = generateRequestID()
|
||||
c.Set("requestID", requestID)
|
||||
} else {
|
||||
c.Set("requestID", requestID)
|
||||
}
|
||||
|
||||
// 记录原始请求体(如果不是文件上传等大请求)
|
||||
var requestBody []byte
|
||||
if c.Request.ContentLength > 0 && c.Request.ContentLength < 1024*1024 && // 1MB限制
|
||||
c.Request.Header.Get("Content-Type") != "multipart/form-data" {
|
||||
// 读取请求体
|
||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||
if err == nil {
|
||||
requestBody = bodyBytes
|
||||
// 重置请求体以便后续使用
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
|
||||
// 尝试解析JSON
|
||||
var jsonBody interface{}
|
||||
if err := json.Unmarshal(bodyBytes, &jsonBody); err == nil {
|
||||
// 敏感信息过滤(如密码)
|
||||
if m, ok := jsonBody.(map[string]interface{}); ok {
|
||||
if _, exists := m["password"]; exists {
|
||||
m["password"] = "***REDACTED***"
|
||||
}
|
||||
if _, exists := m["oldPassword"]; exists {
|
||||
m["oldPassword"] = "***REDACTED***"
|
||||
}
|
||||
if _, exists := m["newPassword"]; exists {
|
||||
m["newPassword"] = "***REDACTED***"
|
||||
}
|
||||
if _, exists := m["confirmPassword"]; exists {
|
||||
m["confirmPassword"] = "***REDACTED***"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 包装ResponseWriter以捕获响应
|
||||
blw := &LogResponseWriter{
|
||||
ResponseWriter: c.Writer,
|
||||
body: bytes.NewBufferString(""),
|
||||
}
|
||||
c.Writer = blw
|
||||
|
||||
// 处理请求
|
||||
c.Next()
|
||||
|
||||
// 结束时间
|
||||
endTime := time.Now()
|
||||
|
||||
// 执行时间
|
||||
latency := endTime.Sub(startTime)
|
||||
|
||||
// 响应状态码
|
||||
statusCode := c.Writer.Status()
|
||||
|
||||
// 错误信息
|
||||
errors := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
||||
if errors == "" {
|
||||
errors = c.Errors.ByType(gin.ErrorTypePublic).String()
|
||||
}
|
||||
|
||||
// 响应体(如果不是文件等大型响应)
|
||||
var responseBody interface{}
|
||||
var responseMap map[string]interface{}
|
||||
if blw.body != nil && blw.body.Len() > 0 && blw.body.Len() < 10000 { // 10KB限制
|
||||
bodyBytes := blw.body.Bytes()
|
||||
if err := json.Unmarshal(bodyBytes, &responseMap); err == nil {
|
||||
responseBody = responseMap
|
||||
} else {
|
||||
responseBody = string(bodyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据状态码决定日志级别
|
||||
fields := []zap.Field{
|
||||
zap.String("request_id", requestID),
|
||||
zap.String("method", httpMethod),
|
||||
zap.String("uri", reqUri),
|
||||
zap.String("client_ip", clientIP),
|
||||
zap.String("user_agent", userAgent),
|
||||
zap.Int("status", statusCode),
|
||||
zap.Duration("latency", latency),
|
||||
}
|
||||
|
||||
// 添加请求体(如果存在且不是太大)
|
||||
if len(requestBody) > 0 && len(requestBody) < 10000 {
|
||||
var reqBody interface{}
|
||||
if err := json.Unmarshal(requestBody, &reqBody); err == nil {
|
||||
fields = append(fields, zap.Any("request_body", reqBody))
|
||||
}
|
||||
}
|
||||
|
||||
// 添加响应体(如果存在且不是太大)
|
||||
if responseBody != nil {
|
||||
fields = append(fields, zap.Any("response_body", responseBody))
|
||||
}
|
||||
|
||||
// 添加错误信息
|
||||
if errors != "" {
|
||||
fields = append(fields, zap.String("error", errors))
|
||||
}
|
||||
|
||||
// 获取用户标识(如果有)
|
||||
if cookieValue := GetCookieValue(c); cookieValue != "" {
|
||||
fields = append(fields, zap.String("auth_cookie_truncated", truncateString(cookieValue, 8)))
|
||||
}
|
||||
if authToken := GetAuthToken(c); authToken != "" {
|
||||
fields = append(fields, zap.String("auth_token_truncated", truncateString(authToken, 8)))
|
||||
}
|
||||
|
||||
// 记录日志
|
||||
logFunc := logger.Info
|
||||
if statusCode >= 400 && statusCode < 500 {
|
||||
logFunc = logger.Warn
|
||||
} else if statusCode >= 500 {
|
||||
logFunc = logger.Error
|
||||
}
|
||||
|
||||
logFunc("HTTP request", fields...)
|
||||
}
|
||||
}
|
||||
|
||||
// Recovery 恢复中间件
|
||||
func Recovery(logger *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// 获取请求ID
|
||||
requestID, _ := c.Get("requestID")
|
||||
|
||||
// 记录Panic
|
||||
logger.Error("HTTP panic recovered",
|
||||
zap.Any("error", err),
|
||||
zap.String("request_id", requestID.(string)),
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("uri", c.Request.RequestURI),
|
||||
zap.String("client_ip", c.ClientIP()),
|
||||
)
|
||||
|
||||
// 返回500错误
|
||||
c.JSON(500, gin.H{
|
||||
"code": "500",
|
||||
"message": "Internal server error",
|
||||
"data": nil,
|
||||
})
|
||||
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func generateRequestID() string {
|
||||
return time.Now().Format("20060102150405") + "-" + shortRandString()
|
||||
}
|
||||
|
||||
func truncateString(s string, length int) string {
|
||||
if len(s) <= length {
|
||||
return s
|
||||
}
|
||||
return s[:length] + "..."
|
||||
}
|
||||
|
||||
func shortRandString() string {
|
||||
// 简化的随机字符串生成
|
||||
return time.Now().Format("150405")
|
||||
}
|
||||
|
||||
// SimpleLogger 简易日志中间件(用于开发和测试)
|
||||
func SimpleLogger() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
|
||||
// 处理请求
|
||||
c.Next()
|
||||
|
||||
// 记录请求信息
|
||||
latency := time.Since(start)
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
statusCode := c.Writer.Status()
|
||||
path := c.Request.URL.Path
|
||||
|
||||
// 输出到控制台
|
||||
fmt.Printf("[GIN] %v | %3d | %13v | %15s | %-7s %s\n",
|
||||
time.Now().Format("2006/01/02 - 15:04:05"),
|
||||
statusCode,
|
||||
latency,
|
||||
clientIP,
|
||||
method,
|
||||
path,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user