rdev/internal/logging/handler.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

74 lines
2.0 KiB
Go

package logging
import (
"context"
"log/slog"
)
// RedactingHandler wraps an slog.Handler to redact sensitive data.
type RedactingHandler struct {
inner slog.Handler
}
// NewRedactingHandler creates a handler that redacts sensitive values.
func NewRedactingHandler(inner slog.Handler) *RedactingHandler {
return &RedactingHandler{inner: inner}
}
// Enabled implements slog.Handler.
func (h *RedactingHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.inner.Enabled(ctx, level)
}
// Handle implements slog.Handler.
func (h *RedactingHandler) Handle(ctx context.Context, r slog.Record) error {
// Clone the record with redacted attributes
newRecord := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
r.Attrs(func(a slog.Attr) bool {
newRecord.AddAttrs(h.redactAttr(a))
return true
})
return h.inner.Handle(ctx, newRecord)
}
// WithAttrs implements slog.Handler.
func (h *RedactingHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
redacted := make([]slog.Attr, len(attrs))
for i, a := range attrs {
redacted[i] = h.redactAttr(a)
}
return &RedactingHandler{inner: h.inner.WithAttrs(redacted)}
}
// WithGroup implements slog.Handler.
func (h *RedactingHandler) WithGroup(name string) slog.Handler {
return &RedactingHandler{inner: h.inner.WithGroup(name)}
}
// redactAttr redacts an attribute if its key or value is sensitive.
func (h *RedactingHandler) redactAttr(a slog.Attr) slog.Attr {
// Check if the field name indicates sensitive data
if IsSensitiveField(a.Key) {
return slog.String(a.Key, RedactedValue)
}
// Check the value based on its kind
switch a.Value.Kind() {
case slog.KindString:
s := a.Value.String()
if ContainsSensitiveData(s) {
return slog.String(a.Key, RedactedValue)
}
case slog.KindGroup:
// Recursively redact group attributes
attrs := a.Value.Group()
redacted := make([]slog.Attr, len(attrs))
for i, attr := range attrs {
redacted[i] = h.redactAttr(attr)
}
return slog.Attr{Key: a.Key, Value: slog.GroupValue(redacted...)}
}
return a
}