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 }