// Filename: internal/middleware/log_redaction.go package middleware import ( "bytes" "io" "regexp" "time" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) const RedactedBodyKey = "redactedBody" const RedactedAuthHeaderKey = "redactedAuthHeader" const RedactedValue = `"[REDACTED]"` func RedactionMiddleware() gin.HandlerFunc { // Pre-compile regex for efficiency jsonKeyPattern := regexp.MustCompile(`("api_key"|"keys")\s*:\s*"[^"]*"`) bearerTokenPattern := regexp.MustCompile(`^(Bearer\s+)\S+$`) return func(c *gin.Context) { // --- 1. Redact Request Body --- if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" { if bodyBytes, err := io.ReadAll(c.Request.Body); err == nil { c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) bodyString := string(bodyBytes) redactedBody := jsonKeyPattern.ReplaceAllString(bodyString, `$1:`+RedactedValue) c.Set(RedactedBodyKey, redactedBody) } } // --- 2. Redact Authorization Header --- authHeader := c.GetHeader("Authorization") if authHeader != "" { if bearerTokenPattern.MatchString(authHeader) { redactedHeader := bearerTokenPattern.ReplaceAllString(authHeader, `${1}[REDACTED]`) c.Set(RedactedAuthHeaderKey, redactedHeader) } } c.Next() } } // LogrusLogger is a Gin middleware that logs requests using a Logrus logger. // It consumes redacted data prepared by the RedactionMiddleware. func LogrusLogger(logger *logrus.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path // Process request c.Next() // After request, gather data and log latency := time.Since(start) statusCode := c.Writer.Status() entry := logger.WithFields(logrus.Fields{ "status_code": statusCode, "latency_ms": latency.Milliseconds(), "client_ip": c.ClientIP(), "method": c.Request.Method, "path": path, }) if redactedBody, exists := c.Get(RedactedBodyKey); exists { entry = entry.WithField("body", redactedBody) } if redactedAuth, exists := c.Get(RedactedAuthHeaderKey); exists { entry = entry.WithField("authorization", redactedAuth) } if len(c.Errors) > 0 { entry.Error(c.Errors.String()) } else { entry.Info("request handled") } } }