Navigace
ELOWEN · Dokumentace

Poslední aktualizace: 20. října 2026

Procházet dokumentaci · Typed logging and reports

Tento článek zatím není přeložen. Je dostupný v angličtině.

Developer reference

Typed logging and reports

Plugin logger methods

ctx.logger.debug(message, extra?) reuses core Logger['debug'] and forwards immediately through the host debug logger with the [plugin:<name>] prefix. It follows the host debug-level visibility setting and propagates injected logger failures. Sandbox's guest file agent uses it for the session-close operation count and latency summary. Debug has the canonical two-argument signature and no reporting metadata; this additive PluginLogger method retains shared API 10.

For info, warn and error, ctx.logger binds plugin identity independently of formatted text. PluginLogger's info accepts optional extra data and LogMetadata. warn and error require three arguments: message, extra data (use undefined when absent), and metadata naming an optional declared code or explicitly selecting local-only. PluginLogger reuses core Logger's warning/error signatures; injected logger types must use PluginLogger rather than weakening this rule. Both core's Logger and PluginLogger require that third argument. Registry forwarding always stamps the owning plugin, so a plugin cannot change its ring owner. Only the daemon bootstrap owns the private observer; plugins cannot register a log observer.

Reporting decision and local-only lines

Metadata may carry one of core's declared problem codes (PROBLEM_CODES in src/licence/contract.ts) and reporting: 'local-only', which keeps that line on the instance. Live codes must belong to PROBLEM_CODES; restored immutable batches retain their original validated wire codes.

With problem reporting enabled, a plugin warning or error with an absent or undeclared code, or an invalid reporting policy, reports plugin.reporting_invalid under the plugin's name with no text sample. A core-owned warning or error with the same fault reports runtime.reporting_invalid instead (src/problems/service.ts:165). Absent or invalid classification is a counted metadata fault with no exported text, while human logs keep the original text.

Choose reportability where the typed cause is known: every warning/error emitter declares a precise code or explicitly selects local-only. A plugin owner alone is not a reporting decision. Normal progress and successful fallback are info. A warning/error should be reported when it can drive an Elowen bug fix or refactor. Operator-only conditions such as a customer's application returning HTTP 5xx use local-only; successful administrative audit transitions use info. Unexpected failures of Elowen's own transport remain reported. Do not filter message strings in the collector or remove local diagnostics. local-only lines never leave the instance.

Redacted samples

Declared codes carry up to three redacted samples (src/problems/redact.ts removes secret-named values such as password= or apiKey:, bearer tokens, cookies, URL userinfo and query strings, e-mail addresses, IP addresses and UUID-like ids, keeps only the last segment of absolute paths, and replaces long hex and mixed-case base64 runs; it cuts the text to 500 characters, including the trailing ellipsis). A line whose text can carry message content or business data (chat or e-mail text, prompts, model output, order or CRM records) travels with its sample like any other: redaction runs before anything is stored or sent. Provider, process and transport error prose is redacted rather than dropped. Names of people may stay.

Samples are bounded: at most three per declared code, at most 500 characters each (PROBLEM_LIMITS in src/licence/contract.ts). The collector keeps at most 200 distinct entries (code, level, plugin and version) and counts occurrences beyond that as dropped (src/problems/service.ts:146); a batch holds at most 50 entries. Redaction runs once per reported warning or error, when the daemon builds the entry (src/problems/service.ts:174).

Interval failure reporting

registerInterval owns both its host timer and promises returned by ticks. Return asynchronous tick work: ticks scheduled while that promise is pending are skipped, and shutdown awaits it before contributed services stop, regardless of registration order. The first two failed ticks of an interval stay in the local log. A failure series is reported exactly once, on its third consecutive failed tick; subsequent failures keep their local diagnostic and cumulative count. The next successful tick logs local INFO recovery and resets the series. Forwarded logger metadata stays intact while core stamps the true owner.

Plugin alert reporting

