# 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:

```js
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:

```js
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:

```js
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.
