// Package logging configures the application's structured logger. package logging import ( "os" "strings" "time" "github.com/gin-gonic/gin" "github.com/rs/zerolog" ) // New builds a zerolog logger for the given level and format. // // level is one of: trace, debug, info, warn, error, fatal, panic. // format is "json" for machine-readable output, anything else produces a // human-friendly console output (useful for local development). func New(level, format, environment string) zerolog.Logger { lvl, err := zerolog.ParseLevel(strings.ToLower(strings.TrimSpace(level))) if err != nil || lvl == zerolog.NoLevel { lvl = zerolog.InfoLevel } var writer zerolog.LevelWriter if strings.EqualFold(format, "json") { writer = zerolog.MultiLevelWriter(os.Stdout) } else { console := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} writer = zerolog.MultiLevelWriter(console) } logger := zerolog.New(writer). Level(lvl). With(). Timestamp(). Str("service", "kereru-forms"). Str("environment", environment). Logger() zerolog.SetGlobalLevel(lvl) return logger } // GinMiddleware logs every HTTP request with useful observability fields. func GinMiddleware(logger zerolog.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path if raw := c.Request.URL.RawQuery; raw != "" { path = path + "?" + raw } c.Next() event := logger.Info() if c.Writer.Status() >= 500 { event = logger.Error() } else if c.Writer.Status() >= 400 { event = logger.Warn() } event. Str("method", c.Request.Method). Str("path", path). Int("status", c.Writer.Status()). Dur("latency", time.Since(start)). Str("client_ip", c.ClientIP()). Str("user_agent", c.Request.UserAgent()). Str("request_id", RequestID(c)). Msg("http request") } } const requestIDKey = "request_id" // RequestID returns the correlation id assigned to the current request. func RequestID(c *gin.Context) string { if v, ok := c.Get(requestIDKey); ok { if s, ok := v.(string); ok { return s } } return "" } // SetRequestID stores the correlation id on the context. func SetRequestID(c *gin.Context, id string) { c.Set(requestIDKey, id) }