PluginAlertInput requires reporting from the core AlertReporting union: local-only, already-reported, or problem with one of the declared categories. The host-disk problem category maps to alert.host_disk; core maintenance uses it for warning and critical host filesystem pressure. It uses the same once-per-transition reporting as other alert categories, sends no measurements or paths, and adds no separate polling. Use local-only for operator conditions, already-reported when an existing error emitter owns the problem, or problem with a declared category for a standalone condition. The host validates this decision and reports visible transitions once per condition without dynamic keys, rendered text or business parameters.

Registry plugin classification

Registry plugins preserve host metadata through injected logger callbacks. Chatbot action policy refusals and optional Sites thumbnail retries are info. Failed chatbot turns, unanswered actions, gateway synchronization and publication transport failures use distinct typed classifications. Sites application HTTP 5xx diagnostics remain local-only, as selected from typed transport/probe outcomes. Registry brainStatusFor warnings forward the real provider owner through the same third metadata argument, including thrown, non-object, duplicate-field and invalid-statusline output. With reporting enabled, typed publisher-owned plugin defects are eligible; customer and unknown plugins remain local.

HTTP route failures and body limits

PluginHttpRoute supports problemReporting: 'local-only' for a route whose failures are operator-only. The host stamps handler and stream failures from the resolved route metadata, so only that route's failures are reported as local-only; unrelated routes remain collected. Core's own request boundary applies the same rule to the canonical licence report route (src/api/server.ts). With no marker, normal diagnostics apply. Cost is one optional field, no extra body buffering or observer registration.

A maxStreamBodyBytes limit applies only to an exact mount, and plugin registration accepts a value up to 10 MiB (src/plugins/pluginContext.ts). Every other hook keeps the ordinary 1 MiB buffered limit (MAX_HOOK_BODY_BYTES in src/api/routes/hooks.ts). Hook handler and stream failures are reported as hooks.handler_failed and hooks.stream_failed with their redacted request path, and carry the route's reporting decision (src/api/routes/hooks.ts:79 and :87).

Plugin contribution model

Elowen plugins are trusted ESM packages. A plugin is a directory containing an elowen-plugin.json manifest and an ESM entry module that exports register(ctx). The manifest is the declaration and audit surface. The registration function is the executable source of the plugin's contributions.

A plugin contributes through a host seam. The plugin owns domain behavior, domain data presentation, and provider-specific copy. Core owns shared protocol, authentication and authorization boundaries, tenancy, persistence boundaries, process supervision, lifecycle framing, generic UI hosting, and generic fallbacks. Do not add a plugin-name condition to a shared host surface when a contribution seam can express the behavior.

A plugin may contribute tools, skills, slash commands, prompt context, hooks, HTTP routes, WebSocket routes, platform adapters, services, controls, MCP tools, configuration forms, and browser UI. Every contribution is validated at the plugin boundary and every host capability is deny-by-default unless the manifest grants it. Every seam has an explicit absent behavior: an unavailable result, an omitted optional surface, or the existing generic fallback. A disabled or missing provider must never look like a successful empty result.

The cache rule is important. Stable instructions belong in the system prompt. Content that can change on every turn belongs in turn context, preferably with placement: "after-user" when it qualifies the request. Never put per-turn state in a system-prompt fragment: the system prompt is the cached prefix.

Typed instance conditions

elowen-plugin-shared/errors exports the one InstanceConditionError class and isInstanceCondition(error) predicate shared with core, plus HostConditionError and isHostCondition(error) for a condition of the host itself. Both predicates follow native Error causes and accept an aggregate only when every member matches. Known SDK refusals and configured-service failures may be wrapped with this class at their origin. Preserve unknown internal errors unchanged. When formatting a diagnostic first, pass its actual cause as ctx.logger.warn(message, extra, { code, failure: error }). The host retains all local diagnostics while excluding typed instance causes from publisher reports. Native Error causes and aggregates preserve this classification; an aggregate containing a software defect remains reportable. The failure object stays local and never enters the report wire contract.

