package utils import ( "io" "os" "time" "github.com/gin-gonic/gin" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) // 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() } // GinLogger returns a Gin middleware for request logging func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path raw := c.Request.URL.RawQuery // Process request c.Next() // Log after request end := time.Now() latency := end.Sub(start) if raw != "" { path = path + "?" + raw } msg := "Request" if len(c.Errors) > 0 { msg = c.Errors.String() } event := log.Info() statusCode := c.Writer.Status() if statusCode >= 400 && statusCode < 500 { event = log.Warn() } else if statusCode >= 500 { event = log.Error() } event. Str("method", c.Request.Method). Str("path", path). Int("status", statusCode). Str("ip", c.ClientIP()). Dur("latency", latency). Str("user-agent", c.Request.UserAgent()). Msg(msg) } } // GinRecovery returns a Gin middleware for panic recovery func GinRecovery() gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { log.Error(). Interface("error", err). Str("path", c.Request.URL.Path). Str("method", c.Request.Method). Msg("Panic recovered") c.AbortWithStatusJSON(500, gin.H{ "error": "Internal server error", }) } }() c.Next() } }