change log
This commit is contained in:
442
internal/shared/middleware/comprehensive_logger.go
Normal file
442
internal/shared/middleware/comprehensive_logger.go
Normal file
@@ -0,0 +1,442 @@
|
||||
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 ""
|
||||
}
|
||||
104
internal/shared/middleware/panic_recovery.go
Normal file
104
internal/shared/middleware/panic_recovery.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// PanicRecoveryMiddleware Panic恢复中间件
|
||||
type PanicRecoveryMiddleware struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewPanicRecoveryMiddleware 创建Panic恢复中间件
|
||||
func NewPanicRecoveryMiddleware(logger *zap.Logger) *PanicRecoveryMiddleware {
|
||||
return &PanicRecoveryMiddleware{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetName 返回中间件名称
|
||||
func (m *PanicRecoveryMiddleware) GetName() string {
|
||||
return "panic_recovery"
|
||||
}
|
||||
|
||||
// GetPriority 返回中间件优先级
|
||||
func (m *PanicRecoveryMiddleware) GetPriority() int {
|
||||
return 100 // 最高优先级,第一个执行
|
||||
}
|
||||
|
||||
// Handle 返回中间件处理函数
|
||||
func (m *PanicRecoveryMiddleware) Handle() gin.HandlerFunc {
|
||||
return gin.RecoveryWithWriter(&panicLogger{logger: m.logger})
|
||||
}
|
||||
|
||||
// IsGlobal 是否为全局中间件
|
||||
func (m *PanicRecoveryMiddleware) IsGlobal() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// panicLogger 实现io.Writer接口,用于记录panic信息
|
||||
type panicLogger struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// Write 实现io.Writer接口
|
||||
func (pl *panicLogger) Write(p []byte) (n int, err error) {
|
||||
pl.logger.Error("系统发生严重错误",
|
||||
zap.String("error_type", "panic"),
|
||||
zap.String("stack_trace", string(p)),
|
||||
zap.String("timestamp", time.Now().Format("2006-01-02 15:04:05")),
|
||||
)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// CustomPanicRecovery 自定义panic恢复中间件
|
||||
func (m *PanicRecoveryMiddleware) CustomPanicRecovery() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// 获取请求信息
|
||||
requestID := c.GetString("request_id")
|
||||
traceID := c.GetString("trace_id")
|
||||
userID := c.GetString("user_id")
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
path := c.Request.URL.Path
|
||||
userAgent := c.Request.UserAgent()
|
||||
|
||||
// 记录详细的panic信息
|
||||
m.logger.Error("系统发生严重错误",
|
||||
zap.Any("panic_error", err),
|
||||
zap.String("error_type", "panic"),
|
||||
zap.String("request_id", requestID),
|
||||
zap.String("trace_id", traceID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("client_ip", clientIP),
|
||||
zap.String("method", method),
|
||||
zap.String("path", path),
|
||||
zap.String("user_agent", userAgent),
|
||||
zap.String("stack_trace", string(debug.Stack())),
|
||||
zap.String("timestamp", time.Now().Format("2006-01-02 15:04:05")),
|
||||
)
|
||||
|
||||
// 返回500错误响应
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
"message": "服务器内部错误",
|
||||
"error_code": "INTERNAL_SERVER_ERROR",
|
||||
"request_id": requestID,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
|
||||
// 中止请求处理
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user