A host condition is an instance condition whose audience the collector decides by repetition rather than per occurrence: the third occurrence of the same problem identity within five minutes reports those three, and each later one while the condition keeps recurring; a lone occurrence stays in the local log. Pool acquisition failure (DatabaseConnectionError) and the event-loop watchdog's measured scheduling stalls carry it. Explicit local-only metadata still wins, and a host condition under an instance-only code stays local.

Platform adapters using elowen-plugin-shared/turnRunner receive log(detail, metadata) for turn, delivery and post-reply failures. Forward the metadata unchanged beside the adapter's problem code:

log: (detail, metadata) => ctx.logger.warn(detail, undefined, {
  code: 'discord.turn_failed',
  ...metadata,
})

The second argument contains the original failure. The shared errors.isInstanceCondition authority also supplies reporting: 'local-only' for a user or instance condition that is not a host condition; an ordinary software defect carries no reporting override. Native cause wrappers and mixed aggregates keep the same semantics as core. Diagnostic text and user replies are unchanged. This additive argument requires no shared API major bump; pin requiresCore to the first verified core artifact carrying it. Keep the existing exact requiresSharedApi pin. Do not parse the detail string or serialize the raw failure.

Publisher ownership is established from bundled plugins and verified install receipts. Customer and unknown plugins remain local. The complete code policy and the registry migration work are documented in Problem report audiences.

File writer and retry ownership

src/shared/logger.ts keeps console output and the typed observer synchronous, then enqueues file lines through one OrderedLogWriter per isolate. Adjacent lines for the same local-day file are coalesced and sent in FIFO batches to logWriterWorker.ts. The worker owns file I/O; normal calls do not wait for storage. The process exit handler synchronously waits on a shared-memory completion barrier for both queued and in-flight batches, including a queued fatal line when the writer and destination remain available. Destination dates are captured when the line is logged, preserving midnight rotation. Writer initialization or transport failure disables file delivery for that isolate and reports directly to stderr; console output remains available. A failed file write is diagnosed by the worker and does not skip later destinations. Initialization has a bounded exit wait; once initialized, storage completion retains the synchronous writer's unbounded exit-flush contract.

Daemon and runner imports resolve the compiled worker beside the logger. The standalone privileged worker script owns its JSON stderr diagnostics and does not import this file logger, so its installed helper layout requires no new worker file.

Retry owners use elowen-plugin-shared/consecutiveFailures: fail(key) reports only attempt three, and reset(key) follows successful completion of that operation. Keys identify operations or resources, not error text. Every failed attempt still logs with reporting: 'local-only' unless it is the report attempt. Skipped work and cancellation are not successful attempts. Existing durable retry counters remain authoritative for recovery, result delivery, artifact deletion and digest generation. Definite terminal faults, invalid states, boot/update failures and reporting-metadata faults remain immediate. No collector-side text filtering or suppression is added.

Owning code and minimal use

The owning code is src/shared/logger.ts (LogMetadata, the Logger type and the single setLogSink observer), src/plugins/api.ts (PluginLogger, PluginAlertInput, PluginHttpRoute), src/plugins/pluginContext.ts (the scoped plugin logger that stamps the owner), src/problems/service.ts (projection of each event into a report entry), src/problems/redact.ts (sample redaction), src/licence/contract.ts (PROBLEM_CODES and PROBLEM_LIMITS) and src/alerts/alertService.ts (the AlertReporting union).

A minimal warning from a plugin declares a code from PROBLEM_CODES and a classification:

ctx.logger.warn('brain status provider failed', error, {
  code: 'plugin.status_failed',
});

An operator-only condition that should stay on the instance declares local-only and no code:

ctx.logger.warn('upstream answered HTTP 503', undefined, { reporting: 'local-only' });

Absent behavior: with problem reporting disabled, nothing is exported, and the human log still prints the line. When a classification is missing, the line is counted as plugin.reporting_invalid and carries no text. The real consumer of the typed logger is the Chatbot plugin in the registry, which classifies failed turns as chatbot.turn_failed and refusals as info.