442 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			442 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package middleware | |||
|  | 
 | |||
|  | import ( | |||
|  | 	"bytes" | |||
|  | 	"context" | |||
|  | 	"io" | |||
|  | 	"strings" | |||
|  | 	"time" | |||
|  | 
 | |||
|  | 	"github.com/gin-gonic/gin" | |||
|  | 	"go.uber.org/zap" | |||
|  | ) | |||
|  | 
 | |||
|  | // ComprehensiveLoggerMiddleware 全面日志中间件 | |||
|  | type ComprehensiveLoggerMiddleware struct { | |||
|  | 	logger *zap.Logger | |||
|  | 	config *ComprehensiveLoggerConfig | |||
|  | } | |||
|  | 
 | |||
|  | // ComprehensiveLoggerConfig 全面日志配置 | |||
|  | type ComprehensiveLoggerConfig struct { | |||
|  | 	EnableRequestLogging    bool // 是否记录请求日志 | |||
|  | 	EnableResponseLogging   bool // 是否记录响应日志 | |||
|  | 	EnableRequestBodyLogging bool // 是否记录请求体 | |||
|  | 	EnableErrorLogging      bool // 是否记录错误日志 | |||
|  | 	EnableBusinessLogging   bool // 是否记录业务日志 | |||
|  | 	EnablePerformanceLogging bool // 是否记录性能日志 | |||
|  | 	MaxBodySize             int64 // 最大记录体大小 | |||
|  | 	ExcludePaths            []string // 排除的路径 | |||
|  | 	IncludePaths            []string // 包含的路径 | |||
|  | } | |||
|  | 
 | |||
