85 lines
2.3 KiB
Go
85 lines
2.3 KiB
Go
// 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")
|
|
}
|
|
}
|
|
}
|