Files
accounted/docs/security/logging-and-observability.md
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

3.0 KiB

Logging and Observability Pipeline

This document states what the logging pipeline actually is in production, and the non-negotiable redaction contract for anything that forwards log data to a third party.

The pipeline today

  • lib/logger.ts writes structured log records to stdout/stderr. On hosted (Vercel) these are collected by the platform and delivered through the configured Vercel log drain, which is the production log delivery path.
  • lib/observability/sink.ts is a provider-agnostic seam for an error tracking vendor. It is a deliberate no-op until an adapter is registered with registerObservabilitySink() from a server-side init path. No adapter is registered by default, so self-hosted builds carry no third-party runtime dependency and setting the DSN environment variables alone changes nothing.

That means: until a vendor adapter is wired, error-level events reach a human only through the log drain. Any production deployment that wants alerting must either configure the log drain with alert rules or register a sink adapter; a no-op sink is not an alerting pipeline on its own.

Redaction contract (GDPR, non-negotiable)

lib/observability/redact.ts is the single source of truth for what must never leave the process in clear text: a key denylist (passwords, tokens, IBAN, personnummer, ...), a personnummer regex applied to every string, and substring patterns for emails, Swedish IBANs, and gnubok API keys.

Structural guarantees, verified in the code:

  • Every public entry point of the sink module (captureException, captureMessage) runs redact() / redactString() on the error, the message, and the context BEFORE the registered adapter sees anything. There is no code path from application data to a vendor that skips redaction.
  • Adapters receive errors already serialized and redacted as plain objects, never live Error instances, and must not re-fetch original values.
  • redact() is idempotent, so double-redaction on records the logger already cleaned is safe and is intentionally not "optimised away".

Rules for anyone adding an adapter or a new emission path:

  1. Never call a vendor SDK directly from application code. Route through captureException / captureMessage so redaction stays structural.
  2. A browser-side adapter (one reading NEXT_PUBLIC_OBSERVABILITY_DSN) ships data straight from the user's browser to the vendor and bypasses every server-side control. It MUST apply the same redaction module before emitting: import from lib/observability/redact.ts and run all payloads through redact() / redactString() client-side. Do not register a browser adapter that forwards raw console or log payloads.
  3. Do not add a log emission path (new logger, direct console.* forwarding, CI log shipping) that reaches a third party without going through the redact module first.

The redaction behavior is pinned by unit tests under lib/observability/__tests__/; extend them when the denylist or patterns change.