|  | // NewComprehensiveLoggerMiddleware 创建全面日志中间件 | |||
|  | func NewComprehensiveLoggerMiddleware(logger *zap.Logger, config *ComprehensiveLoggerConfig) *ComprehensiveLoggerMiddleware { | |||
|  | 	if config == nil { | |||
|  | 		config = &ComprehensiveLoggerConfig{ | |||
|  | 			EnableRequestLogging:     true, | |||
|  | 			EnableResponseLogging:    true, | |||
|  | 			EnableRequestBodyLogging: false, // 生产环境默认关闭 | |||
|  | 			EnableErrorLogging:       true, | |||
|  | 			EnableBusinessLogging:    true, | |||
|  | 			EnablePerformanceLogging: true, | |||
|  | 			MaxBodySize:              1024 * 10, // 10KB | |||
|  | 			ExcludePaths:             []string{"/health", "/metrics", "/favicon.ico"}, | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return &ComprehensiveLoggerMiddleware{ | |||
|  | 		logger: logger, | |||
|  | 		config: config, | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // GetName 返回中间件名称 | |||
|  | func (m *ComprehensiveLoggerMiddleware) GetName() string { | |||
|  | 	return "comprehensive_logger" | |||
|  | } | |||
|  | 
 | |||
|  | // GetPriority 返回中间件优先级 | |||
|  | func (m *ComprehensiveLoggerMiddleware) GetPriority() int { | |||
|  | 	return 90 // 高优先级,在panic恢复之后 | |||
|  | } | |||
|  | 
 | |||
|  | // Handle 返回中间件处理函数 | |||
|  | func (m *ComprehensiveLoggerMiddleware) Handle() gin.HandlerFunc { | |||
|  | 	return func(c *gin.Context) { | |||
|  | 		// 检查是否应该记录此路径 | |||
|  | 		if !m.shouldLogPath(c.Request.URL.Path) { | |||
|  | 			c.Next() | |||
|  | 			return | |||
|  | 		} | |||
|  | 
 | |||
|  | 		startTime := time.Now() | |||
|  | 		requestID := c.GetString("request_id") | |||
|  | 		traceID := c.GetString("trace_id") | |||
|  | 		userID := c.GetString("user_id") | |||
|  | 
 | |||
|  | 		// 记录请求开始 | |||
|  | 		if m.config.EnableRequestLogging { | |||
|  | 			m.logRequest(c, startTime, requestID, traceID, userID) | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// 捕获请求体(如果需要) | |||
|  | 		var requestBody []byte | |||
|  | 		if m.config.EnableRequestBodyLogging && m.shouldLogRequestBody(c) { | |||
|  | 			requestBody = m.captureRequestBody(c) | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// 创建响应写入器包装器 | |||
|  | 		responseWriter := &responseWriterWrapper{ | |||
|  | 			ResponseWriter: c.Writer, | |||
|  | 			logger:         m.logger, | |||
|  | 			config:         m.config, | |||
|  | 			requestID:      requestID, | |||
|  | 			traceID:        traceID, | |||
|  | 			userID:         userID, | |||
|  | 			startTime:      startTime, | |||
|  | 			path:           c.Request.URL.Path, | |||
|  | 			method:         c.Request.Method, | |||
|  | 		} | |||
|  | 		c.Writer = responseWriter | |||
|  | 
 | |||
|  | 		// 处理请求 | |||
|  | 		c.Next() | |||
|  | 
 | |||
|  | 		// 记录响应 | |||
|  | 		if m.config.EnableResponseLogging { | |||
|  | 			m.logResponse(c, responseWriter, startTime, requestID, traceID, userID, requestBody) | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// 记录错误 | |||
|  | 		if m.config.EnableErrorLogging && len(c.Errors) > 0 { | |||
|  | 			m.logErrors(c, requestID, traceID, userID) | |||
|  | 		} | |||
|  | 
 | |||
|  | 		// 记录性能指标 | |||
|  | 		if m.config.EnablePerformanceLogging { | |||
|  | 			m.logPerformance(c, startTime, requestID, traceID, userID) | |||
|  | 		} | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // IsGlobal 是否为全局中间件 | |||
|  | func (m *ComprehensiveLoggerMiddleware) IsGlobal() bool { | |||
|  | 	return true | |||
|  | } | |||
|  | 
 | |||
|  | // shouldLogPath 检查是否应该记录此路径 | |||
|  | func (m *ComprehensiveLoggerMiddleware) shouldLogPath(path string) bool { | |||
|  | 	// 检查排除路径 | |||
|  | 	for _, excludePath := range m.config.ExcludePaths { | |||
|  | 		if strings.HasPrefix(path, excludePath) { | |||
|  | 			return false | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 检查包含路径(如果指定了) | |||
|  | 	if len(m.config.IncludePaths) > 0 { | |||
|  | 		for _, includePath := range m.config.IncludePaths { | |||
|  | 			if strings.HasPrefix(path, includePath) { | |||
|  | 				return true | |||
|  | 			} | |||
|  | 		} | |||
|  | 		return false | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return true | |||
|  | } | |||
|  | 
 | |||
|  | // shouldLogRequestBody 检查是否应该记录请求体 | |||
|  | func (m *ComprehensiveLoggerMiddleware) shouldLogRequestBody(c *gin.Context) bool { | |||
|  | 	contentType := c.GetHeader("Content-Type") | |||
|  | 	return strings.Contains(contentType, "application/json") || | |||
|  | 		strings.Contains(contentType, "application/x-www-form-urlencoded") | |||
|  | } | |||
|  | 
 | |||
|  | // captureRequestBody 捕获请求体 | |||
|  | func (m *ComprehensiveLoggerMiddleware) captureRequestBody(c *gin.Context) []byte { | |||
|  | 	if c.Request.Body == nil { | |||
|  | 		return nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	body, err := io.ReadAll(c.Request.Body) | |||
|  | 	if err != nil { | |||
|  | 		return nil | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 重新设置body | |||
|  | 	c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) | |||
|  | 
 | |||
|  | 	// 限制大小 | |||
|  | 	if int64(len(body)) > m.config.MaxBodySize { | |||
|  | 		return body[:m.config.MaxBodySize] | |||
|  | 	} | |||
|  | 
 | |||
|  | 	return body | |||
|  | } | |||
|  | 
 | |||
|  | // logRequest 记录请求日志 | |||
|  | func (m *ComprehensiveLoggerMiddleware) logRequest(c *gin.Context, startTime time.Time, requestID, traceID, userID string) { | |||
|  | 	logFields := []zap.Field{ | |||
|  | 		zap.String("log_type", "request"), | |||
|  | 		zap.String("request_id", requestID), | |||
|  | 		zap.String("trace_id", traceID), | |||
|  | 		zap.String("user_id", userID), | |||
|  | 		zap.String("method", c.Request.Method), | |||
|  | 		zap.String("path", c.Request.URL.Path), | |||
|  | 		zap.String("query", c.Request.URL.RawQuery), | |||
|  | 		zap.String("client_ip", c.ClientIP()), | |||
|  | 		zap.String("user_agent", c.Request.UserAgent()), | |||
|  | 		zap.String("referer", c.Request.Referer()), | |||
|  | 		zap.Int64("content_length", c.Request.ContentLength), | |||
|  | 		zap.String("content_type", c.GetHeader("Content-Type")), | |||
|  | 		zap.Time("timestamp", startTime), | |||
|  | 	} | |||
|  | 
 | |||
|  | 	m.logger.Info("收到HTTP请求", logFields...) | |||
|  | } | |||
|  | 
 | |||
|  | // logResponse 记录响应日志 | |||
|  | func (m *ComprehensiveLoggerMiddleware) logResponse(c *gin.Context, responseWriter *responseWriterWrapper, startTime time.Time, requestID, traceID, userID string, requestBody []byte) { | |||
|  | 	duration := time.Since(startTime) | |||
|  | 	statusCode := responseWriter.Status() | |||
|  | 
 | |||
|  | 	logFields := []zap.Field{ | |||
|  | 		zap.String("log_type", "response"), | |||
|  | 		zap.String("request_id", requestID), | |||
|  | 		zap.String("trace_id", traceID), | |||
|  | 		zap.String("user_id", userID), | |||
|  | 		zap.String("method", c.Request.Method), | |||
|  | 		zap.String("path", c.Request.URL.Path), | |||
|  | 		zap.Int("status_code", statusCode), | |||
|  | 		zap.Duration("duration", duration), | |||
|  | 		zap.Int("response_size", responseWriter.Size()), | |||
|  | 		zap.Time("timestamp", time.Now()), | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 添加请求体(如果记录了) | |||
|  | 	if len(requestBody) > 0 { | |||
|  | 		logFields = append(logFields, zap.String("request_body", string(requestBody))) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 根据状态码选择日志级别 | |||
|  | 	if statusCode >= 500 { | |||
|  | 		m.logger.Error("HTTP响应错误", logFields...) | |||
|  | 	} else if statusCode >= 400 { | |||
|  | 		m.logger.Warn("HTTP响应警告", logFields...) | |||
|  | 	} else { | |||
|  | 		m.logger.Info("HTTP响应成功", logFields...) | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // logErrors 记录错误日志 | |||
|  | func (m *ComprehensiveLoggerMiddleware) logErrors(c *gin.Context, requestID, traceID, userID string) { | |||
|  | 	for _, ginErr := range c.Errors { | |||
|  | 		logFields := []zap.Field{ | |||
|  | 			zap.String("log_type", "error"), | |||
|  | 			zap.String("request_id", requestID), | |||
|  | 			zap.String("trace_id", traceID), | |||
|  | 			zap.String("user_id", userID), | |||
|  | 			zap.String("method", c.Request.Method), | |||
|  | 			zap.String("path", c.Request.URL.Path), | |||
|  | 			zap.String("error_type", string(ginErr.Type)), | |||
|  | 			zap.Error(ginErr.Err), | |||
|  | 			zap.Time("timestamp", time.Now()), | |||
|  | 		} | |||
|  | 
 | |||
|  | 		m.logger.Error("请求处理错误", logFields...) | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // logPerformance 记录性能日志 | |||
|  | func (m *ComprehensiveLoggerMiddleware) logPerformance(c *gin.Context, startTime time.Time, requestID, traceID, userID string) { | |||
|  | 	duration := time.Since(startTime) | |||
|  | 
 | |||
|  | 	// 记录慢请求 | |||
|  | 	if duration > 1*time.Second { | |||
|  | 		logFields := []zap.Field{ | |||
|  | 			zap.String("log_type", "performance"), | |||
|  | 			zap.String("performance_type", "slow_request"), | |||
|  | 			zap.String("request_id", requestID), | |||
|  | 			zap.String("trace_id", traceID), | |||
|  | 			zap.String("user_id", userID), | |||
|  | 			zap.String("method", c.Request.Method), | |||
|  | 			zap.String("path", c.Request.URL.Path), | |||
|  | 			zap.Duration("duration", duration), | |||
|  | 			zap.Time("timestamp", time.Now()), | |||
|  | 		} | |||
|  | 
 | |||
|  | 		m.logger.Warn("检测到慢请求", logFields...) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 记录性能指标 | |||
|  | 	logFields := []zap.Field{ | |||
|  | 		zap.String("log_type", "performance"), | |||
|  | 		zap.String("performance_type", "request_metrics"), | |||
|  | 		zap.String("request_id", requestID), | |||
|  | 		zap.String("trace_id", traceID), | |||
|  | 		zap.String("user_id", userID), | |||
|  | 		zap.String("method", c.Request.Method), | |||
|  | 		zap.String("path", c.Request.URL.Path), | |||
|  | 		zap.Duration("duration", duration), | |||
|  | 		zap.Time("timestamp", time.Now()), | |||
|  | 	} | |||
|  | 
 | |||
|  | 	m.logger.Debug("请求性能指标", logFields...) | |||
|  | } | |||
|  | 
 | |||
|  | // responseWriterWrapper 响应写入器包装器 | |||
|  | type responseWriterWrapper struct { | |||
|  | 	gin.ResponseWriter | |||
|  | 	logger    *zap.Logger | |||
|  | 	config    *ComprehensiveLoggerConfig | |||
|  | 	requestID string | |||
|  | 	traceID   string | |||
|  | 	userID    string | |||
|  | 	startTime time.Time | |||
|  | 	path      string | |||
|  | 	method    string | |||
|  | 	status    int | |||
|  | 	size      int | |||
|  | } | |||
|  | 
 | |||
|  | // Write 实现Write方法 | |||
|  | func (w *responseWriterWrapper) Write(b []byte) (int, error) { | |||
|  | 	size, err := w.ResponseWriter.Write(b) | |||
|  | 	w.size += size | |||
|  | 	return size, err | |||
|  | } | |||
|  | 
 | |||
|  | // WriteHeader 实现WriteHeader方法 | |||
|  | func (w *responseWriterWrapper) WriteHeader(code int) { | |||
|  | 	w.status = code | |||
|  | 	w.ResponseWriter.WriteHeader(code) | |||
|  | } | |||
|  | 
 | |||
|  | // WriteString 实现WriteString方法 | |||
|  | func (w *responseWriterWrapper) WriteString(s string) (int, error) { | |||
|  | 	size, err := w.ResponseWriter.WriteString(s) | |||
|  | 	w.size += size | |||
|  | 	return size, err | |||
|  | } | |||
|  | 
 | |||
|  | // Status 获取状态码 | |||
|  | func (w *responseWriterWrapper) Status() int { | |||
|  | 	return w.status | |||
|  | } | |||
|  | 
 | |||
|  | // Size 获取响应大小 | |||
|  | func (w *responseWriterWrapper) Size() int { | |||
|  | 	return w.size | |||
|  | } | |||
|  | 
 | |||
|  | // BusinessLogger 业务日志记录器 | |||
|  | type BusinessLogger struct { | |||
|  | 	logger *zap.Logger | |||
|  | } | |||
|  | 
 | |||
|  | // NewBusinessLogger 创建业务日志记录器 | |||
|  | func NewBusinessLogger(logger *zap.Logger) *BusinessLogger { | |||
|  | 	return &BusinessLogger{ | |||
|  | 		logger: logger, | |||
|  | 	} | |||
|  | } | |||
|  | 
 | |||
|  | // LogUserAction 记录用户操作 | |||
|  | func (bl *BusinessLogger) LogUserAction(ctx context.Context, action string, details map[string]interface{}) { | |||
|  | 	requestID := bl.getRequestIDFromContext(ctx) | |||
|  | 	traceID := bl.getTraceIDFromContext(ctx) | |||
|  | 	userID := bl.getUserIDFromContext(ctx) | |||
|  | 
 | |||
|  | 	logFields := []zap.Field{ | |||
|  | 		zap.String("log_type", "business"), | |||
|  | 		zap.String("business_type", "user_action"), | |||
|  | 		zap.String("action", action), | |||
|  | 		zap.String("request_id", requestID), | |||
|  | 		zap.String("trace_id", traceID), | |||
|  | 		zap.String("user_id", userID), | |||
|  | 		zap.Time("timestamp", time.Now()), | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 添加详细信息 | |||
|  | 	for key, value := range details { | |||
|  | 		logFields = append(logFields, zap.Any(key, value)) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	bl.logger.Info("用户操作记录", logFields...) | |||
|  | } | |||
|  | 
 | |||
|  | // LogBusinessEvent 记录业务事件 | |||
|  | func (bl *BusinessLogger) LogBusinessEvent(ctx context.Context, event string, details map[string]interface{}) { | |||
|  | 	requestID := bl.getRequestIDFromContext(ctx) | |||
|  | 	traceID := bl.getTraceIDFromContext(ctx) | |||
|  | 	userID := bl.getUserIDFromContext(ctx) | |||
|  | 
 | |||
|  | 	logFields := []zap.Field{ | |||
|  | 		zap.String("log_type", "business"), | |||
|  | 		zap.String("business_type", "business_event"), | |||
|  | 		zap.String("event", event), | |||
|  | 		zap.String("request_id", requestID), | |||
|  | 		zap.String("trace_id", traceID), | |||
|  | 		zap.String("user_id", userID), | |||
|  | 		zap.Time("timestamp", time.Now()), | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 添加详细信息 | |||
|  | 	for key, value := range details { | |||
|  | 		logFields = append(logFields, zap.Any(key, value)) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	bl.logger.Info("业务事件记录", logFields...) | |||
|  | } | |||
|  | 
 | |||
|  | // LogSystemEvent 记录系统事件 | |||
|  | func (bl *BusinessLogger) LogSystemEvent(ctx context.Context, event string, details map[string]interface{}) { | |||
|  | 	requestID := bl.getRequestIDFromContext(ctx) | |||
|  | 	traceID := bl.getTraceIDFromContext(ctx) | |||
|  | 
 | |||
|  | 	logFields := []zap.Field{ | |||
|  | 		zap.String("log_type", "business"), | |||
|  | 		zap.String("business_type", "system_event"), | |||
|  | 		zap.String("event", event), | |||
|  | 		zap.String("request_id", requestID), | |||
|  | 		zap.String("trace_id", traceID), | |||
|  | 		zap.Time("timestamp", time.Now()), | |||
|  | 	} | |||
|  | 
 | |||
|  | 	// 添加详细信息 | |||
|  | 	for key, value := range details { | |||
|  | 		logFields = append(logFields, zap.Any(key, value)) | |||
|  | 	} | |||
|  | 
 | |||
|  | 	bl.logger.Info("系统事件记录", logFields...) | |||
|  | } | |||
|  | 
 | |||
|  | // 辅助方法 | |||
|  | func (bl *BusinessLogger) getRequestIDFromContext(ctx context.Context) string { | |||
|  | 	if requestID := ctx.Value("request_id"); requestID != nil { | |||
|  | 		if id, ok := requestID.(string); ok { | |||
|  | 			return id | |||
|  | 		} | |||
|  | 	} | |||
|  | 	return "" | |||
|  | } | |||
|  | 
 | |||
|  | func (bl *BusinessLogger) getTraceIDFromContext(ctx context.Context) string { | |||
|  | 	if traceID := ctx.Value("trace_id"); traceID != nil { | |||
|  | 		if id, ok := traceID.(string); ok { | |||
|  | 			return id | |||
|  | 		} | |||
|  | 	} | |||
|  | 	return "" | |||
|  | } | |||
|  | 
 | |||
|  | func (bl *BusinessLogger) getUserIDFromContext(ctx context.Context) string { | |||
|  | 	if userID := ctx.Value("user_id"); userID != nil { | |||
|  | 		if id, ok := userID.(string); ok { | |||
|  | 			return id | |||
|  | 		} | |||
|  | 	} | |||
|  | 	return "" | |||
|  | }  |