Lumi/docs/LOGGING_STANDARD.md

4.5 KiB

Logging standard

Lumi uses the structured logger in src/services/logger.js for operational events. It stores searchable entries for the admin UI, attaches request context, redacts common credential fields, and publishes live admin updates.

Operational logging

Core services create a named logger:

const { createLogger } = require("./logger");
const logger = createLogger("core:updater", { category: "updates" });

logger.info("Update check completed", { version }, {
  event: "update_check_completed"
});

Plugins receive a plugin-scoped logger in their init() dependencies. Use that injected logger when practical. A plugin module that must log outside init() may create a logger named plugin:<plugin-id>.

Use these source prefixes consistently:

  • core:<component> for Lumi core and WebUI services.
  • platform:<provider> for Discord, Twitch, YouTube, and similar integrations.
  • plugin:<plugin-id> for bundled and locally installed plugins.
  • companion:<component> for the desktop Companion host.

Give each logger a stable default category such as lifecycle, http, integration, command, automation, security, updates, or plugin. Every operational call must provide a stable, lowercase snake_case event ID. Messages are for people and may improve over time; event IDs are for filtering and automation and should remain stable.

Use:

  • debug for detailed, low-value troubleshooting information.
  • info for meaningful lifecycle and administrative actions.
  • warn for degraded behavior that Lumi can continue through.
  • error for a failed operation that may require attention.

Pass an Error as the details argument when one is available. The logger preserves its stack trace while applying credential redaction:

logger.error("Plugin refresh failed", error, {
  event: "plugin_refresh_failed"
});

The metadata argument only accepts source, category, event, and requestId. Operational fields such as plugin_id, user_id, status, or duration_ms belong in the details argument:

logger.warn("Plugin health check degraded", {
  plugin_id: plugin.id,
  status: health.status
}, {
  event: "plugin_health_degraded"
});

Do not include passwords, tokens, cookies, pairing secrets, authorization headers, signature fragments, full request bodies, or full third-party payloads in messages or details. Record a bounded summary with IDs, status, counts, and safe error text instead. Redaction is a safety net, not a reason to collect secrets.

Metrics and high-frequency events

Do not write high-frequency counters or per-frame events to the operational log. There is no global metrics API.

A feature may retain its own bounded metrics only when it also owns the storage, retention, and inspection UI. Lumi AI is one example: plugins/lumi_ai/backend/metrics.js aggregates AI timings and retains bounded work history. Operational failures in such a feature still belong in the core logger.

Feature-owned diagnostic logs follow the same credential and payload rules. They must have explicit size/age retention, sanitize recursively before writing, and remain admin-only. Transcription worker diagnostics are kept separately because they are high-volume troubleshooting data; they are not a substitute for operational warnings and errors in the core logger.

The desktop Companion cannot write directly to the server database. Its local JSON Lines logs therefore carry the same level, source, category, event, and human-readable message shape, use the shared CompanionLogSanitizer, and enforce local retention.

The native OBS Bridge must use OBS's blog() facility so its messages remain in the operator's OBS log. Prefix each message with [Lumi Companion] and a stable event=<snake_case> field, and never include IPC payloads or credentials.

Console output

Direct console.* calls are captured after hookConsole() starts, but they lose the useful source and event metadata of a named logger. Prefer a named logger in runtime code, including startup and shutdown paths. Console output remains reasonable in standalone verification and build scripts where the terminal is the intended consumer. Browser-side console diagnostics and isolated worker sandboxes are outside the server operational-log boundary.

Retention and context

Operational logs default to 30 days and at most 100,000 entries. Request correlation is inherited through withLogContext() and logger run() scopes. The logger redacts common sensitive keys and credential-looking text before an entry is stored or published.