package middleware import ( "crypto/rand" "encoding/hex" "errors" "fmt" "log/slog" "net" "net/http" "runtime/debug" "time" "github.com/labstack/echo/v4" ) func RequestID() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { id := c.Request().Header.Get(echo.HeaderXRequestID) if id == "" { id = newRequestID() } c.Response().Header().Set(echo.HeaderXRequestID, id) c.Set(echo.HeaderXRequestID, id) return next(c) } } } func newRequestID() string { var buf [16]byte if _, err := rand.Read(buf[:]); err != nil { return fmt.Sprintf("req-%d", time.Now().UnixNano()) } return hex.EncodeToString(buf[:]) } func RealIP() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if forwarded := c.Request().Header.Get("X-Forwarded-For"); forwarded != "" { c.Set("real_ip", forwarded) } else if host, _, err := net.SplitHostPort(c.Request().RemoteAddr); err == nil { c.Set("real_ip", host) } return next(c) } } } func AccessLog(logger *slog.Logger) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { start := time.Now() err := next(c) session := GetSession(c) customerID := int64Value(session.CustomerID) cartID := int64Value(session.CartID) logger.Info("request complete", "method", c.Request().Method, "path", c.Request().URL.Path, "status", c.Response().Status, "latency_ms", time.Since(start).Milliseconds(), "request_id", c.Response().Header().Get(echo.HeaderXRequestID), "parse_status", session.ParseStatus, "customer_id", customerID, "cart_id", cartID, ) return err } } } func Recover(logger *slog.Logger) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { defer func() { if recovered := recover(); recovered != nil { logger.Error("panic recovered", "error", recovered, "stack", string(debug.Stack())) _ = c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"}) } }() return next(c) } } } func HTTPErrorHandler(logger *slog.Logger) echo.HTTPErrorHandler { return func(err error, c echo.Context) { if c.Response().Committed { return } var httpErr *echo.HTTPError code := http.StatusInternalServerError message := map[string]string{"error": "internal server error"} if errors.As(err, &httpErr) { code = httpErr.Code if msg, ok := httpErr.Message.(string); ok { message = map[string]string{"error": msg} } } logger.Error("request failed", "status", code, "error", err) _ = c.JSON(code, message) } } func int64Value(value *int64) any { if value == nil { return nil } return *value }