diagnostics-otel plugin
using OTLP/HTTP (protobuf). Logs can also be written as stdout JSONL for
container and sandbox log pipelines. Any collector or backend that accepts
OTLP/HTTP works without code changes. For local file logs, see
Logging.
- Diagnostics events are structured, in-process records emitted by the Gateway and bundled plugins for model runs, message flow, sessions, queues, and exec.
diagnostics-otelsubscribes to those events and exports them as OpenTelemetry metrics, traces, and logs over OTLP/HTTP, and can mirror log records to stdout JSONL.- Provider calls receive a W3C
traceparentheader from OpenClaw’s trusted model-call span context when the provider transport accepts custom headers. Plugin-emitted trace context is not propagated. - Exporters attach only when both the diagnostics surface and the plugin are enabled, so in-process cost stays near zero by default.
Quick start
openclaw plugins enable diagnostics-otel.
protocol supports http/protobuf only. Since traces and metrics default to enabled, any other value (including grpc) aborts the entire diagnostics-otel subscription with an unsupported protocol warning - this also stops stdout log export. Explicitly set traces: false and metrics: false if you only want logsExporter: "stdout" with a non-OTLP protocol value.Signals exported
Toggle
traces, metrics, and logs independently. Traces and metrics
default to on when diagnostics.otel.enabled is true; logs default to off
and export only when diagnostics.otel.logs is explicitly true. Log export
defaults to OTLP; set diagnostics.otel.logsExporter to stdout for JSONL on
stdout, or both for both.
Which processes export
- Gateway starts the exporter at startup and exports from the Gateway
process for every run it executes, including
openclaw agentturns dispatched to it. - One-shot local runs (
openclaw agent --local) execute in the CLI process. When OTel export is configured and the plugin is enabled, that same CLI process starts one exporter instance for the run and flushes buffered spans, metrics, and logs before the process exits. The CLI waits at most 5 seconds for the diagnostic-event queue to drain and 10 more for the flush, so an unreachable collector cannot hold the command open. A collector that accepts the connection but never answers can still delay exit until the exporter’s own request timeout (OTEL_EXPORTER_OTLP_TIMEOUT). In JSON output mode, these one-shot runs suppress only the stdout JSONL log sink so command stdout stays reserved for the JSON response; OTLP traces, metrics, and logs continue when configured. openclaw agent execalso runs the agent embedded in the CLI process, but does not yet start this exporter, so its runs export no telemetry. Dispatch through the Gateway, or useopenclaw agent --local, when you need traces from a headless run.
Configuration reference
metricNamePrefix replaces the default openclaw. prefix only on
OpenClaw-owned metrics. For example, "acme." exports openclaw.tokens as
acme.tokens; set it to "" to export tokens with no prefix. Non-empty
values must start with an ASCII letter, use only letters, digits, underscores,
dots, hyphens, and slashes, and contain at most 128 characters. Set it to
"acme.openclaw." if you want acme.openclaw.tokens. Standard
semantic-convention metrics such as
gen_ai.client.token.usage and gen_ai.client.operation.duration keep their
original names. Leave the option unset to preserve every current metric name.
Enabling or changing this option renames the affected metric series, so update
dashboards, alerts, and recording rules that query the old names.
Environment variables
Continue an upstream WebSocket trace
An authenticated Gateway WebSocket client can attach a W3Ctraceparent to
each request frame:
traceparent independently for
every RPC. Concurrent requests remain isolated even when their work
interleaves.
The field is accepted only after the existing Gateway authentication handshake
and does not affect authentication or method authorization. A traceparent on
the initial connect frame is ignored. Missing or syntactically malformed
values within the 128-character field limit silently fall back to a fresh
request trace; longer values make the request frame invalid. tracestate and
baggage are not accepted by the Gateway WebSocket protocol.
Privacy and content capture
Raw model/tool content is not exported by default. Spans carry bounded identifiers (channel, provider, model, error category, hash-only request ids, tool source, tool owner, skill name/source) and never include prompt text, response text, tool inputs, tool outputs, skill file paths, or session keys. Values that look like scoped agent session keys (for example starting withagent:) are replaced with unknown on low-cardinality attributes. OTLP log
records keep severity, logger, code location, trusted trace context, and
sanitized attributes by default; the raw log message body is exported only
when diagnostics.otel.captureContent is true. Talk metrics export only
bounded event metadata (mode, transport, provider, event type) - no
transcripts, audio payloads, session ids, turn ids, call ids, room ids, or
handoff tokens.
Outbound model requests may include a W3C traceparent header generated only
from OpenClaw-owned diagnostic trace context for the active model call.
Existing caller-supplied traceparent headers are replaced, so plugins or
custom provider options cannot spoof cross-service trace ancestry.
Set diagnostics.otel.captureContent to true only when your collector and
retention policy are approved for prompt, response, tool, and tool-definition
text. This enables bounded, redacted input messages, output messages, tool
inputs, tool outputs, tool definitions, and OTLP log bodies. System prompts
remain excluded. Provider-internal thinking and redacted_thinking payloads
are also excluded: compatibility attributes retain only a redacted structural
marker, while GenAI message attributes omit those parts.
toolInputs/toolOutputs content is captured for the built-in agent
runtime’s tool executions (openclaw.content.tool_input and
gen_ai.tool.call.arguments on completed/error spans;
openclaw.content.tool_output and gen_ai.tool.call.result on completed
spans). The openclaw.content.* names remain the stable OpenClaw attribute
names; the gen_ai.tool.call.* copies mirror them for semconv-native viewers.
External harness tool calls (Codex, Claude CLI) emit
tool.execution.* spans without content payloads. Captured content travels on a
trusted, listener-only channel and is never placed on the public diagnostic event
bus.
Sampling and flushing
- Traces:
diagnostics.otel.sampleRatesets aTraceIdRatioBasedSampleron the root span only (0.0drops all,1.0keeps all). Unset uses the OpenTelemetry SDK default (always-on). - Metrics:
diagnostics.otel.flushIntervalMs(clamped to a minimum of1000); unset uses the SDK’s periodic-export default. - Logs: OTLP logs respect
logging.level(file log level) and use the diagnostic log-record redaction path, not console formatting. High-volume installs should prefer OTLP collector sampling/filtering over local sampling. Setdiagnostics.otel.logsExporter: "stdout"when your platform already ships stdout/stderr to a log processor and you have no OTLP logs collector. Stdout records are one JSON object per line withts,signal,service.name, severity, body, redacted attributes, and trusted trace fields when available. - File-log correlation: JSONL file logs include top-level
traceId,spanId,parentSpanId, andtraceFlagswhen the log call carries a valid diagnostic trace context, letting log processors join local log lines with exported spans. - Request correlation: Gateway HTTP requests and WebSocket frames create
an internal request trace scope. Logs and diagnostic events inside that
scope inherit the request trace by default, while agent run and model-call
spans are created as children so provider
traceparentheaders stay on the same trace. - Model-call correlation:
openclaw.model.callspans include safe prompt component sizes by default and per-call token attributes when the provider result exposes usage.openclaw.model.usageremains the run-level accounting span for aggregate cost, context, and channel dashboards, and stays on the same diagnostic trace when the emitting runtime has trusted trace context.
Model-call observation units
Everyopenclaw.model.call span identifies what its lifecycle measures through
openclaw.model_call.observation_unit:
request- one observable model/provider request. Native embedded model calls use this unit, and exporters treat a missing value asrequestfor compatibility with older or external emitters.turn- one opaque agent CLI turn that may contain hidden model requests, retries, tool work, or background work. Claude Code CLI and Codex app-server calls use this unit.
chat, generate_content, or text_completion), while turn spans use
gen_ai.operation.name = invoke_agent. Both contribute to
gen_ai.client.operation.duration, where the operation name keeps direct
request latency separate from full-turn latency. OpenClaw’s OTEL model-call
metrics also include openclaw.model_call.observation_unit; the Prometheus
model-call metrics expose the equivalent observation_unit label.
Claude Code CLI model-call fidelity
Claude Code CLI turns emit one synthetic, turn-levelopenclaw.model.call
span. These are not Anthropic HTTP request spans. They use openclaw.api = claude-code, openclaw.model_call.observation_unit = turn, and identify
the operation as gen_ai.operation.name = invoke_agent. They identify
OpenClaw’s CLI boundary through
openclaw.transport:
stdio- one-shot local Claude Code process.stdio-live- one turn on a managed persistent Claude stdio session.paired-node-cli- one-shot Claude Code execution delegated to a paired node.
openclaw.harness.run (openclaw.harness.id = claude-cli)
contains openclaw.run, which contains the Claude openclaw.model.call
span. The harness and run spans are synthetic OpenClaw turn boundaries, not
Claude Code internal phases. One-shot and managed stdio turns use the same
hierarchy; a real fresh-session retry creates another model-call child inside
the same OpenClaw run.
The span starts when OpenClaw admits the prepared CLI turn and ends only after
that turn succeeds or fails. For managed sessions, an interim success result
does not end the span while Claude reports result-holding background agents or
workflows; the final post-drain result does. Abort, timeout, process failure,
output/parse failure, and other turn failures end the same span with an error.
Claude Code reports per-assistant-message usage and may also report cumulative
usage on its terminal result. OpenClaw reply accounting continues to use the
last assistant message so existing cost semantics do not change; the
turn-level model-call span uses terminal cumulative usage when available,
including cache-read and cache-creation tokens.
For these CLI spans, byte and timing fields describe the observable OpenClaw
CLI boundary:
openclaw.model_call.request_bytesis the UTF-8 size of the prompt value sent over one-shot stdin/argv, or the managed stdio JSONL user envelope. It is not the size of Claude Code’s hidden model request.openclaw.model_call.response_bytesis the UTF-8 size of Claude CLI stdout observed during the turn. It is not Anthropic HTTP response size.openclaw.model_call.time_to_first_byte_msis time to the first observable Claude CLI stdout or stderr output. It is not network TTFB.
captureContent enabled, the span exports the effective prompt OpenClaw
sends to Claude Code and visible assistant text/tool-call identity
through gen_ai.input.messages and gen_ai.output.messages. Tool arguments,
internal thinking, opaque thinking signatures, tool results, and system prompts
are omitted from the Claude assistant envelope. OpenClaw does not
claim access to Claude Code’s private system prompt, hidden resumed or
compacted request payload, native internal tool schemas, raw Anthropic HTTP
request, internal retries, upstream request id, or true network TTFB. Because
Claude Code does not expose its effective native tool definitions accurately,
these spans do not populate gen_ai.tool.definitions.
External Claude harness tool spans remain metadata-only even when tool content
capture is enabled. As with every model span, captured Claude CLI content uses
the trusted listener-only path and the exporter’s existing redaction and size
bounds; content remains off by default.
Exported metrics
Model usage
openclaw.tokens(counter, attrs:openclaw.token,openclaw.channel,openclaw.provider,openclaw.model,openclaw.agent)openclaw.cost.usd(counter, attrs:openclaw.channel,openclaw.provider,openclaw.model)openclaw.run.duration_ms(histogram, attrs:openclaw.channel,openclaw.provider,openclaw.model)openclaw.context.tokens(histogram, attrs:openclaw.context,openclaw.channel,openclaw.provider,openclaw.model)gen_ai.client.token.usage(histogram, GenAI semantic-conventions metric, attrs:gen_ai.token.type=input/output,gen_ai.provider.name,gen_ai.operation.name,gen_ai.request.model)gen_ai.client.operation.duration(histogram, seconds, GenAI semantic-conventions metric for model requests and synthetic agent turns; attrs:gen_ai.provider.name,gen_ai.operation.name,gen_ai.request.model, optionalerror.type; turn observations usegen_ai.operation.name = invoke_agent)openclaw.model_call.duration_ms(histogram, attrs:openclaw.provider,openclaw.model,openclaw.api,openclaw.transport,openclaw.model_call.observation_unit, plusopenclaw.errorCategoryandopenclaw.failureKindon classified errors)openclaw.model_call.request_bytes(histogram, UTF-8 byte size of the final model request payload; for Claude Code CLI, the observable prompt input/envelope described above; no raw payload content)openclaw.model_call.response_bytes(histogram, UTF-8 byte size of streamed response chunk payloads; high-frequency text, thinking, and tool-call deltas count only incrementaldeltabytes; for Claude Code CLI, observed stdout bytes; no raw response content)openclaw.model_call.time_to_first_byte_ms(histogram, elapsed time before the first streamed response event; for Claude Code CLI, first observable CLI output rather than network TTFB)openclaw.model.failover(counter, attrs:openclaw.provider,openclaw.model,openclaw.failover.to_provider,openclaw.failover.to_model,openclaw.failover.reason,openclaw.failover.suspended,openclaw.lane)openclaw.skill.used(counter, attrs:openclaw.skill.name,openclaw.skill.source,openclaw.skill.activation, optionalopenclaw.agent, optionalopenclaw.toolName)
Message flow
openclaw.webhook.received(counter, attrs:openclaw.channel,openclaw.webhook)openclaw.webhook.error(counter, attrs:openclaw.channel,openclaw.webhook)openclaw.webhook.duration_ms(histogram, attrs:openclaw.channel,openclaw.webhook)openclaw.message.queued(counter, attrs:openclaw.channel,openclaw.source)openclaw.message.received(counter, attrs:openclaw.channel,openclaw.source)openclaw.message.dispatch.started(counter, attrs:openclaw.channel,openclaw.source)openclaw.message.dispatch.completed(counter, attrs:openclaw.channel,openclaw.outcome,openclaw.reason,openclaw.source)openclaw.message.dispatch.duration_ms(histogram, attrs:openclaw.channel,openclaw.outcome,openclaw.reason,openclaw.source)openclaw.message.processed(counter, attrs:openclaw.channel,openclaw.outcome)openclaw.message.duration_ms(histogram, attrs:openclaw.channel,openclaw.outcome)openclaw.message.delivery.started(counter, attrs:openclaw.channel,openclaw.delivery.kind)openclaw.message.delivery.duration_ms(histogram, attrs:openclaw.channel,openclaw.delivery.kind,openclaw.outcome,openclaw.errorCategory)
Talk
openclaw.talk.event(counter, attrs:openclaw.talk.event_type,openclaw.talk.mode,openclaw.talk.transport,openclaw.talk.brain,openclaw.talk.provider)openclaw.talk.event.duration_ms(histogram, attrs: same asopenclaw.talk.event; emitted when a Talk event reports duration)openclaw.talk.audio.bytes(histogram, attrs: same asopenclaw.talk.event; emitted for Talk audio frame events that report byte length)
Queues and sessions
openclaw.queue.lane.enqueue(counter, attrs:openclaw.lane)openclaw.queue.lane.dequeue(counter, attrs:openclaw.lane)openclaw.queue.depth(histogram, attrs:openclaw.laneoropenclaw.channel=heartbeat)openclaw.queue.wait_ms(histogram, attrs:openclaw.lane)openclaw.session.state(counter, attrs:openclaw.state,openclaw.reason)openclaw.session.stuck(counter, attrs:openclaw.state; emitted for recoverable stale session bookkeeping)openclaw.session.stuck_age_ms(histogram, attrs:openclaw.state; emitted for recoverable stale session bookkeeping)openclaw.session.turn.created(counter, attrs:openclaw.agent,openclaw.channel,openclaw.trigger)openclaw.session.recovery.requested(counter, attrs:openclaw.state,openclaw.action,openclaw.active_work_kind,openclaw.reason)openclaw.session.recovery.completed(counter, attrs:openclaw.state,openclaw.action,openclaw.status,openclaw.active_work_kind,openclaw.reason)openclaw.session.recovery.age_ms(histogram, attrs: same as the matching recovery counter)openclaw.run.attempt(counter, attrs:openclaw.attempt)
Session liveness telemetry
Aprocessing session does not age toward the built-in liveness threshold while OpenClaw observes reply, tool, status, block, or ACP runtime progress. Typing keepalives do not count as progress, so a silent model or harness can still be detected.
OpenClaw classifies sessions by the work it can still observe:
session.long_running: active embedded work, model calls, or tool calls are still making progress. Owned silent model calls also report as long-running before the built-in abort threshold, so slow or non-streaming model providers do not look like stalled gateway sessions while abort-observable.session.stalled: active work exists, but the active run has not reported recent progress. Owned model calls switch fromsession.long_runningtosession.stalledat or after the built-in abort threshold; ownerless stale model/tool activity is not treated as harmless long-running work. Stalled embedded runs stay observe-only at first, then abort-drain after the abort threshold with no progress so queued turns behind the lane can resume.session.stuck: stale session bookkeeping with no active work, or an idle queued session with stale ownerless model/tool activity. This releases the affected session lane immediately after recovery gates pass.
session.recovery.requested and
session.recovery.completed events. Diagnostic session state is marked idle
only after a mutating recovery outcome (aborted or released) and only if
the same processing generation is still current.
Only session.stuck emits the openclaw.session.stuck counter, the
openclaw.session.stuck_age_ms histogram, and the openclaw.session.stuck
span. Repeated session.stuck diagnostics back off while the session remains
unchanged, so dashboards should alert on sustained increases rather than
every heartbeat tick. For the config knob and defaults, see
Configuration reference.
Liveness warnings also emit:
openclaw.liveness.warning(counter, attrs:openclaw.liveness.reason)openclaw.liveness.event_loop_delay_p99_ms(histogram, attrs:openclaw.liveness.reason)openclaw.liveness.event_loop_delay_max_ms(histogram, attrs:openclaw.liveness.reason)openclaw.liveness.event_loop_utilization(histogram, attrs:openclaw.liveness.reason)openclaw.liveness.cpu_core_ratio(histogram, attrs:openclaw.liveness.reason)
Harness lifecycle
openclaw.harness.duration_ms(histogram, attrs:openclaw.harness.id,openclaw.harness.plugin,openclaw.outcome,openclaw.harness.phaseon errors)
Tool execution and loop detection
openclaw.tool.execution.duration_ms(histogram, attrs:gen_ai.tool.name,openclaw.toolName,openclaw.tool.source,openclaw.tool.owner,openclaw.tool.params.kind, plusopenclaw.errorCategoryon errors)openclaw.tool.execution.blocked(counter, attrs:gen_ai.tool.name,openclaw.toolName,openclaw.tool.source,openclaw.tool.owner,openclaw.tool.params.kind,openclaw.deniedReason)openclaw.tool.loop(counter, attrs:openclaw.toolName,openclaw.loop.level,openclaw.loop.action,openclaw.loop.detector,openclaw.loop.count, optionalopenclaw.loop.paired_tool; emitted when a repetitive tool-call loop is detected)
Exec
openclaw.exec.duration_ms(histogram, attrs:openclaw.exec.target,openclaw.exec.mode,openclaw.outcome,openclaw.failureKind)
Diagnostics internals (memory, payloads, exporter health)
openclaw.payload.large(counter, attrs:openclaw.payload.surface,openclaw.payload.action,openclaw.channel,openclaw.plugin,openclaw.reason)openclaw.payload.large_bytes(histogram, attrs: same asopenclaw.payload.large)openclaw.memory.rss_bytes/openclaw.memory.heap_used_bytes/openclaw.memory.heap_total_bytes/openclaw.memory.external_bytes/openclaw.memory.array_buffers_bytes(histograms, no attrs; process memory samples)openclaw.memory.pressure(counter, attrs:openclaw.memory.level,openclaw.memory.reason)openclaw.diagnostic.async_queue.dropped(counter, attrs:openclaw.diagnostic.async_queue.drop_class; internal diagnostic-queue backpressure drops)openclaw.telemetry.exporter.events(counter, attrs:openclaw.exporter,openclaw.signal,openclaw.status, optionalopenclaw.reason, optionalopenclaw.errorCategory; exporter lifecycle/failure self-telemetry)
Exported spans
openclaw.model.usageopenclaw.channel,openclaw.provider,openclaw.modelopenclaw.tokens.*(input/output/cache_read/cache_write/total)gen_ai.systemby default, orgen_ai.provider.namewhen the latest GenAI semantic conventions are opted ingen_ai.request.model,gen_ai.operation.name,gen_ai.usage.*
openclaw.runopenclaw.outcome,openclaw.channel,openclaw.provider,openclaw.model,openclaw.errorCategory
openclaw.model.callgen_ai.systemby default, orgen_ai.provider.namewhen the latest GenAI semantic conventions are opted ingen_ai.request.model,gen_ai.operation.name,openclaw.provider,openclaw.model,openclaw.api,openclaw.transport,openclaw.model_call.observation_unit(requestorturn)openclaw.errorCategory,error.type, and optionalopenclaw.failureKindon errorsopenclaw.model_call.request_bytes,openclaw.model_call.response_bytes,openclaw.model_call.time_to_first_byte_msopenclaw.model_call.prompt.input_messages_count,openclaw.model_call.prompt.input_messages_chars,openclaw.model_call.prompt.system_prompt_chars,openclaw.model_call.prompt.tool_definitions_count,openclaw.model_call.prompt.tool_definitions_chars,openclaw.model_call.prompt.total_chars(safe component sizes only, no prompt text)openclaw.model_call.usage.*andgen_ai.usage.*when the result carries usage for that request or aggregate turn- Span event
openclaw.provider.requestwith attributeopenclaw.upstreamRequestIdHash(bounded, hash-based) when the upstream provider result exposes a request id; raw ids are never exported - With
OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental, request spans use the latest GenAI inference span name{gen_ai.operation.name} {gen_ai.request.model}. Turn spans useinvoke_agentbecause OpenClaw does not claim a native agent name from the opaque CLI boundary. Both useCLIENTspan kind instead ofopenclaw.model.call.
openclaw.harness.runopenclaw.harness.id,openclaw.harness.plugin,openclaw.outcome,openclaw.provider,openclaw.model,openclaw.channel- On completion:
openclaw.harness.result_classification,openclaw.harness.yield_detected,openclaw.harness.items.started,openclaw.harness.items.completed,openclaw.harness.items.active - On error:
openclaw.harness.phase,openclaw.errorCategory, optionalopenclaw.harness.cleanup_failed
openclaw.tool.executiongen_ai.tool.name,gen_ai.operation.name(execute_tool),openclaw.toolName,openclaw.tool.source, optionalgen_ai.tool.call.id,openclaw.tool.owner,openclaw.tool.params.*- Optional
openclaw.errorCategory/openclaw.errorCodeon errors,openclaw.deniedReasonandopenclaw.outcome=blockedwhen denied by policy or sandbox
openclaw.execopenclaw.exec.target,openclaw.exec.mode,openclaw.outcome,openclaw.failureKind,openclaw.exec.command_length,openclaw.exec.exit_code,openclaw.exec.exit_signal,openclaw.exec.timed_out
openclaw.webhook.processedopenclaw.channel,openclaw.webhook
openclaw.webhook.erroropenclaw.channel,openclaw.webhook,openclaw.error
openclaw.message.processedopenclaw.channel,openclaw.outcome,openclaw.reason
openclaw.message.deliveryopenclaw.channel,openclaw.delivery.kind,openclaw.outcome,openclaw.errorCategory,openclaw.delivery.result_count
openclaw.session.stuckopenclaw.state,openclaw.ageMs,openclaw.queueDepth
openclaw.context.assembledopenclaw.prompt.size,openclaw.history.size,openclaw.context.tokens,openclaw.errorCategory(no prompt, history, response, or session-key content)
openclaw.tool.loopopenclaw.toolName,openclaw.loop.level,openclaw.loop.action,openclaw.loop.detector,openclaw.loop.count, optionalopenclaw.loop.paired_tool(no loop messages, params, or tool output)
openclaw.memory.pressureopenclaw.memory.level,openclaw.memory.reason,openclaw.memory.rss_bytes,openclaw.memory.heap_used_bytes,openclaw.memory.heap_total_bytes,openclaw.memory.external_bytes,openclaw.memory.array_buffers_bytes, optionalopenclaw.memory.threshold_bytes/openclaw.memory.rss_growth_bytes/openclaw.memory.window_ms
openclaw.content.* attributes for the specific
content classes you opted into.
Diagnostic event catalog
The events below back the metrics and spans above. Public events are also available for direct plugin subscription; trusted core events such asmodel.usage are restricted to authorized internal consumers.
run.progress and run.execution_phase are direct-only lifecycle signals;
the diagnostics-otel plugin does not export them as standalone OTLP signals.
Event kinds and run.execution_phase.phase values are additive. TypeScript
consumers should keep default branches instead of assuming either union is
permanently exhaustive.
Model usage
model.usage is a trusted, in-process diagnostic event, not a JSONL log
record. A representative event has this shape:
tsis a Unix timestamp in milliseconds;seqis process-local.usageholds turn-level token counts.promptTokensincludesinput,cacheRead, andcacheWrite;lastCallUsage, when available, describes the final model call.context.usedis the current prompt/context snapshot and can be lower thanusage.totalwhen cached input or tool-loop calls are involved.- Provider/model/session identifiers, token buckets,
lastCallUsage,context,costUsd,durationMs, andtracefields are optional.costUsdis an estimate and can be absent when model pricing is unavailable; it is not provider-reported billing. Trace context can also includeparentSpanId.
/tmp/openclaw/openclaw-YYYY-MM-DD.log JSONL file and
diagnostics.otel.logsExporter: "stdout" contain ordinary log records, not raw
model.usage events. Public diagnostic subscriptions and
diagnostics.stability do not expose trusted core usage events. The
diagnostics-otel plugin converts them to metrics such as openclaw.tokens and
openclaw.cost.usd and to openclaw.model.usage spans; those usage metrics
and spans intentionally omit session identifiers.
For an external integration that needs session-correlated usage, query the
authenticated Gateway instead:
operator.read. sessions.usage can include per-session
sessionId, provider/model details, and token/cost summaries; per-session usage
can be temporarily null while its cache refreshes. usage-cost provides
aggregate estimates. Omit agentScope or --all-agents to scope the report
to the default agent. For continuously updated clients,
subscribe to session changes instead of polling usage reports.
See the Gateway RPC method reference
for usage methods and request options.
Message flow
webhook.received/webhook.processed/webhook.errormessage.queued/message.processedmessage.delivery.started/message.delivery.completed/message.delivery.error
queue.lane.enqueue/queue.lane.dequeuesession.state/session.long_running/session.stalled/session.stuckrun.attempt/run.progressrun.execution_phase(public, session-correlated embedded-runner startup milestones)diagnostic.heartbeat(aggregate counters: webhooks/queue/session)
harness.run.started/harness.run.completed/harness.run.error- per-run lifecycle for the agent harness. IncludesharnessId, optionalpluginId, provider/model/channel, and run id. Completion addsdurationMs,outcome, optionalresultClassification,yieldDetected, anditemLifecyclecounts. Errors addphase(prepare/start/send/resolve/cleanup),errorCategory, and optionalcleanupFailed.
exec.process.completed- terminal outcome, duration, target, mode, exit code, and failure kind. Command text and working directories are not included.exec.approval.followup_suppressed- stale approval follow-up dropped after a session rebound. IncludesapprovalId,reason(session_rebound),phase(direct_deliveryorgateway_preflight), and the dispatcher timestamp. Session keys, routes, and command text are not included.
Without an exporter
Keep diagnostics events available to plugins or custom sinks without runningdiagnostics-otel:
logging.level, use diagnostics
flags. Flags are case-insensitive and support wildcards (telegram.* or
*):
logging.file) and is still
redacted by the always-on log redaction policy. Full guide:
Diagnostics flags.
Disable
diagnostics-otel out of plugins.allow, or run
openclaw plugins disable diagnostics-otel.
Related
- Logging - file logs, console output, CLI tailing, and the Control UI Logs tab
- Gateway logging internals - WS log styles, subsystem prefixes, and console capture
- Diagnostics flags - targeted debug-log flags
- Diagnostics export - operator support-bundle tool (separate from OTEL export)
- Configuration reference - full
diagnostics.*field reference