initial commit
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled

This commit is contained in:
Daniel Noulet 2026-10-07 15:11:35 +02:00
commit 951dd8d26b
175 changed files with 50041 additions and 0 deletions

130
internal/logging/logging.go Normal file
View file

@ -0,0 +1,130 @@
// Package logging configures structured logging for the service.
//
// It wraps log/slog rather than a third-party logger so that the output is
// plain JSON or plain text with no vendor in the dependency graph, and so that
// the level and format can be chosen from the environment the Helm chart sets.
package logging
import (
"context"
"io"
"log/slog"
"os"
"strings"
)
// Logger is the logging interface the application depends on. Declaring it here
// keeps handlers and middleware testable with a recorder, and keeps them from
// importing slog directly.
type Logger interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
With(args ...any) Logger
}
// Options configures a logger.
type Options struct {
// Level is debug, info, warn or error.
Level string
// Format is json or text.
Format string
// Service is the value of the service.name attribute.
Service string
// Environment is the deployment name.
Environment string
// Version is the build version, reported in logs.
Version string
// Commit is the source revision, reported in logs.
Commit string
// BuildDate is the build timestamp, reported in logs.
BuildDate string
// Writer overrides the destination, for tests.
Writer io.Writer
}
// New builds a logger.
func New(opts Options) Logger {
writer := opts.Writer
if writer == nil {
writer = os.Stdout
}
handlerOpts := &slog.HandlerOptions{Level: parseLevel(opts.Level)}
var handler slog.Handler
if strings.EqualFold(opts.Format, "text") {
handler = slog.NewTextHandler(writer, handlerOpts)
} else {
handler = slog.NewJSONHandler(writer, handlerOpts)
}
logger := slog.New(handler).With(
slog.String("service", orDefault(opts.Service, "Go Bananas!!")),
slog.String("environment", opts.Environment),
slog.String("version", opts.Version),
slog.String("commit", orDefault(opts.Commit, "unknown")),
slog.String("built", orDefault(opts.BuildDate, "unknown")),
)
return &slogLogger{logger: logger}
}
// parseLevel maps a configuration string onto a slog level, defaulting to info
// so that a typo never silences the service.
func parseLevel(raw string) slog.Level {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "debug":
return slog.LevelDebug
case "warn", "warning":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
// orDefault returns value when it is not empty, otherwise fallback.
func orDefault(value, fallback string) string {
if strings.TrimSpace(value) == "" {
return fallback
}
return value
}
// slogLogger adapts *slog.Logger to the Logger interface.
type slogLogger struct {
logger *slog.Logger
}
func (l *slogLogger) Debug(msg string, args ...any) { l.logger.Debug(msg, args...) }
func (l *slogLogger) Info(msg string, args ...any) { l.logger.Info(msg, args...) }
func (l *slogLogger) Warn(msg string, args ...any) { l.logger.Warn(msg, args...) }
func (l *slogLogger) Error(msg string, args ...any) { l.logger.Error(msg, args...) }
func (l *slogLogger) With(args ...any) Logger {
return &slogLogger{logger: l.logger.With(args...)}
}
// Discard returns a logger that drops everything. It exists so that tests and
// one-off commands do not have to thread a logger through just to satisfy the
// dependency.
func Discard() Logger {
return &slogLogger{logger: slog.New(slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelError + 8}))}
}
// FromContext returns the logger stored on a context, or a discarding logger.
func FromContext(ctx context.Context) Logger {
if logger, ok := ctx.Value(loggerKey{}).(Logger); ok {
return logger
}
return Discard()
}
type loggerKey struct{}
// WithContext stores a logger on a context.
func WithContext(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, loggerKey{}, logger)
}