mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
954a1a1080
- Replace Redis with DragonflyDB for better performance and memory efficiency - Remove redundant environment variables (POSTGRES_*, ENCRYPTION_KEY, OAUTH_SERVICE_URL) - Consolidate database configuration to use single DB_* variables - Use JWT_SECRET for both JWT tokens and encryption - Remove PORT variable redundancy, use BACKEND_PORT consistently - Clean up docker-compose configurations for dev/prod consistency - Add DragonflyDB configuration with optimized memory usage - Remove redis.conf as it's no longer needed - Update health checks to use Redis-compatible CLI for DragonflyDB - Add missing VITE_API_URL to production frontend - Fix GitHub Actions to use correct go.sum path - Clean up development directories and unused files
275 lines
6.9 KiB
Go
275 lines
6.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/trackeep/backend/config"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// LoggerConfig holds configuration for the logger
|
|
type LoggerConfig struct {
|
|
LogFile string
|
|
LogLevel string
|
|
EnableJSON bool
|
|
}
|
|
|
|
// GetLogger returns the logger instance
|
|
func (lc LoggerConfig) GetLogger() *zap.Logger {
|
|
return config.GetLogger()
|
|
}
|
|
|
|
// Logger returns a middleware that logs HTTP requests using Zap
|
|
func Logger(config LoggerConfig) gin.HandlerFunc {
|
|
logger := config.GetLogger()
|
|
|
|
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
|
// Create log entry
|
|
entry := map[string]interface{}{
|
|
"timestamp": param.TimeStamp.Format(time.RFC3339),
|
|
"method": param.Method,
|
|
"path": param.Path,
|
|
"status": param.StatusCode,
|
|
"latency": param.Latency.String(),
|
|
"client_ip": param.ClientIP,
|
|
"user_agent": param.Request.UserAgent(),
|
|
"request_id": param.Request.Header.Get("X-Request-ID"),
|
|
}
|
|
|
|
// Add user ID if available
|
|
var userID interface{}
|
|
if uid, exists := param.Keys["user_id"]; exists {
|
|
userID = uid
|
|
entry["user_id"] = userID
|
|
}
|
|
|
|
// Add error if present
|
|
if param.ErrorMessage != "" {
|
|
entry["error"] = param.ErrorMessage
|
|
}
|
|
|
|
// Log with Zap
|
|
if param.ErrorMessage != "" {
|
|
logger.Error("HTTP request",
|
|
zap.String("method", param.Method),
|
|
zap.String("path", param.Path),
|
|
zap.Int("status", param.StatusCode),
|
|
zap.Duration("latency", param.Latency),
|
|
zap.String("client_ip", param.ClientIP),
|
|
zap.String("user_agent", param.Request.UserAgent()),
|
|
zap.Any("user_id", userID),
|
|
zap.String("error", param.ErrorMessage),
|
|
)
|
|
} else {
|
|
logger.Info("HTTP request",
|
|
zap.String("method", param.Method),
|
|
zap.String("path", param.Path),
|
|
zap.Int("status", param.StatusCode),
|
|
zap.Duration("latency", param.Latency),
|
|
zap.String("client_ip", param.ClientIP),
|
|
zap.String("user_agent", param.Request.UserAgent()),
|
|
zap.Any("user_id", userID),
|
|
)
|
|
}
|
|
|
|
// Return empty string since Zap handles output
|
|
return ""
|
|
})
|
|
}
|
|
|
|
// RequestLogger logs detailed request information using Zap
|
|
func RequestLogger() gin.HandlerFunc {
|
|
logger := config.GetLogger()
|
|
|
|
return func(c *gin.Context) {
|
|
start := time.Now()
|
|
path := c.Request.URL.Path
|
|
raw := c.Request.URL.RawQuery
|
|
|
|
// Process request
|
|
c.Next()
|
|
|
|
// Skip logging for health checks
|
|
if path == "/health" {
|
|
return
|
|
}
|
|
|
|
// Calculate latency
|
|
latency := time.Since(start)
|
|
|
|
// Get request ID
|
|
requestID := c.GetHeader("X-Request-ID")
|
|
if requestID == "" {
|
|
requestID = generateRequestID()
|
|
}
|
|
|
|
// Get user ID if authenticated
|
|
var userID interface{}
|
|
if uid, exists := c.Get("user_id"); exists {
|
|
userID = uid
|
|
}
|
|
|
|
// Log request body for POST/PUT requests (excluding sensitive data)
|
|
var requestBody string
|
|
if c.Request.Method == "POST" || c.Request.Method == "PUT" {
|
|
requestBody = logRequestBody(c)
|
|
}
|
|
|
|
// Create log fields
|
|
fields := []zap.Field{
|
|
zap.String("request_id", requestID),
|
|
zap.String("method", c.Request.Method),
|
|
zap.String("path", path),
|
|
zap.String("query", raw),
|
|
zap.Int("status", c.Writer.Status()),
|
|
zap.Duration("latency_ms", latency),
|
|
zap.String("client_ip", c.ClientIP()),
|
|
zap.String("user_agent", c.Request.UserAgent()),
|
|
zap.String("referer", c.Request.Referer()),
|
|
zap.String("content_type", c.GetHeader("Content-Type")),
|
|
zap.Int64("content_length", c.Request.ContentLength),
|
|
}
|
|
|
|
if userID != nil {
|
|
fields = append(fields, zap.Any("user_id", userID))
|
|
}
|
|
|
|
if requestBody != "" {
|
|
fields = append(fields, zap.String("request_body", requestBody))
|
|
}
|
|
|
|
if c.Writer.Size() > 0 {
|
|
fields = append(fields, zap.Int("response_size", c.Writer.Size()))
|
|
}
|
|
|
|
if len(c.Errors) > 0 {
|
|
fields = append(fields, zap.String("errors", c.Errors.String()))
|
|
}
|
|
|
|
// Log based on status code
|
|
statusCode := c.Writer.Status()
|
|
if statusCode >= 500 {
|
|
logger.Error("HTTP request", fields...)
|
|
} else if statusCode >= 400 {
|
|
logger.Warn("HTTP request", fields...)
|
|
} else {
|
|
logger.Info("HTTP request", fields...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// logRequestBody safely logs request body
|
|
func logRequestBody(c *gin.Context) string {
|
|
// Skip logging for file uploads and sensitive endpoints
|
|
if c.Request.Header.Get("Content-Type") == "multipart/form-data" {
|
|
return "[multipart data]"
|
|
}
|
|
|
|
if c.Request.URL.Path == "/api/v1/auth/login" ||
|
|
c.Request.URL.Path == "/api/v1/auth/register" {
|
|
return "[sensitive data]"
|
|
}
|
|
|
|
// Read body
|
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
|
if err != nil {
|
|
return "[failed to read body]"
|
|
}
|
|
|
|
// Restore body for next handler
|
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
|
|
|
// Limit body size for logging
|
|
if len(bodyBytes) > 1024 {
|
|
return string(bodyBytes[:1024]) + "... [truncated]"
|
|
}
|
|
|
|
return string(bodyBytes)
|
|
}
|
|
|
|
// SecurityLogger logs security-related events
|
|
func SecurityLogger() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Log authentication failures
|
|
if c.Writer.Status() == 401 {
|
|
logSecurityEvent("authentication_failure", map[string]interface{}{
|
|
"client_ip": c.ClientIP(),
|
|
"path": c.Request.URL.Path,
|
|
"user_agent": c.Request.UserAgent(),
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|
|
|
|
// Log authorization failures
|
|
if c.Writer.Status() == 403 {
|
|
logSecurityEvent("authorization_failure", map[string]interface{}{
|
|
"client_ip": c.ClientIP(),
|
|
"path": c.Request.URL.Path,
|
|
"user_agent": c.Request.UserAgent(),
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// logSecurityEvent logs security-related events using Zap
|
|
func logSecurityEvent(eventType string, data map[string]interface{}) {
|
|
logger := config.GetLogger()
|
|
|
|
fields := []zap.Field{
|
|
zap.String("event_type", "security"),
|
|
zap.String("event", eventType),
|
|
zap.String("timestamp", time.Now().Format(time.RFC3339)),
|
|
}
|
|
|
|
for k, v := range data {
|
|
fields = append(fields, zap.Any(k, v))
|
|
}
|
|
|
|
logger.Warn("Security event", fields...)
|
|
}
|
|
|
|
// PerformanceLogger logs performance metrics
|
|
func PerformanceLogger() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
start := time.Now()
|
|
|
|
c.Next()
|
|
|
|
// Log slow requests (> 1 second)
|
|
latency := time.Since(start)
|
|
if latency > time.Second {
|
|
logPerformanceEvent("slow_request", map[string]interface{}{
|
|
"path": c.Request.URL.Path,
|
|
"method": c.Request.Method,
|
|
"latency_ms": latency.Milliseconds(),
|
|
"status": c.Writer.Status(),
|
|
"client_ip": c.ClientIP(),
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// logPerformanceEvent logs performance-related events using Zap
|
|
func logPerformanceEvent(eventType string, data map[string]interface{}) {
|
|
logger := config.GetLogger()
|
|
|
|
fields := []zap.Field{
|
|
zap.String("event_type", "performance"),
|
|
zap.String("event", eventType),
|
|
zap.String("timestamp", time.Now().Format(time.RFC3339)),
|
|
}
|
|
|
|
for k, v := range data {
|
|
fields = append(fields, zap.Any(k, v))
|
|
}
|
|
|
|
logger.Info("Performance event", fields...)
|
|
}
|