openclaw-logfire-observability 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rita-aga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # logfire-observability
2
+
3
+ Full OpenClaw observability in [Pydantic Logfire](https://logfire.pydantic.dev). Get agent traces, tool calls, metrics, and logs — all in one dashboard.
4
+
5
+ This setup combines two plugins:
6
+
7
+ | Plugin | What it sends to Logfire | Source |
8
+ |--------|-------------------------|--------|
9
+ | **logfire-observability** (this plugin) | Agent→tool trace hierarchy with params, results, and parent-child nesting | Custom, ships here |
10
+ | **diagnostics-otel** (built-in) | Metrics (tokens, cost, duration), logs, webhook/queue/session telemetry | Ships with OpenClaw |
11
+
12
+ Both are configured to export to Logfire via OTLP. Together they give you full coverage.
13
+
14
+ ## Quick start
15
+
16
+ ### 1. Get a Logfire token
17
+
18
+ 1. Go to [logfire.pydantic.dev](https://logfire.pydantic.dev)
19
+ 2. Create a project (or use an existing one)
20
+ 3. Go to Settings > Write Tokens > Create Token
21
+ 4. Copy the `pylf_v1_us_...` token
22
+
23
+ ### 2. Install this plugin
24
+
25
+ ```bash
26
+ # From the repo root
27
+ cd openclaw-plugins/logfire-observability
28
+ npm install
29
+
30
+ # Link into your OpenClaw instance
31
+ openclaw plugins install -l /path/to/openclaw-plugins/logfire-observability
32
+ ```
33
+
34
+ Or copy the folder to `~/.openclaw/extensions/logfire-observability/` and run `npm install` there.
35
+
36
+ ### 3. Configure both plugins
37
+
38
+ Add the following to your `openclaw.json` (or `~/.clawdbot/openclaw.json`).
39
+
40
+ Replace `YOUR_TOKEN_HERE` with your Logfire write token in **both** places:
41
+
42
+ ```json
43
+ {
44
+ "diagnostics": {
45
+ "enabled": true,
46
+ "otel": {
47
+ "enabled": true,
48
+ "endpoint": "https://logfire-us.pydantic.dev",
49
+ "headers": {
50
+ "Authorization": "Bearer pylf_v1_us_YOUR_TOKEN_HERE"
51
+ },
52
+ "serviceName": "openclaw",
53
+ "traces": true,
54
+ "metrics": true,
55
+ "logs": true
56
+ }
57
+ },
58
+ "plugins": {
59
+ "entries": {
60
+ "logfire-observability": {
61
+ "enabled": true,
62
+ "config": {
63
+ "logfireToken": "pylf_v1_us_YOUR_TOKEN_HERE"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ Then restart OpenClaw (`sudo systemctl restart clawdbot` or `openclaw restart`).
72
+
73
+ > **EU region?** Change the endpoint to `https://logfire-eu.pydantic.dev` and set `logfireEndpoint` to `https://logfire-eu.pydantic.dev/v1/traces`.
74
+
75
+ ## What you get in Logfire
76
+
77
+ ### From logfire-observability (this plugin)
78
+
79
+ Detailed agent execution traces with parent-child nesting:
80
+
81
+ ```
82
+ User message
83
+ └─ message.received span
84
+ └─ agent.run span (parent)
85
+ ├─ tool.web_search span
86
+ ├─ tool.read_file span
87
+ └─ tool.send_message span
88
+ ```
89
+
90
+ | Span | Fires when | Key attributes |
91
+ |------|-----------|----------------|
92
+ | `message.received` | Inbound user message | channel, from, content |
93
+ | `agent.run` | LLM call start → end | agent, provider, prompt preview, response, duration, message count |
94
+ | `tool.<name>` | Each tool execution | tool name, params, result, call ID |
95
+
96
+ All spans include `openclaw.sessionKey` and `openclaw.agent` for filtering.
97
+
98
+ ### From diagnostics-otel (built-in)
99
+
100
+ Operational metrics, logs, and diagnostic traces:
101
+
102
+ **Metrics**
103
+ | Metric | Type | What it tracks |
104
+ |--------|------|----------------|
105
+ | `openclaw.tokens` | counter | Token usage by type (input, output, cache, prompt, total) |
106
+ | `openclaw.cost.usd` | counter | Estimated cost per run |
107
+ | `openclaw.run.duration_ms` | histogram | Agent run duration |
108
+ | `openclaw.context.tokens` | histogram | Context window limit vs used |
109
+ | `openclaw.webhook.received` | counter | Inbound webhooks |
110
+ | `openclaw.webhook.duration_ms` | histogram | Webhook processing time |
111
+ | `openclaw.message.queued` / `.processed` | counters | Message throughput |
112
+ | `openclaw.queue.depth` / `.wait_ms` | histograms | Queue health |
113
+ | `openclaw.session.state` / `.stuck` | counters | Session lifecycle |
114
+ | `openclaw.run.attempt` | counter | Run retry tracking |
115
+
116
+ **Logs** — All OpenClaw logs forwarded to Logfire via OTLP (when `logs: true`).
117
+
118
+ **Traces** — `model.usage`, `webhook.processed`, `webhook.error`, `message.processed`, `session.stuck` spans.
119
+
120
+ ## Config reference
121
+
122
+ ### logfire-observability (plugin config)
123
+
124
+ | Option | Type | Default | Description |
125
+ |--------|------|---------|-------------|
126
+ | `logfireToken` | string | **(required)** | Your Logfire project write token |
127
+ | `logfireEndpoint` | string | `https://logfire-us.pydantic.dev/v1/traces` | OTLP trace endpoint |
128
+ | `serviceName` | string | `openclaw` | Service name shown in Logfire |
129
+ | `captureContent` | boolean | `true` | Include message text, LLM responses, tool results |
130
+ | `captureToolParams` | boolean | `true` | Include tool call parameters |
131
+ | `maxAttributeLength` | number | `4096` | Truncate attributes beyond this length |
132
+
133
+ ### diagnostics-otel (top-level diagnostics config)
134
+
135
+ | Option | Type | Default | Description |
136
+ |--------|------|---------|-------------|
137
+ | `diagnostics.enabled` | boolean | `false` | Enable diagnostics |
138
+ | `diagnostics.otel.enabled` | boolean | `false` | Enable OTLP export |
139
+ | `diagnostics.otel.endpoint` | string | — | OTLP endpoint base URL |
140
+ | `diagnostics.otel.headers` | object | — | Custom headers (use for Logfire auth) |
141
+ | `diagnostics.otel.serviceName` | string | `openclaw` | Service name |
142
+ | `diagnostics.otel.traces` | boolean | `true` | Export traces |
143
+ | `diagnostics.otel.metrics` | boolean | `true` | Export metrics |
144
+ | `diagnostics.otel.logs` | boolean | `false` | Export logs |
145
+ | `diagnostics.otel.sampleRate` | number | `1.0` | Trace sample rate (0.0–1.0) |
146
+
147
+ ## Useful Logfire queries
148
+
149
+ ```sql
150
+ -- Failed agent runs (from logfire-observability)
151
+ SELECT * FROM spans WHERE span_name = 'agent.run' AND attributes->>'openclaw.success' = 'false'
152
+
153
+ -- Slowest tool calls (from logfire-observability)
154
+ SELECT span_name, duration FROM spans WHERE span_name LIKE 'tool.%' ORDER BY duration DESC LIMIT 20
155
+
156
+ -- Token usage by model (from diagnostics-otel)
157
+ SELECT attributes->>'openclaw.model', sum(value) FROM metrics WHERE name = 'openclaw.tokens' GROUP BY 1
158
+
159
+ -- Cost per channel (from diagnostics-otel)
160
+ SELECT attributes->>'openclaw.channel', sum(value) FROM metrics WHERE name = 'openclaw.cost.usd' GROUP BY 1
161
+
162
+ -- Messages by channel (from logfire-observability)
163
+ SELECT attributes->>'openclaw.channel', count(*) FROM spans WHERE span_name = 'message.received' GROUP BY 1
164
+ ```
165
+
166
+ ## Architecture
167
+
168
+ ```
169
+ ┌──────────────────────────────────┐
170
+ │ Logfire │
171
+ │ (traces, metrics, logs) │
172
+ └──────────┬───────────────────────┘
173
+ │ OTLP/HTTP
174
+ ┌──────────┴───────────────────────┐
175
+ │ │
176
+ ┌───────────┴──────────┐ ┌──────────────┴──────────┐
177
+ │ logfire-observability │ │ diagnostics-otel │
178
+ │ (this plugin) │ │ (built-in) │
179
+ ├──────────────────────┤ ├─────────────────────────┤
180
+ │ agent.run traces │ │ metrics (tokens, cost) │
181
+ │ tool.* child spans │ │ diagnostic traces │
182
+ │ message.received │ │ log forwarding │
183
+ │ │ │ webhook/queue/session │
184
+ └───────────┬──────────┘ └──────────────┬──────────┘
185
+ │ api.on() hooks │ onDiagnosticEvent()
186
+ └───────────┬───────────────────────┘
187
+
188
+ ┌───────┴────────┐
189
+ │ OpenClaw │
190
+ └────────────────┘
191
+ ```
192
+
193
+ The two plugins use different event systems (`api.on()` vs `onDiagnosticEvent()`) and different OTel setups (self-contained provider vs NodeSDK). They don't conflict — `logfire-observability` avoids global OTel registration, sidestepping the module isolation bug where jiti's per-plugin scoping prevents shared TracerProviders.
194
+
195
+ ## Using only this plugin
196
+
197
+ If you don't need metrics/logs and just want agent traces, you can use `logfire-observability` alone — no need to enable `diagnostics-otel`. The trace hierarchy (agent.run → tool.*) works independently.
package/index.ts ADDED
@@ -0,0 +1,263 @@
1
+ import { SpanStatusCode, context as otelContext, trace, type Span, type Context, type Tracer } from "@opentelemetry/api";
2
+ import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
3
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
4
+ import { Resource } from "@opentelemetry/resources";
5
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
6
+
7
+ const TRACER_NAME = "openclaw.logfire-observability";
8
+ const TRACER_VERSION = "1.0.0";
9
+
10
+ interface ActiveRun {
11
+ span: Span;
12
+ ctx: Context; // OTel context with agent.run span — used to parent tool spans
13
+ toolSpans: Map<string, Span>; // keyed by toolCallId for parallel-safe lookup
14
+ toolStack: Span[]; // fallback LIFO for events missing toolCallId
15
+ }
16
+
17
+ function truncate(str: string | undefined | null, maxLen: number): string {
18
+ if (!str) return "";
19
+ return str.length > maxLen ? str.slice(0, maxLen) + "...[truncated]" : str;
20
+ }
21
+
22
+ function safeStringify(value: unknown, maxLen: number): string {
23
+ try {
24
+ const str = typeof value === "string" ? value : JSON.stringify(value);
25
+ return truncate(str, maxLen);
26
+ } catch {
27
+ return "[unstringifiable]";
28
+ }
29
+ }
30
+
31
+ function extractTextContent(content: unknown, maxLen: number): string {
32
+ if (typeof content === "string") return truncate(content, maxLen);
33
+ if (Array.isArray(content)) {
34
+ const parts = content
35
+ .filter((b: any) => b.type === "text")
36
+ .map((b: any) => b.text ?? "");
37
+ return truncate(parts.join("\n"), maxLen);
38
+ }
39
+ return safeStringify(content, maxLen);
40
+ }
41
+
42
+ export default {
43
+ id: "logfire-observability",
44
+ name: "Logfire Observability",
45
+ version: TRACER_VERSION,
46
+
47
+ register(api: any) {
48
+ const cfg = api.pluginConfig ?? {};
49
+
50
+ // ── Validate config ──────────────────────────────────────────
51
+ const logfireToken: string | undefined = cfg.logfireToken;
52
+ if (!logfireToken) {
53
+ api.logger?.error?.(
54
+ "logfire-observability: missing logfireToken in config — plugin disabled. " +
55
+ "Add your Logfire write token to openclaw.json under plugins.entries.logfire-observability.config.logfireToken"
56
+ );
57
+ return;
58
+ }
59
+
60
+ const endpoint: string = cfg.logfireEndpoint || "https://logfire-us.pydantic.dev/v1/traces";
61
+ const serviceName: string = cfg.serviceName || "openclaw";
62
+ const captureContent: boolean = cfg.captureContent !== false;
63
+ const captureToolParams: boolean = cfg.captureToolParams !== false;
64
+ const maxLen: number = typeof cfg.maxAttributeLength === "number" ? cfg.maxAttributeLength : 4096;
65
+
66
+ // ── Set up self-contained OTLP exporter ──────────────────────
67
+ const exporter = new OTLPTraceExporter({
68
+ url: endpoint,
69
+ headers: {
70
+ Authorization: `Bearer ${logfireToken}`,
71
+ },
72
+ });
73
+
74
+ const provider = new NodeTracerProvider({
75
+ resource: new Resource({
76
+ [ATTR_SERVICE_NAME]: serviceName,
77
+ }),
78
+ spanProcessors: [new BatchSpanProcessor(exporter)],
79
+ });
80
+
81
+ // Use our own tracer from our own provider — no global registration,
82
+ // no conflict with diagnostics-otel or any other plugin.
83
+ const tracer: Tracer = provider.getTracer(TRACER_NAME, TRACER_VERSION);
84
+
85
+ api.logger?.info?.(
86
+ `logfire-observability: initialized (endpoint=${endpoint} service=${serviceName} content=${captureContent} toolParams=${captureToolParams})`
87
+ );
88
+
89
+ // Active agent runs keyed by sessionKey
90
+ const runs = new Map<string, ActiveRun>();
91
+
92
+ // ── message_received ─────────────────────────────────────────
93
+ api.on("message_received", (event: any, ctx: any) => {
94
+ const span = tracer.startSpan("message.received", {
95
+ attributes: {
96
+ "openclaw.channel": ctx.channelId ?? "unknown",
97
+ "openclaw.from": event.from ?? "unknown",
98
+ "openclaw.account": ctx.accountId ?? "unknown",
99
+ ...(captureContent && event.content
100
+ ? { "openclaw.message.content": truncate(event.content, maxLen) }
101
+ : {}),
102
+ },
103
+ });
104
+ span.end();
105
+ });
106
+
107
+ // ── before_agent_start ───────────────────────────────────────
108
+ api.on("before_agent_start", (event: any, ctx: any) => {
109
+ const sessionKey = ctx.sessionKey ?? ctx.agentId ?? "unknown";
110
+
111
+ // End any stale run for this session
112
+ const stale = runs.get(sessionKey);
113
+ if (stale) {
114
+ endAllToolSpans(stale, true);
115
+ stale.span.setAttribute("openclaw.incomplete", true);
116
+ stale.span.end();
117
+ runs.delete(sessionKey);
118
+ }
119
+
120
+ const attrs: Record<string, string | number | boolean> = {
121
+ "openclaw.agent": ctx.agentId ?? "unknown",
122
+ "openclaw.sessionKey": sessionKey,
123
+ "openclaw.provider": ctx.messageProvider ?? "unknown",
124
+ "openclaw.prompt.length": event.prompt?.length ?? 0,
125
+ "openclaw.messages.count": event.messages?.length ?? 0,
126
+ };
127
+ if (captureContent && event.prompt) {
128
+ attrs["openclaw.prompt.preview"] = truncate(event.prompt, 1024);
129
+ }
130
+
131
+ const span = tracer.startSpan("agent.run", { attributes: attrs });
132
+ // Create an OTel context with this span as the active span.
133
+ // Tool spans started within this context become children of agent.run.
134
+ const spanCtx = trace.setSpan(otelContext.active(), span);
135
+ runs.set(sessionKey, { span, ctx: spanCtx, toolSpans: new Map(), toolStack: [] });
136
+ });
137
+
138
+ // ── before_tool_call ─────────────────────────────────────────
139
+ api.on("before_tool_call", (event: any, ctx: any) => {
140
+ const sessionKey = ctx.sessionKey ?? ctx.agentId ?? "unknown";
141
+ const parent = runs.get(sessionKey);
142
+
143
+ const attrs: Record<string, string | number | boolean> = {
144
+ "openclaw.tool.name": event.toolName,
145
+ "openclaw.sessionKey": sessionKey,
146
+ "openclaw.agent": ctx.agentId ?? "unknown",
147
+ };
148
+ if (captureToolParams && event.params) {
149
+ attrs["openclaw.tool.params"] = safeStringify(event.params, maxLen);
150
+ }
151
+
152
+ // Start tool span as a child of the agent.run span via parent context
153
+ const span = tracer.startSpan(
154
+ "tool." + event.toolName,
155
+ { attributes: attrs },
156
+ parent?.ctx, // passes parent context → makes this a child span
157
+ );
158
+
159
+ if (parent) {
160
+ // If we have a toolCallId, use the Map for parallel-safe lookup.
161
+ // Otherwise fall back to the LIFO stack.
162
+ if (event.toolCallId) {
163
+ parent.toolSpans.set(event.toolCallId, span);
164
+ } else {
165
+ parent.toolStack.push(span);
166
+ }
167
+ } else {
168
+ api.logger?.debug?.(`logfire-observability: tool.${event.toolName} fired without active agent run`);
169
+ span.end();
170
+ }
171
+ });
172
+
173
+ // ── tool_result_persist ──────────────────────────────────────
174
+ api.on("tool_result_persist", (event: any, ctx: any) => {
175
+ const sessionKey = ctx.sessionKey ?? ctx.agentId ?? "unknown";
176
+ const parent = runs.get(sessionKey);
177
+
178
+ // Look up the matching tool span: prefer Map by toolCallId, fall back to stack
179
+ let span: Span | undefined;
180
+ if (event.toolCallId && parent?.toolSpans.has(event.toolCallId)) {
181
+ span = parent.toolSpans.get(event.toolCallId);
182
+ parent.toolSpans.delete(event.toolCallId);
183
+ } else {
184
+ span = parent?.toolStack.pop();
185
+ }
186
+
187
+ if (span) {
188
+ if (event.toolName) span.setAttribute("openclaw.tool.name", event.toolName);
189
+ if (event.toolCallId) span.setAttribute("openclaw.tool.callId", event.toolCallId);
190
+ if (event.isSynthetic) span.setAttribute("openclaw.tool.synthetic", true);
191
+
192
+ if (captureContent && event.message) {
193
+ const msg = event.message as any;
194
+ if (msg.content) {
195
+ span.setAttribute("openclaw.tool.result", extractTextContent(msg.content, maxLen));
196
+ }
197
+ }
198
+ span.end();
199
+ }
200
+ });
201
+
202
+ // ── agent_end ────────────────────────────────────────────────
203
+ api.on("agent_end", (event: any, ctx: any) => {
204
+ const sessionKey = ctx.sessionKey ?? ctx.agentId ?? "unknown";
205
+ const active = runs.get(sessionKey);
206
+
207
+ // End any dangling tool spans
208
+ if (active) {
209
+ endAllToolSpans(active, false);
210
+ }
211
+
212
+ const span = active?.span;
213
+ if (span) {
214
+ span.setAttribute("openclaw.success", event.success ?? true);
215
+ if (event.durationMs != null) span.setAttribute("openclaw.durationMs", event.durationMs);
216
+ if (event.error) {
217
+ span.setStatus({ code: SpanStatusCode.ERROR, message: event.error });
218
+ span.setAttribute("openclaw.error", truncate(event.error, maxLen));
219
+ }
220
+ if (event.messages?.length) {
221
+ span.setAttribute("openclaw.messages.total", event.messages.length);
222
+
223
+ if (captureContent) {
224
+ for (let i = event.messages.length - 1; i >= 0; i--) {
225
+ const msg = (event.messages as any[])[i];
226
+ if (msg?.role === "assistant") {
227
+ span.setAttribute("openclaw.response", extractTextContent(msg.content, maxLen));
228
+ break;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ span.end();
234
+ runs.delete(sessionKey);
235
+ }
236
+ });
237
+
238
+ // ── Graceful shutdown ────────────────────────────────────────
239
+ api.on("shutdown", async () => {
240
+ api.logger?.info?.("logfire-observability: flushing and shutting down...");
241
+ await provider.forceFlush();
242
+ await provider.shutdown();
243
+ });
244
+
245
+ /** End all pending tool spans for a run (from both Map and stack). */
246
+ function endAllToolSpans(run: ActiveRun, markIncomplete: boolean) {
247
+ for (const ts of run.toolSpans.values()) {
248
+ if (markIncomplete) ts.setAttribute("openclaw.incomplete", true);
249
+ ts.end();
250
+ }
251
+ run.toolSpans.clear();
252
+ for (const ts of run.toolStack) {
253
+ if (markIncomplete) ts.setAttribute("openclaw.incomplete", true);
254
+ ts.end();
255
+ }
256
+ run.toolStack.length = 0;
257
+ }
258
+
259
+ api.logger?.info?.(
260
+ "logfire-observability: hooks registered (message_received, before_agent_start, before_tool_call, tool_result_persist, agent_end, shutdown)"
261
+ );
262
+ },
263
+ };
@@ -0,0 +1,63 @@
1
+ {
2
+ "id": "logfire-observability",
3
+ "name": "Logfire Observability",
4
+ "description": "Self-contained agent observability — traces messages, LLM calls, and tool executions to Logfire via OpenTelemetry. No dependency on diagnostics-otel.",
5
+ "version": "1.0.0",
6
+ "kind": "tool",
7
+ "uiHints": {
8
+ "logfireToken": {
9
+ "label": "Logfire Write Token",
10
+ "help": "Your Logfire project write token (pylf_v1_us_...)"
11
+ },
12
+ "serviceName": {
13
+ "label": "Service Name",
14
+ "help": "Service name shown in Logfire traces (default: openclaw)"
15
+ },
16
+ "captureContent": {
17
+ "label": "Capture Content",
18
+ "help": "Include message text, LLM responses, and tool results in traces"
19
+ },
20
+ "captureToolParams": {
21
+ "label": "Capture Tool Params",
22
+ "help": "Include tool call parameters in traces"
23
+ },
24
+ "maxAttributeLength": {
25
+ "label": "Max Attribute Length",
26
+ "help": "Truncate string attributes beyond this length (bytes)"
27
+ }
28
+ },
29
+ "configSchema": {
30
+ "type": "object",
31
+ "required": ["logfireToken"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "logfireToken": {
35
+ "type": "string",
36
+ "description": "Logfire write token (pylf_v1_us_...)"
37
+ },
38
+ "logfireEndpoint": {
39
+ "type": "string",
40
+ "default": "https://logfire-us.pydantic.dev/v1/traces",
41
+ "description": "Logfire OTLP endpoint (override for EU or self-hosted)"
42
+ },
43
+ "serviceName": {
44
+ "type": "string",
45
+ "default": "openclaw",
46
+ "description": "Service name for traces"
47
+ },
48
+ "captureContent": {
49
+ "type": "boolean",
50
+ "default": true
51
+ },
52
+ "captureToolParams": {
53
+ "type": "boolean",
54
+ "default": true
55
+ },
56
+ "maxAttributeLength": {
57
+ "type": "number",
58
+ "default": 4096,
59
+ "minimum": 256
60
+ }
61
+ }
62
+ }
63
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "openclaw-logfire-observability",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "OpenClaw plugin for full observability in Pydantic Logfire — traces agent runs, tool calls, and messages via OpenTelemetry",
6
+ "keywords": [
7
+ "openclaw",
8
+ "openclaw-plugin",
9
+ "logfire",
10
+ "pydantic",
11
+ "observability",
12
+ "opentelemetry",
13
+ "otel",
14
+ "tracing",
15
+ "agent",
16
+ "llm"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/rita-aga/openclaw-logfire-observability"
21
+ },
22
+ "license": "MIT",
23
+ "author": "rita-aga",
24
+ "files": [
25
+ "index.ts",
26
+ "openclaw.plugin.json",
27
+ "README.md"
28
+ ],
29
+ "openclaw": {
30
+ "extensions": [
31
+ "./index.ts"
32
+ ]
33
+ },
34
+ "dependencies": {
35
+ "@opentelemetry/api": "^1.9.0",
36
+ "@opentelemetry/sdk-trace-node": "^1.30.0",
37
+ "@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
38
+ "@opentelemetry/resources": "^1.30.0",
39
+ "@opentelemetry/semantic-conventions": "^1.28.0"
40
+ }
41
+ }