Files
tyapi-server/internal/shared/middleware/comprehensive_logger.go
2025-07-28 15:21:37 +08:00

442 lines
12 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 (
"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 ""
}