package utils import ( "io" "net/http" "os" "runtime/debug" "time" "github.com/labstack/echo/v4" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/treytartt/honeydue-api/internal/dto/responses" ) // InitLogger initializes the zerolog logger func InitLogger(debug bool) { InitLoggerWithWriter(debug, nil) } // InitLoggerWithWriter initializes the zerolog logger with an optional additional writer // The additional writer receives JSON formatted logs (useful for monitoring) func InitLoggerWithWriter(debug bool, additionalWriter io.Writer) { zerolog.TimeFieldFormat = time.RFC3339 if debug { zerolog.SetGlobalLevel(zerolog.DebugLevel) } else { zerolog.SetGlobalLevel(zerolog.InfoLevel) } // Build the output writer(s) var output io.Writer if additionalWriter != nil { // Always write JSON to additional writer for monitoring // The additional writer parses JSON to extract log entries if debug { // In debug mode: pretty console to stdout + JSON to additional writer consoleOutput := zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: "15:04:05", } output = io.MultiWriter(consoleOutput, additionalWriter) } else { // In production: JSON to both stdout and additional writer output = io.MultiWriter(os.Stdout, additionalWriter) } } else { if debug { output = zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: "15:04:05", } } else { output = os.Stdout } } log.Logger = zerolog.New(output).With().Timestamp().Caller().Logger() } // EchoLogger returns an Echo middleware for request logging func EchoLogger() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { start := time.Now() req := c.Request() path := req.URL.Path raw := req.URL.RawQuery // Process request err := next(c) // Log after request end := time.Now() latency := end.Sub(start) if raw != "" { path = path + "?" + raw } res := c.Response() statusCode := res.Status msg := "Request" if err != nil { msg = err.Error() } event := log.Info() if statusCode >= 400 && statusCode < 500 { event = log.Warn() } else if statusCode >= 500 { event = log.Error() } event. Str("method", req.Method). Str("path", path). Int("status", statusCode). Str("ip", c.RealIP()). Dur("latency", latency). Str("user-agent", req.UserAgent()). Msg(msg) return err } } } // EchoRecovery returns an Echo middleware for panic recovery func EchoRecovery() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { defer func() { if err := recover(); err != nil { // F-14: Include full stack trace for debugging log.Error(). Interface("error", err). Str("path", c.Request().URL.Path). Str("method", c.Request().Method). Str("stack", string(debug.Stack())). Msg("Panic recovered") // F-15: Use the project's standard ErrorResponse struct c.JSON(http.StatusInternalServerError, responses.ErrorResponse{ Error: "Internal server error", }) } }() return next(c) } } }