Skip to main content

Logging guide

CONA sends operational logs to Axiom. Logging helpers do not write to Postgres. Business audit records, product workflow state, product analytics, metrics, and traces use separate paths. The repository decision Telemetry classification and persistence boundaries is the source of truth for ownership, storage, retention, and access.

Choose the record type first

One business action may need both an operational log and an audit record. Call the logger and the domain audit writer explicitly. Do not make either one a side effect of the other.

Use the shared operational envelope

Use @cona/observability for new shared logging code. It emits a bounded, redacted envelope with stable event names, runtime context, retry semantics, and these classification fields:
The package has no runtime dependencies. Automated lint and test checks prevent it from importing database clients or persistence packages.
Existing application adapters already attach request, deployment, Temporal, process, and correlation context. Add fields at the closest useful boundary, but do not query a database merely to enrich a log.

Log at meaningful boundaries

  • Emit one terminal error when an operation has stopped.
  • Emit a warning when an operation will retry or continue in degraded mode.
  • Emit an informational recovery event when useful for an incident timeline.
  • Use stable domain.action event names and low-cardinality fingerprints.
  • Include operation, outcome, durationMs, and retry state when relevant.
  • Preserve structured errors instead of logging only their message.
  • Avoid per-record success logs in batch operations. Prefer bounded summaries.
Temporal Activities use @temporalio/activity logging so the Worker adapter can add Workflow, run, Activity, attempt, task-queue, heartbeat, process, and capacity context.

Keep sensitive data out

Never log credentials, access tokens, session tokens, cookies, raw bank data, complete third-party payloads, or document contents. Prefer stable internal identifiers and bounded reason codes. Avoid raw URLs and query strings when they can contain customer or attacker-controlled data. The shared serializer redacts common secret fields, but redaction is a safety net, not permission to pass sensitive payloads to a logger. Governed event families use defineEventRegistry(registry, schema). Every event must declare an owner and description, while the registry declares a positive schema version, required envelope fields, and—where practical—an allowlist of attribute keys. CI also rejects new raw console.warn / console.error calls and error-message-only logging. Existing exceptions are owned and tracked in the logging governance baseline.

Current datasets

Dataset names come from deployment configuration. The current topology is:
  • Webapp and portal application logs: the configured application dataset.
  • Webapp security events: the configured restricted security dataset.
  • Temporal Worker and SDK lifecycle logs: cona_temporal-workers in production, with the staging equivalent in staging.
  • Temporal Activity logs: cona_temporal-activity in production, with the staging equivalent in staging.
If the security dataset is missing, security events currently fall back to the application logger. Treat that as a configuration defect and restore the restricted dataset rather than relying on the fallback.

Retention and access

Axiom retention and access are dataset-level controls. Do not assume that an error level changes retention.
  • Application logs: 90 days in production and 30 days outside production.
  • Security telemetry: 365 days, with access restricted to Security and approved incident responders.
  • Log-derived metric rollups: migration bridge only. OpenTelemetry metrics keep raw series for 30 days and aggregates for 13 months.
  • Traces: 30 days in production and 14 days outside production.
Platform and Security own provider configuration and quarterly verification. Environment variables select datasets but do not enforce retention or access.

Business audit is not logging

Use logActivity for activity_logs and keep it in the same transaction as the business mutation it describes. Use the copilot audit writers for agent runs, actions, and approvals. Keep background_activities limited to mutable, user-visible workflow state. Never call those writers from an observability transport or adapter. Never use Postgres for log delivery, logger buffering, metrics, or traces.