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:
debugfor detailed, low-value troubleshooting information.infofor meaningful lifecycle and administrative actions.warnfor degraded behavior that Lumi can continue through.errorfor 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.