2.4 KB
middleware.go
package application
import (
"encoding/json"
"log"
"net/http"
"os"
"time"
)
// RequestLog represents a logged request.
type RequestLog struct {
Timestamp string `json:"timestamp"`
Method string `json:"method"`
Path string `json:"path"`
Status int `json:"status"`
Duration string `json:"duration"`
DurationMs int64 `json:"duration_ms"`
IP string `json:"ip"`
UserAgent string `json:"user_agent,omitempty"`
}
// responseWriter wraps http.ResponseWriter to capture status code.
// It also implements http.Flusher to support SSE streaming.
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
// Flush implements http.Flusher for SSE streaming support.
func (rw *responseWriter) Flush() {
if flusher, ok := rw.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
// LoggingMiddleware returns middleware that logs all requests.
func LoggingMiddleware(next http.Handler) http.Handler {
isProduction := os.Getenv("ENV") == "production"
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Wrap response writer to capture status
wrapped := &responseWriter{ResponseWriter: w, status: http.StatusOK}
// Process request
next.ServeHTTP(wrapped, r)
// Calculate duration
duration := time.Since(start)
// Get client IP
ip := r.RemoteAddr
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
ip = forwarded
}
// Log request
if isProduction {
// Structured JSON logging for production
logEntry := RequestLog{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Method: r.Method,
Path: r.URL.Path,
Status: wrapped.status,
Duration: duration.String(),
DurationMs: duration.Milliseconds(),
IP: ip,
UserAgent: r.UserAgent(),
}
if data, err := json.Marshal(logEntry); err == nil {
log.Println(string(data))
}
} else {
// Human-readable logging for development
log.Printf("%s %s %d %s", r.Method, r.URL.Path, wrapped.status, duration)
}
})
}
// WithLogging returns an option that enables request logging.
// Note: logging is always enabled by default. This option is kept for API
// compatibility but has no additional effect.
func WithLogging() Option {
return func(app *App) {}
}