kereru-forms/backend/internal/logging/logging.go

90 lines
2.2 KiB
Go
Raw Normal View History

// 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)
}