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
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:
commit
951dd8d26b
175 changed files with 50041 additions and 0 deletions
130
internal/logging/logging.go
Normal file
130
internal/logging/logging.go
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue