Skip to main content
OpenClaw exports diagnostics through the bundled diagnostics-otel plugin using OTLP/HTTP (protobuf). Any collector or backend that accepts OTLP/HTTP works without code changes. For local file logs and how to read them, see Logging.

How it fits together

  • Diagnostics events are structured, in-process records emitted by the Gateway and bundled plugins for model runs, message flow, sessions, queues, and exec.
  • diagnostics-otel plugin subscribes to those events and exports them as OpenTelemetry metrics, traces, and logs over OTLP/HTTP.
  • Exporters only attach when both the diagnostics surface and the plugin are enabled, so the in-process cost stays near zero by default.

Quick start

{
  plugins: {
    allow: ["diagnostics-otel"],
    entries: {
      "diagnostics-otel": { enabled: true },
    },
  },
  diagnostics: {
    enabled: true,
    otel: {
      enabled: true,
      endpoint: "http://otel-collector:4318",
      protocol: "http/protobuf",
      serviceName: "openclaw-gateway",
      traces: true,
      metrics: true,
      logs: true,
      sampleRate: 0.2,
      flushIntervalMs: 60000,
    },
  },
}
You can also enable the plugin from the CLI:
openclaw plugins enable diagnostics-otel
protocol currently supports http/protobuf only. grpc is ignored.

Signals exported

SignalWhat goes in it
MetricsCounters and histograms for token usage, cost, run duration, message flow, queue lanes, session state, exec, and memory pressure.
TracesSpans for model usage, model calls, tool execution, exec, webhook/message processing, context assembly, and tool loops.
LogsStructured logging.file records exported over OTLP when diagnostics.otel.logs is enabled.
Toggle traces, metrics, and logs independently. All three default to on when diagnostics.otel.enabled is true.

Configuration reference

{
  diagnostics: {
    enabled: true,
    otel: {
      enabled: true,
      endpoint: "http://otel-collector:4318",
      tracesEndpoint: "http://otel-collector:4318/v1/traces",
      metricsEndpoint: "http://otel-collector:4318/v1/metrics",
      logsEndpoint: "http://otel-collector:4318/v1/logs",
      protocol: "http/protobuf", // grpc is ignored
      serviceName: "openclaw-gateway",
      headers: { "x-collector-token": "..." },
      traces: true,
      metrics: true,
      logs: true,
      sampleRate: 0.2, // root-span sampler, 0.0..1.0
      flushIntervalMs: 60000, // metric export interval (min 1000ms)
      captureContent: {
        enabled: false,
        inputMessages: false,
        outputMessages: false,
        toolInputs: false,
        toolOutputs: false,
        systemPrompt: false,
      },
    },
  },
}

Environment variables

VariablePurpose
OTEL_EXPORTER_OTLP_ENDPOINTOverride diagnostics.otel.endpoint. If the value already contains /v1/traces, /v1/metrics, or /v1/logs, it is used as-is.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / OTEL_EXPORTER_OTLP_METRICS_ENDPOINT / OTEL_EXPORTER_OTLP_LOGS_ENDPOINTSignal-specific endpoint overrides used when the matching diagnostics.otel.*Endpoint config key is unset. Signal-specific config wins over signal-specific env, which wins over the shared endpoint.
OTEL_SERVICE_NAMEOverride diagnostics.otel.serviceName.
OTEL_EXPORTER_OTLP_PROTOCOLOverride the wire protocol (only http/protobuf is honored today).
OTEL_SEMCONV_STABILITY_OPT_INSet to gen_ai_latest_experimental to emit the latest experimental GenAI span attribute (gen_ai.provider.name) instead of the legacy gen_ai.system. GenAI metrics always use bounded, low-cardinality semantic attributes regardless.
OPENCLAW_OTEL_PRELOADEDSet to 1 when another preload or host process already registered the global OpenTelemetry SDK. The plugin then skips its own NodeSDK lifecycle but still wires diagnostic listeners and honors traces/metrics/logs.

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) and never include prompt text, response text, tool inputs, tool outputs, or session keys. Set diagnostics.otel.captureContent.* to true only when your collector and retention policy are approved for prompt, response, tool, or system-prompt text. Each subkey is opt-in independently:
  • inputMessages — user prompt content.
  • outputMessages — model response content.
  • toolInputs — tool argument payloads.
  • toolOutputs — tool result payloads.
  • systemPrompt — assembled system/developer prompt.
When any subkey is enabled, model and tool spans get bounded, redacted openclaw.content.* attributes for that class only.

Sampling and flushing

  • Traces: diagnostics.otel.sampleRate (root-span only, 0.0 drops all, 1.0 keeps all).
  • Metrics: diagnostics.otel.flushIntervalMs (minimum 1000).
  • Logs: OTLP logs respect logging.level (file log level). Console redaction does not apply to OTLP logs. High-volume installs should prefer OTLP collector sampling/filtering over local sampling.

Exported metrics

