rdev/internal/logging/logger.go
jordan d69da6d627 feat: add structured logging infrastructure and SDLC extensions
Major changes:
- Add internal/logging package with field constants, context propagation,
  sensitive data auto-redaction, and per-component log levels
- Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.)
- Extend SDLC with callback handlers, generate endpoints, and executor
- Add new cookbook trees for aeries and slackpath progression
- Add skeleton templates for queue, realtime, and microservices
- Add worker component template with async job processing
- Refactor services and handlers to use new logging infrastructure
- Split component.go into component_infra.go and component_listing.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:56:04 -07:00

153 lines
3.8 KiB
Go

package logging
import (
"io"
"log/slog"
"os"
"time"
)
// Logger wraps slog.Logger with convenience methods and standard fields.
type Logger struct {
*slog.Logger
config Config
}
// New creates a new Logger from the given config.
func New(cfg Config) *Logger {
return NewWithWriter(cfg, os.Stdout)
}
// NewWithWriter creates a new Logger writing to the given writer.
func NewWithWriter(cfg Config, w io.Writer) *Logger {
var handler slog.Handler
opts := &slog.HandlerOptions{
Level: cfg.Level.SlogLevel(),
AddSource: cfg.AddSource,
}
if cfg.Format == FormatText {
handler = slog.NewTextHandler(w, opts)
} else {
handler = slog.NewJSONHandler(w, opts)
}
// Wrap with redacting handler if enabled
if cfg.RedactEnabled {
handler = NewRedactingHandler(handler)
}
return &Logger{
Logger: slog.New(handler),
config: cfg,
}
}
// With returns a new Logger with the given attributes.
func (l *Logger) With(args ...any) *Logger {
return &Logger{
Logger: l.Logger.With(args...),
config: l.config,
}
}
// WithComponent returns a new Logger with the component field set.
func (l *Logger) WithComponent(name string) *Logger {
return l.With(FieldComponent, name)
}
// WithHandler returns a new Logger with the handler field set.
func (l *Logger) WithHandler(name string) *Logger {
return l.With(FieldHandler, name)
}
// WithService returns a new Logger with the service field set.
func (l *Logger) WithService(name string) *Logger {
return l.With(FieldService, name)
}
// WithWorker returns a new Logger with the worker field set.
func (l *Logger) WithWorker(name string) *Logger {
return l.With(FieldWorker, name)
}
// WithAdapter returns a new Logger with the adapter field set.
func (l *Logger) WithAdapter(name string) *Logger {
return l.With(FieldAdapter, name)
}
// WithRequestID returns a new Logger with the request_id field set.
func (l *Logger) WithRequestID(id string) *Logger {
return l.With(FieldRequestID, id)
}
// WithProjectID returns a new Logger with the project_id field set.
func (l *Logger) WithProjectID(id string) *Logger {
return l.With(FieldProjectID, id)
}
// WithUserID returns a new Logger with the user_id field set.
func (l *Logger) WithUserID(id string) *Logger {
return l.With(FieldUserID, id)
}
// WithError returns a new Logger with the error field set.
// Always use "error" as the field name, never "err" or "e".
func (l *Logger) WithError(err error) *Logger {
if err == nil {
return l
}
return l.With(FieldError, err.Error())
}
// WithOperation returns a new Logger with the operation field set.
func (l *Logger) WithOperation(name string) *Logger {
return l.With(FieldOperation, name)
}
// Timed returns a function that logs the duration when called.
// Usage: defer log.Timed("operation_name")()
func (l *Logger) Timed(operation string) func() {
start := time.Now()
return func() {
duration := time.Since(start)
l.Info("operation completed",
FieldOperation, operation,
FieldDuration, duration.Milliseconds(),
)
}
}
// TimedWithLevel returns a function that logs the duration at the specified level.
// Usage: defer log.TimedWithLevel("operation_name", LevelDebug)()
func (l *Logger) TimedWithLevel(operation string, level Level) func() {
start := time.Now()
return func() {
duration := time.Since(start)
msg := "operation completed"
args := []any{FieldOperation, operation, FieldDuration, duration.Milliseconds()}
switch level {
case LevelDebug:
l.Debug(msg, args...)
case LevelInfo:
l.Info(msg, args...)
case LevelWarn:
l.Warn(msg, args...)
case LevelError:
l.Error(msg, args...)
}
}
}
// Slog returns the underlying slog.Logger for compatibility.
func (l *Logger) Slog() *slog.Logger {
return l.Logger
}
// Config returns the logger's configuration.
func (l *Logger) Config() Config {
return l.config
}