Get OpenTelemetry observability for your OpenClaw AI agents.
- OpenClaw v2026.2.0 or later
- An OTLP endpoint (local collector, Dynatrace, Grafana, etc.)
The fastest way to get observability. No installation needed — just configure.
Add to your ~/.openclaw/openclaw.json:
{
"diagnostics": {
"enabled": true,
"otel": {
"enabled": true,
"endpoint": "http://localhost:4318",
"serviceName": "openclaw-gateway",
"traces": true,
"metrics": true,
"logs": true
}
}
}openclaw gateway restartSend a message to your agent and check your backend for:
- Metrics (emitted by
diagnostics-otel):openclaw.tokens,openclaw.cost.usd,openclaw.run.duration_ms - Traces:
openclaw.model.usage,openclaw.message.processed - Logs: Gateway logs with severity and code location
If you also installed Option 2 (this repo's custom plugin), the metrics from the custom plugin are different:
openclaw.llm.tokens.{total,prompt,completion},openclaw.llm.cost.usd, and the OTel stablegen_ai.*set.
For connected traces and per-tool-call visibility, add the custom plugin.
cd ~/.openclaw/extensions
git clone https://github.com/henrikrexed/openclaw-observability-plugin.git otel-observabilitycd otel-observability
npm installAdd to your ~/.openclaw/openclaw.json:
{
"plugins": {
"load": {
"paths": ["~/.openclaw/extensions/otel-observability"]
},
"entries": {
"otel-observability": {
"enabled": true,
"config": {
"endpoint": "http://localhost:4318",
"protocol": "http",
"serviceName": "openclaw-gateway"
},
"hooks": {
"allowConversationAccess": true
}
}
}
}
}Required for OpenClaw ≥ 2026.4.23. Path-loaded (non-bundled) plugins must explicitly opt in to conversation typed hooks. Without
hooks.allowConversationAccess: true, OpenClaw silently drops the registrations forbefore_model_resolve,llm_input,llm_output,before_agent_finalize,agent_end,before_agent_reply, andbefore_agent_run. The plugin still prints its[otel] Registered ... hook (via api.on)banners but the handlers never fire, so noopenclaw.request/openclaw.agent.turnspans reach your backend. See github issue #20 and the troubleshooting section.
Note: Custom plugin settings live under
plugins.entries.otel-observability.config. OpenClaw's built-in diagnostics exporter uses a separatediagnostics.otelblock; do not mix the two config surfaces.
rm -rf /tmp/jiti
systemctl --user restart openclaw-gateway
# Or: openclaw gateway restartFirst, confirm the plugin is wired into the gateway. Tail the gateway log during startup:
journalctl --user -u openclaw-gateway -f | grep -E '\[otel\]'You should see these lines during register() (before the gateway accepts traffic):
[otel] Registered message_received hook (via api.on)
[otel] Registered before_model_resolve hook (via api.on)
[otel] Registered before_prompt_build hook (via api.on)
[otel] Registered tool_result_persist hook (via api.on)
[otel] Registered agent_end hook (via api.on)
[otel] Registered command event hooks (via api.registerHook)
[otel] Registered gateway:startup hook (via api.registerHook)
Then, during start() (after the gateway is ready):
[otel] Starting OpenTelemetry observability...
[otel] ✅ Observability pipeline active
[otel] Traces=true Metrics=true Logs=true
[otel] Endpoint=http://localhost:4318 (http)
Now send a message that triggers tool calls (e.g., "read my AGENTS.md file"). On each inbound message, debug-level logs confirm hooks are firing:
[otel] Root span started for session=<sessionKey>
[otel] Agent turn span started: agent=<agentId>, session=<sessionKey>
In your backend you should see a connected trace:
openclaw.request
└── openclaw.agent.turn
└── tool.Read
For complete observability, enable both:
{
"diagnostics": {
"enabled": true,
"otel": {
"enabled": true,
"endpoint": "http://localhost:4318",
"serviceName": "openclaw-gateway",
"traces": true,
"metrics": true,
"logs": true
}
},
"plugins": {
"load": {
"paths": ["~/.openclaw/extensions/otel-observability"]
},
"entries": {
"otel-observability": {
"enabled": true,
"hooks": {
"allowConversationAccess": true
},
"config": {
"endpoint": "http://localhost:4318",
"serviceName": "openclaw-gateway"
}
}
}
}
}
hooks.allowConversationAccess: trueis required on OpenClaw ≥ 2026.4.23. See the install step above.
What you get:
| Source | Data |
|---|---|
| Official | Gateway health, queue metrics, log forwarding, session states |
| Custom | Connected request traces, tool call spans, agent turn details |
-
Install:
# Ubuntu/Debian wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.144.0/otelcol-contrib_0.144.0_linux_amd64.deb sudo dpkg -i otelcol-contrib_0.144.0_linux_amd64.deb -
Configure (
/etc/otelcol-contrib/config.yaml):receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 processors: batch: exporters: debug: verbosity: detailed # Add your backend exporter service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [debug] metrics: receivers: [otlp] processors: [batch] exporters: [debug] logs: receivers: [otlp] processors: [batch] exporters: [debug]
-
Start:
sudo systemctl start otelcol-contrib
No collector needed:
{
"diagnostics": {
"enabled": true,
"otel": {
"enabled": true,
"endpoint": "https://{environment-id}.live.dynatrace.com/api/v2/otlp",
"headers": {
"Authorization": "Api-Token {your-token}"
},
"serviceName": "openclaw-gateway",
"traces": true,
"metrics": true,
"logs": true
}
}
}Required scopes: metrics.ingest, logs.ingest, openTelemetryTrace.ingest
{
"diagnostics": {
"enabled": true,
"otel": {
"enabled": true,
"endpoint": "https://otlp-gateway-{region}.grafana.net/otlp",
"headers": {
"Authorization": "Basic {base64(instanceId:apiKey)}"
},
"serviceName": "openclaw-gateway",
"traces": true,
"metrics": true
}
}
}-
Check Gateway logs:
journalctl --user -u openclaw-gateway -f
-
Verify endpoint is reachable:
curl -v http://localhost:4318/v1/traces
-
Check diagnostics config:
cat ~/.openclaw/openclaw.json | jq '.diagnostics'
-
Check plugin discovery:
openclaw plugins list
-
Clear jiti cache:
rm -rf /tmp/jiti
-
Check for TypeScript errors in Gateway logs
The custom plugin requires messages to flow through the normal pipeline. Heartbeats and some internal events may not have full trace context.
Symptom: Gateway logs [otel] ✅ Observability pipeline active, but no openclaw.request or tool.* spans reach your backend when you send messages.
Cause (pre-PR #6): Earlier builds registered typed hooks from inside the async service.start() phase. OpenClaw snapshots typed hooks at plugin registration time — ~30 s before start() runs — so the gateway never saw the 15 hook listeners. Tracked as ISI-515.
Fix: Upgrade to a build that includes PR #6. Hooks are now registered synchronously in register() and resolve the telemetry runtime through a lazy getter.
How to confirm hooks are live:
-
During gateway startup, look for all six registration lines from
register():[otel] Registered message_received hook (via api.on) [otel] Registered before_agent_start hook (via api.on) [otel] Registered tool_result_persist hook (via api.on) [otel] Registered agent_end hook (via api.on) [otel] Registered command event hooks (via api.registerHook) [otel] Registered gateway:startup hook (via api.registerHook)If these are missing, the plugin is not loaded — recheck
plugins.load.pathsand clear/tmp/jiti. -
Send a real message through the pipeline and enable debug logging. You should see:
[otel] Root span started for session=<sessionKey> [otel] Agent turn span started: agent=<agentId>, session=<sessionKey>If registration lines are present but these are missing, the gateway is not firing hooks for that event path (e.g., heartbeats skip
message_received). -
Verify the OTLP endpoint is reachable:
curl -v http://localhost:4318/v1/traces
See Architecture → Plugin Lifecycle for why the register() vs start() split matters.
- Configuration Reference — All options
- Architecture — How it works
- Limitations — Known constraints
- Backend Guides — Specific backend setup