mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-04 20:42:59 +00:00
286 lines
6.6 KiB
Go
286 lines
6.6 KiB
Go
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// LoggerConfig holds configuration for the logger
|
|
type LoggerConfig struct {
|
|
LogFile string
|
|
LogLevel string
|
|
EnableJSON bool
|
|
}
|
|
|
|
// Logger returns a middleware that logs HTTP requests
|
|
func Logger(config LoggerConfig) gin.HandlerFunc {
|
|
// Create log file if specified
|
|
var file *os.File
|
|
if config.LogFile != "" {
|
|
var err error
|
|
file, err = os.OpenFile(config.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
log.Printf("Failed to open log file: %v", err)
|
|
}
|
|
}
|
|
|
|
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
|
|
if userID, exists := param.Keys["user_id"]; exists {
|
|
entry["user_id"] = userID
|
|
}
|
|
|
|
// Add error if present
|
|
if param.ErrorMessage != "" {
|
|
entry["error"] = param.ErrorMessage
|
|
}
|
|
|
|
// Format output
|
|
var output string
|
|
if config.EnableJSON {
|
|
jsonData, _ := json.Marshal(entry)
|
|
output = string(jsonData) + "\n"
|
|
} else {
|
|
output = fmt.Sprintf("[%s] %s %s %d %s %s %s",
|
|
entry["timestamp"],
|
|
entry["method"],
|
|
entry["path"],
|
|
entry["status"],
|
|
entry["latency"],
|
|
entry["client_ip"],
|
|
entry["user_agent"],
|
|
)
|
|
if userID, exists := entry["user_id"]; exists {
|
|
output += fmt.Sprintf(" user_id:%v", userID)
|
|
}
|
|
if param.ErrorMessage != "" {
|
|
output += fmt.Sprintf(" error:%s", param.ErrorMessage)
|
|
}
|
|
output += "\n"
|
|
}
|
|
|
|
// Write to file and console
|
|
if file != nil {
|
|
file.WriteString(output)
|
|
}
|
|
return output
|
|
})
|
|
}
|
|
|
|
// RequestLogger logs detailed request information
|
|
func RequestLogger() gin.HandlerFunc {
|
|
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 client IP
|
|
clientIP := c.ClientIP()
|
|
|
|
// Get status code
|
|
statusCode := c.Writer.Status()
|
|
|
|
// 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
|
|
}
|
|
|
|
// Create log entry
|
|
logEntry := map[string]interface{}{
|
|
"timestamp": start.Format(time.RFC3339),
|
|
"request_id": requestID,
|
|
"method": c.Request.Method,
|
|
"path": path,
|
|
"query": raw,
|
|
"status": statusCode,
|
|
"latency_ms": latency.Milliseconds(),
|
|
"client_ip": clientIP,
|
|
"user_agent": c.Request.UserAgent(),
|
|
"referer": c.Request.Referer(),
|
|
"content_type": c.GetHeader("Content-Type"),
|
|
"content_length": c.Request.ContentLength,
|
|
}
|
|
|
|
if userID != nil {
|
|
logEntry["user_id"] = userID
|
|
}
|
|
|
|
// Log request body for POST/PUT requests (excluding sensitive data)
|
|
if c.Request.Method == "POST" || c.Request.Method == "PUT" {
|
|
body := logRequestBody(c)
|
|
if body != "" {
|
|
logEntry["request_body"] = body
|
|
}
|
|
}
|
|
|
|
// Log response size
|
|
if c.Writer.Size() > 0 {
|
|
logEntry["response_size"] = c.Writer.Size()
|
|
}
|
|
|
|
// Log errors
|
|
if len(c.Errors) > 0 {
|
|
logEntry["errors"] = c.Errors.String()
|
|
}
|
|
|
|
// Write structured log
|
|
logJSON(logEntry)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// logJSON writes structured JSON logs
|
|
func logJSON(data map[string]interface{}) {
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
log.Printf("Failed to marshal log entry: %v", err)
|
|
return
|
|
}
|
|
|
|
log.Println(string(jsonData))
|
|
}
|
|
|
|
// 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
|
|
func logSecurityEvent(eventType string, data map[string]interface{}) {
|
|
event := map[string]interface{}{
|
|
"event_type": "security",
|
|
"event": eventType,
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
|
|
for k, v := range data {
|
|
event[k] = v
|
|
}
|
|
|
|
logJSON(event)
|
|
}
|
|
|
|
// 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
|
|
func logPerformanceEvent(eventType string, data map[string]interface{}) {
|
|
event := map[string]interface{}{
|
|
"event_type": "performance",
|
|
"event": eventType,
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
|
|
for k, v := range data {
|
|
event[k] = v
|
|
}
|
|
|
|
logJSON(event)
|
|
}
|