90 lines
2.2 KiB
Go
90 lines
2.2 KiB
Go
|
|
// 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)
|
||
|
|
}
|