CMS structured logger
Problem¶
LMS had a structured JSON logger (apps/lms/src/lib/log.ts) with redaction, while CMS still used raw console.* calls in request-path code (src/lib/auth/*, src/app/api/**, and shared API error handlers), creating inconsistent observability and violating the redaction expectations in .cursor/rules/090-error-handling.mdc.
Normative contract after implementation: .cursor/rules/091-structured-logging.mdc.
Proposal¶
Option A (selected): Duplicate LMS log.ts into CMS as apps/cms/src/lib/log.ts, then mature both copies in lockstep to avoid drift.
- CMS import adaptation: use
import "server-only";directly (CMS has no@/lib/server-onlyshim). - Keep the logger API identical across apps:
log.info,log.warn,log.error. - Keep output as one JSON line per log entry.
- Emit through
console.log,console.warn, andconsole.erroronly so the canonical logger remains safe in Edge-reachable graphs.
Architecture + SDLC maturity updates¶
Redaction contract¶
The logger must redact, at minimum:
- Credentials/secrets: passwords, password hashes, tokens, token hashes, proofs, secrets, authorization headers.
- Runtime/platform secrets: API keys,
AUTH_SECRET,DATABASE_URL/connection strings,CMS_API_UUID,CMS_WEBHOOK_SECRET. - Session/user-sensitive fields: cookies, session tokens/IDs.
- PII: emails (masked to domain) and person-name fields (
name,firstName,lastName,displayName,fullName).
To preserve observability while redacting generic name, non-PII display labels should use explicit keys such as tenantName, courseName, or resourceName.
Error serialization contract¶
redactValue must special-case Error values and emit a safe object shape so non-enumerable fields are preserved:
{ name, message, stack }messageis redaction-awarestackis retained for server debugging
Parity / anti-drift¶
CMS and LMS logger implementations must stay byte-identical except for the server-only import line (@/lib/server-only in LMS vs server-only package import in CMS). Divergence is a review-blocking regression.
Correlation now vs later¶
This ticket supports correlation fields (traceId, tenantId, userId) via context. Promoting those to required top-level fields and attaching them to PostHog scope is tracked in T-015.
Testability¶
Both apps must ship co-located logger tests (src/lib/log.test.ts) covering:
- JSON output shape
- console routing
- redaction behavior
Errorserialization
Adoption scope¶
In scope¶
- Add
apps/cms/src/lib/log.ts. - Replace request-path
console.errorcall sites in CMS auth/API handlers withlog.error. - Update NextAuth logger hooks in CMS auth to use structured logger methods.
- Backport the same logger maturity improvements to LMS for parity.
- Add logger tests in both apps.
Out of scope¶
- PostHog observability integration (T-015)
- Runtime log-level configurability
- Log shipping/aggregation infrastructure
- CLI script logging migration (
seed,migrate,resetscripts remain human-readable for now) - Client-side
console.*cleanup
Acceptance criteria¶
- CMS has
apps/cms/src/lib/log.tswith API parity to LMS. - Redaction includes credentials/secrets, environment secrets, session identifiers, emails, and person-name PII.
-
Errorvalues serialize as redaction-safe{ name, message, stack }. - Co-located logger tests exist in both apps and cover shape/routing/redaction/error handling.
- No
console.errorremains inapps/cms/src/lib/auth/orapps/cms/src/app/api/. - Logger adopted in at least five CMS request-path call sites.
-
npm run checkpasses at repo root.
Notes / decisions log¶
- 2026-07-07: Proposed. Option A preferred for speed; extraction to shared package deferred until T-006 is reactivated.
- 2026-07-09: Deferred — moved to
tickets/deferred/; no active implementation planned. - 2026-07-10: Reactivated under Option A with implementation in CMS and parity backport in LMS.
- 2026-07-10: Decision — redact generic
namefields; use explicit non-PII keys (tenantName,courseName,resourceName) when observability requires clear labels. - 2026-07-10: Decision — exclude CLI/seed/migrate script logging from this ticket; focus on request-path server logs.
- 2026-07-10: Implemented — CMS logger added, CMS request-path
console.errorreplaced, LMS logger matured for parity, tests added in both apps, andnpm run checkpassed. - 2026-07-10: Follow-up — consolidated the logger contract in
.cursor/rules/091-structured-logging.mdc, made both app loggers edge-safe via universalconsole.*emit, removed interimlog-core/log-edgesplit files, and eliminatedprocess.stdout/process.stderrreferences fromsrc/lib/log.ts.