Structured Logging (Canonical)¶
This is the single normative rule for structured server logging in the Open Learning Hub monorepo. Other docs should link here instead of restating logging policy.
Requirement¶
- Both apps use
src/lib/log.tsas the canonical structured server logger. - Import it as
@/lib/log. - Keep the public API identical across apps:
log.info,log.warn, andlog.error. - Emit one JSON line per entry with
{ level, message, timestamp, context? }. - Do not use raw
console.*in server request paths such assrc/lib/auth/,src/app/api/, shared API error handlers, import/export pipelines, orinstrumentation.tscall chains. Use@/lib/loginstead.
Emit Contract¶
- The logger always emits through
console.log,console.warn, andconsole.error. This console emit is unconditional and is the source of truth for the JSON contract and logger tests. - Never reference
process.stdoutorprocess.stderrinsrc/lib/log.ts. Turbopack statically flags those Node APIs in Edge Runtime graphs, even behind runtime guards. @/lib/logmust remain safe to import from code that can be compiled for Edge, includinginstrumentation.tsdependencies,proxy.tsdependencies, Auth.js logger hooks, and route handlers.- The structured
levelfield is the source of truth for log severity. Do not depend on stdout/stderr stream routing for application semantics.
Log-sink seam (OpenTelemetry forwarding)¶
- After the mandatory
console.*emit, the logger forwards the already-redacted record to an optional sink viaforwardToLogSinkfrom@open-learning-hub/observability/log-sink. - The
log-sinkmodule is intentionally dependency-free (no OpenTelemetry, noposthog-node, no Node built-ins), so importing it fromsrc/lib/log.tspreserves Edge-safety. Its default is a no-op — nothing is forwarded until a sink is registered. - A sink is registered only in the Node runtime from
instrumentation.ts(guarded byprocess.env.NEXT_RUNTIME === "nodejs"), which builds the OpenTelemetry log bridge and callsregisterLogSink(...). The logger core must never import OpenTelemetry orposthog-nodedirectly. - Sink failures must never break the request path;
forwardToLogSinkswallows errors. See 092-analytics-observability.mdc for the PostHog/OTel wiring.
Redaction Contract¶
The logger boundary must scrub sensitive values before emitting a record.
Redact at minimum:
- Credentials and secrets: passwords, password hashes, tokens, token hashes, proofs, secrets, authorization headers, private keys, and client secrets.
- Runtime/platform secrets: API keys,
AUTH_SECRET,DATABASE_URL/connection strings,CMS_API_UUID, andCMS_WEBHOOK_SECRET. - Session/user-sensitive fields: cookies, session tokens, and session IDs.
- PII: emails and person-name fields such as
name,firstName,lastName,displayName, andfullName.
Email values may be masked to preserve the domain, for example ***@example.com.
To preserve observability while redacting generic name, non-PII labels should use explicit keys such as tenantName, courseName, or resourceName.
Deeply nested context should be truncated rather than emitted unbounded.
Error values must serialize as a safe object shape so non-enumerable fields are preserved:
namemessage(redaction-aware)stack
Parity And Anti-Drift¶
- CMS and LMS
src/lib/log.tsimplementations must stay byte-identical except for the server-only import line:- CMS:
import "server-only"; - LMS:
import "@/lib/server-only";
- CMS:
- Do not introduce
log-edge,log-core, or per-import-site logger splits. Keep one canonical logger per app. - If one app's logger behavior changes, update the other app and both logger test files in the same change.
Scope Boundaries¶
In scope:
- Server request paths.
- Auth.js logger hooks.
- Shared server boundaries such as
env.ts,errors.ts, import pipelines, export pipelines, and health checks.
Out of scope:
- CLI, seed, migrate, and reset scripts that intentionally write human-readable terminal output.
- Client-side
console.*cleanup. - PostHog error-tracking, product analytics, and alerting configuration, which are governed by 092-analytics-observability.mdc. This rule only owns the console emit contract and the Edge-safe log-sink seam.
Testing¶
Both apps must ship co-located logger tests at src/lib/log.test.ts covering:
- JSON output shape.
console.log/console.warn/console.errorrouting.- Redaction behavior.
Errorserialization.- Log-sink forwarding: no forwarding by default (no-op), and the redacted record is forwarded to a registered sink.
Related Rules¶
- Error handling, PostHog observability, correlation IDs, and user-safe error responses: 090-error-handling.mdc.
- PostHog analytics + observability package contract: 092-analytics-observability.mdc.