From f398f2d194ed9944dd1aef580c094fcfa1ecdfaa Mon Sep 17 00:00:00 2001 From: Zi Dong Date: Fri, 17 Apr 2026 18:00:20 -0700 Subject: [PATCH] fix: share one TelemetryService across repeated register() calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenClaw may call plugin.register(api) multiple times per plugin lifetime — e.g. once for CLI plugin metadata, once for the full gateway load. The previous code instantiated a fresh svc inside register(), so: - start(ctx) sets fileWriter on instance A - api.on("before_tool_call", ...) hooks fire on instance B, whose fileWriter is still null - every write() silently no-ops, and ~/.openclaw/logs/telemetry.jsonl is never created Hoist svc to module scope with a null-coalescing init so every register() call wires hooks to the same service that receives start()/stop(). Verified via a 20s bounded agent session: events land in telemetry.jsonl as expected. Also updates README to: - Fix the config file path (openclaw.json, not config.json) - Clarify that plugins.entries.telemetry.config MUST contain a nested `{"enabled": true}`; without the inner config object start() bails silently and no events are written. This was the most common misconfiguration we hit during CRUX-Windows setup. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 14 ++++++++++++-- index.ts | 23 +++++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b2acb32..2905dd4 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ cp -R ./openclaw-telemetry ~/.openclaw/extensions/telemetry Via Control UI: **Settings → Config → plugins.entries.telemetry** -Or edit `~/.openclaw/config.json`: +Or edit `~/.openclaw/openclaw.json` (note the path — some older docs say +`config.json`, but the actual file is `openclaw.json`): ```json { "plugins": { @@ -56,10 +57,19 @@ Or edit `~/.openclaw/config.json`: } ``` +**Important** — both the outer `enabled` (OpenClaw's "plugin is active" +flag) and the inner `config.enabled` (this plugin's "capture events" flag) +must be `true`. If you omit the nested `config` object entirely — e.g. you +have only `{ "enabled": true }` at `plugins.entries.telemetry` — the +plugin's `start()` silently bails and **no events are written**. This is +the most common misconfiguration; if `~/.openclaw/logs/telemetry.jsonl` +never appears after an agent turn, check that the nested `config.enabled` +is present. + ### 3. Restart Gateway ```bash -openclaw gateway +systemctl --user restart openclaw-gateway ``` Logs write to `~/.openclaw/logs/telemetry.jsonl` by default. diff --git a/index.ts b/index.ts index 80df86e..8f76c53 100644 --- a/index.ts +++ b/index.ts @@ -1,16 +1,23 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { createTelemetryService } from "./src/service.js"; +import { createTelemetryService, type TelemetryService } from "./src/service.js"; + +// Module-level singleton so re-registrations share the same service instance. +// OpenClaw may call register() multiple times per plugin lifetime (e.g., CLI +// metadata vs gateway full load); keeping svc at module scope ensures hooks +// reference the same service that receives start()/stop(). +let svc: TelemetryService | null = null; export default { id: "telemetry", name: "OpenClaw Telemetry", description: "Captures tool calls, LLM usage, and message events to JSONL", register(api: OpenClawPluginApi) { - const svc = createTelemetryService(); + if (!svc) svc = createTelemetryService(); api.registerService(svc); + const s = svc; api.on("before_tool_call", (evt, ctx) => { - svc.write({ + s.write({ type: "tool.start", toolName: evt.toolName, params: evt.params, @@ -20,7 +27,7 @@ export default { }); api.on("after_tool_call", (evt, ctx) => { - svc.write({ + s.write({ type: "tool.end", toolName: evt.toolName, durationMs: evt.durationMs, @@ -32,7 +39,7 @@ export default { }); api.on("message_received", (evt, ctx) => { - svc.write({ + s.write({ type: "message.in", channel: ctx.channelId, from: evt.from, @@ -41,7 +48,7 @@ export default { }); api.on("message_sent", (evt, ctx) => { - svc.write({ + s.write({ type: "message.out", channel: ctx.channelId, to: evt.to, @@ -51,7 +58,7 @@ export default { }); api.on("before_agent_start", (evt, ctx) => { - svc.write({ + s.write({ type: "agent.start", sessionKey: ctx.sessionKey, agentId: ctx.agentId, @@ -60,7 +67,7 @@ export default { }); api.on("agent_end", (evt, ctx) => { - svc.write({ + s.write({ type: "agent.end", sessionKey: ctx.sessionKey, agentId: ctx.agentId,