Model usage

  • openclaw.tokens (counter, attrs: openclaw.token, openclaw.channel, openclaw.provider, openclaw.model)
  • 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, attrs: gen_ai.provider.name, gen_ai.operation.name, gen_ai.request.model, optional error.type)

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.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)

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.lane or openclaw.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)
  • openclaw.session.stuck_age_ms (histogram, attrs: openclaw.state)
  • openclaw.run.attempt (counter, attrs: openclaw.attempt)

Exec

  • openclaw.exec.duration_ms (histogram, attrs: openclaw.exec.target, openclaw.exec.mode, openclaw.outcome, openclaw.failureKind)

Diagnostics internals (memory and tool loop)

  • openclaw.memory.heap_used_bytes (histogram, attrs: openclaw.memory.kind)
  • openclaw.memory.rss_bytes (histogram)
  • openclaw.memory.pressure (counter, attrs: openclaw.memory.level)
  • openclaw.tool.loop.iterations (counter, attrs: openclaw.toolName, openclaw.outcome)
  • openclaw.tool.loop.duration_ms (histogram, attrs: openclaw.toolName, openclaw.outcome)

Exported spans

  • openclaw.model.usage
    • openclaw.channel, openclaw.provider, openclaw.model
    • openclaw.tokens.* (input/output/cache_read/cache_write/total)
    • gen_ai.system by default, or gen_ai.provider.name when the latest GenAI semantic conventions are opted in
    • gen_ai.request.model, gen_ai.operation.name, gen_ai.usage.*
  • openclaw.run
    • openclaw.outcome, openclaw.channel, openclaw.provider, openclaw.model, openclaw.errorCategory
  • openclaw.model.call
    • gen_ai.system by default, or gen_ai.provider.name when the latest GenAI semantic conventions are opted in
    • gen_ai.request.model, gen_ai.operation.name, openclaw.provider, openclaw.model, openclaw.api, openclaw.transport
    • openclaw.provider.request_id_hash (bounded SHA-based hash of the upstream provider request id; raw ids are not exported)
  • openclaw.tool.execution
    • gen_ai.tool.name, openclaw.toolName, openclaw.errorCategory, openclaw.tool.params.*
  • openclaw.exec
    • openclaw.exec.target, openclaw.exec.mode, openclaw.outcome, openclaw.failureKind, openclaw.exec.command_length, openclaw.exec.exit_code, openclaw.exec.timed_out
  • openclaw.webhook.processed
    • openclaw.channel, openclaw.webhook, openclaw.chatId
  • openclaw.webhook.error
    • openclaw.channel, openclaw.webhook, openclaw.chatId, openclaw.error
  • openclaw.message.processed
    • openclaw.channel, openclaw.outcome, openclaw.chatId, openclaw.messageId, openclaw.reason
  • openclaw.message.delivery
    • openclaw.channel, openclaw.delivery.kind, openclaw.outcome, openclaw.errorCategory, openclaw.delivery.result_count
  • openclaw.session.stuck
    • openclaw.state, openclaw.ageMs, openclaw.queueDepth
  • openclaw.context.assembled
    • openclaw.prompt.size, openclaw.history.size, openclaw.context.tokens, openclaw.errorCategory (no prompt, history, response, or session-key content)
  • openclaw.tool.loop
    • openclaw.toolName, openclaw.outcome, openclaw.iterations, openclaw.errorCategory (no loop messages, params, or tool output)
  • openclaw.memory.pressure
    • openclaw.memory.level, openclaw.memory.heap_used_bytes, openclaw.memory.rss_bytes
When content capture is explicitly enabled, model and tool spans can also include bounded, redacted openclaw.content.* attributes for the specific content classes you opted into.

Diagnostic event catalog

The events below back the metrics and spans above. Plugins can also subscribe to them directly without OTLP export. Model usage
  • model.usage — tokens, cost, duration, context, provider/model/channel, session ids. usage is provider/turn accounting for cost and telemetry; context.used is the current prompt/context snapshot and can be lower than provider usage.total when cached input or tool-loop calls are involved.
Message flow
  • webhook.received / webhook.processed / webhook.error
  • message.queued / message.processed
  • message.delivery.started / message.delivery.completed / message.delivery.error
Queue and session
  • queue.lane.enqueue / queue.lane.dequeue
  • session.state / session.stuck
  • run.attempt
  • diagnostic.heartbeat (aggregate counters: webhooks/queue/session)
Exec
  • exec.process.completed — terminal outcome, duration, target, mode, exit code, and failure kind. Command text and working directories are not included.

Without an exporter

You can keep diagnostics events available to plugins or custom sinks without running diagnostics-otel:
{
  diagnostics: { enabled: true },
}
For targeted debug output without raising logging.level, use diagnostics flags. Flags are case-insensitive and support wildcards (e.g. telegram.* or *):
{
  diagnostics: { flags: ["telegram.http"] },
}
Or as a one-off env override:
OPENCLAW_DIAGNOSTICS=telegram.http,telegram.payload openclaw gateway
Flag output goes to the standard log file (logging.file) and is still redacted by logging.redactSensitive. Full guide: Diagnostics flags.

Disable

{
  diagnostics: { otel: { enabled: false } },
}
You can also leave diagnostics-otel out of plugins.allow, or run openclaw plugins disable diagnostics-otel.