@openclaw/diagnostics-otel 2026.5.2 → 2026.5.3-beta.1

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/dist/index.js ADDED
@@ -0,0 +1,1460 @@
1
+ import { isValidDiagnosticSpanId, isValidDiagnosticTraceFlags, isValidDiagnosticTraceId, redactSensitiveText } from "./api.js";
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+ import { SpanStatusCode, TraceFlags, context, metrics, trace } from "@opentelemetry/api";
4
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
5
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
6
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
7
+ import { resourceFromAttributes } from "@opentelemetry/resources";
8
+ import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
9
+ import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
10
+ import { NodeSDK } from "@opentelemetry/sdk-node";
11
+ import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
12
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
13
+ //#region extensions/diagnostics-otel/src/service.ts
14
+ const DEFAULT_SERVICE_NAME = "openclaw";
15
+ const DROPPED_OTEL_ATTRIBUTE_KEYS = new Set([
16
+ "openclaw.callId",
17
+ "openclaw.parentSpanId",
18
+ "openclaw.runId",
19
+ "openclaw.sessionId",
20
+ "openclaw.sessionKey",
21
+ "openclaw.spanId",
22
+ "openclaw.toolCallId",
23
+ "openclaw.traceId"
24
+ ]);
25
+ const LOW_CARDINALITY_VALUE_RE = /^[A-Za-z0-9_.:-]{1,120}$/u;
26
+ const MAX_OTEL_CONTENT_ATTRIBUTE_CHARS = 4 * 1024;
27
+ const MAX_OTEL_CONTENT_ARRAY_ITEMS = 16;
28
+ const MAX_OTEL_LOG_BODY_CHARS = 4 * 1024;
29
+ const MAX_OTEL_LOG_ATTRIBUTE_COUNT = 64;
30
+ const MAX_OTEL_LOG_ATTRIBUTE_VALUE_CHARS = 4 * 1024;
31
+ const LOG_RECORD_EXPORT_FAILURE_REPORT_INTERVAL_MS = 6e4;
32
+ const OTEL_LOG_RAW_ATTRIBUTE_KEY_RE = /^[A-Za-z0-9_.:-]{1,64}$/u;
33
+ const OTEL_LOG_ATTRIBUTE_KEY_RE = /^[A-Za-z0-9_.:-]{1,96}$/u;
34
+ const BLOCKED_OTEL_LOG_ATTRIBUTE_KEYS = new Set([
35
+ "__proto__",
36
+ "prototype",
37
+ "constructor"
38
+ ]);
39
+ const PRELOADED_OTEL_SDK_ENV = "OPENCLAW_OTEL_PRELOADED";
40
+ const OTEL_EXPORTER_OTLP_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_ENDPOINT";
41
+ const OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT";
42
+ const OTEL_EXPORTER_OTLP_METRICS_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT";
43
+ const OTEL_EXPORTER_OTLP_LOGS_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT";
44
+ const OTEL_SEMCONV_STABILITY_OPT_IN_ENV = "OTEL_SEMCONV_STABILITY_OPT_IN";
45
+ const GEN_AI_LATEST_EXPERIMENTAL_OPT_IN = "gen_ai_latest_experimental";
46
+ const GEN_AI_TOKEN_USAGE_BUCKETS = [
47
+ 1,
48
+ 4,
49
+ 16,
50
+ 64,
51
+ 256,
52
+ 1024,
53
+ 4096,
54
+ 16384,
55
+ 65536,
56
+ 262144,
57
+ 1048576,
58
+ 4194304,
59
+ 16777216,
60
+ 67108864
61
+ ];
62
+ const GEN_AI_OPERATION_DURATION_BUCKETS = [
63
+ .01,
64
+ .02,
65
+ .04,
66
+ .08,
67
+ .16,
68
+ .32,
69
+ .64,
70
+ 1.28,
71
+ 2.56,
72
+ 5.12,
73
+ 10.24,
74
+ 20.48,
75
+ 40.96,
76
+ 81.92
77
+ ];
78
+ const NO_CONTENT_CAPTURE = {
79
+ inputMessages: false,
80
+ outputMessages: false,
81
+ toolInputs: false,
82
+ toolOutputs: false,
83
+ systemPrompt: false
84
+ };
85
+ function normalizeEndpoint(endpoint) {
86
+ const trimmed = endpoint?.trim();
87
+ return trimmed ? trimmed.replace(/\/+$/, "") : void 0;
88
+ }
89
+ function resolveOtelUrl(endpoint, path) {
90
+ if (!endpoint) return;
91
+ const endpointWithoutQueryOrFragment = endpoint.split(/[?#]/, 1)[0] ?? endpoint;
92
+ if (/\/v1\/(?:traces|metrics|logs)$/i.test(endpointWithoutQueryOrFragment)) return endpoint;
93
+ return `${endpoint}/${path}`;
94
+ }
95
+ function resolveSignalOtelUrl(params) {
96
+ return resolveOtelUrl(normalizeEndpoint(params.signalEndpoint ?? params.signalEnvEndpoint) ?? params.endpoint, params.path);
97
+ }
98
+ function resolveSampleRate(value) {
99
+ if (typeof value !== "number" || !Number.isFinite(value)) return;
100
+ if (value < 0 || value > 1) return;
101
+ return value;
102
+ }
103
+ function formatError(err) {
104
+ if (err instanceof Error) return err.stack ?? err.message;
105
+ if (typeof err === "string") return err;
106
+ try {
107
+ return JSON.stringify(err);
108
+ } catch {
109
+ return String(err);
110
+ }
111
+ }
112
+ function errorCategory(err) {
113
+ try {
114
+ if (err instanceof Error && typeof err.name === "string" && err.name.trim()) return lowCardinalityAttr(err.name, "Error");
115
+ return lowCardinalityAttr(typeof err, "unknown");
116
+ } catch {
117
+ return "unknown";
118
+ }
119
+ }
120
+ function redactOtelAttributes(attributes) {
121
+ const redactedAttributes = {};
122
+ for (const [key, value] of Object.entries(attributes)) {
123
+ if (DROPPED_OTEL_ATTRIBUTE_KEYS.has(key)) continue;
124
+ redactedAttributes[key] = typeof value === "string" ? redactSensitiveText(value) : value;
125
+ }
126
+ return redactedAttributes;
127
+ }
128
+ function lowCardinalityAttr(value, fallback = "unknown") {
129
+ if (!value) return fallback;
130
+ const redacted = redactSensitiveText(value.trim());
131
+ return LOW_CARDINALITY_VALUE_RE.test(redacted) ? redacted : fallback;
132
+ }
133
+ function hasOtelSemconvOptIn(value, optIn) {
134
+ return value?.split(",").map((part) => part.trim()).includes(optIn) ?? false;
135
+ }
136
+ function emitLatestGenAiSemconv() {
137
+ return hasOtelSemconvOptIn(process.env[OTEL_SEMCONV_STABILITY_OPT_IN_ENV], GEN_AI_LATEST_EXPERIMENTAL_OPT_IN);
138
+ }
139
+ function genAiOperationName(api) {
140
+ const normalized = api?.trim().toLowerCase();
141
+ if (!normalized) return "chat";
142
+ if (normalized === "completions" || normalized.endsWith("-completions")) return "text_completion";
143
+ if (normalized === "generate_content" || normalized.includes("generative-ai")) return "generate_content";
144
+ return "chat";
145
+ }
146
+ function positiveFiniteNumber(value) {
147
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
148
+ }
149
+ function assignPositiveNumberAttr(attrs, key, value) {
150
+ const normalized = positiveFiniteNumber(value);
151
+ if (normalized !== void 0) attrs[key] = normalized;
152
+ }
153
+ function assignModelCallSizeTimingAttrs(attrs, evt) {
154
+ assignPositiveNumberAttr(attrs, "openclaw.model_call.request_bytes", evt.requestPayloadBytes);
155
+ assignPositiveNumberAttr(attrs, "openclaw.model_call.response_bytes", evt.responseStreamBytes);
156
+ assignPositiveNumberAttr(attrs, "openclaw.model_call.time_to_first_byte_ms", evt.timeToFirstByteMs);
157
+ }
158
+ function assignGenAiSpanIdentityAttrs(attrs, input) {
159
+ if (emitLatestGenAiSemconv()) attrs["gen_ai.provider.name"] = lowCardinalityAttr(input.provider);
160
+ else attrs["gen_ai.system"] = lowCardinalityAttr(input.provider);
161
+ if (input.model) attrs["gen_ai.request.model"] = lowCardinalityAttr(input.model);
162
+ attrs["gen_ai.operation.name"] = genAiOperationName(input.api);
163
+ }
164
+ function assignGenAiModelCallAttrs(attrs, evt) {
165
+ assignGenAiSpanIdentityAttrs(attrs, evt);
166
+ }
167
+ function addUpstreamRequestIdSpanEvent(span, upstreamRequestIdHash) {
168
+ if (!upstreamRequestIdHash) return;
169
+ const boundedHash = lowCardinalityAttr(upstreamRequestIdHash);
170
+ if (boundedHash === "unknown") return;
171
+ span.addEvent?.("openclaw.provider.request", { "openclaw.upstreamRequestIdHash": boundedHash });
172
+ }
173
+ function clampOtelLogText(value, maxChars) {
174
+ return value.length > maxChars ? `${value.slice(0, maxChars)}...(truncated)` : value;
175
+ }
176
+ function normalizeOtelLogString(value, maxChars) {
177
+ return clampOtelLogText(redactSensitiveText(value), maxChars);
178
+ }
179
+ function resolveContentCapturePolicy(value) {
180
+ if (value === true) return {
181
+ inputMessages: true,
182
+ outputMessages: true,
183
+ toolInputs: true,
184
+ toolOutputs: true,
185
+ systemPrompt: false
186
+ };
187
+ if (!value || typeof value !== "object" || Array.isArray(value)) return NO_CONTENT_CAPTURE;
188
+ const config = value;
189
+ if (config.enabled !== true) return NO_CONTENT_CAPTURE;
190
+ return {
191
+ inputMessages: config.inputMessages === true,
192
+ outputMessages: config.outputMessages === true,
193
+ toolInputs: config.toolInputs === true,
194
+ toolOutputs: config.toolOutputs === true,
195
+ systemPrompt: config.systemPrompt === true
196
+ };
197
+ }
198
+ function hasPreloadedOtelSdk() {
199
+ return process.env[PRELOADED_OTEL_SDK_ENV] === "1";
200
+ }
201
+ function normalizeOtelContentValue(value) {
202
+ if (typeof value === "string") return normalizeOtelLogString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
203
+ if (Array.isArray(value)) {
204
+ const items = [];
205
+ for (const item of value.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) if (typeof item === "string") items.push(item);
206
+ if (items.length > 0) return normalizeOtelLogString(items.join("\n"), MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
207
+ }
208
+ }
209
+ function assignOtelContentAttribute(attributes, key, value) {
210
+ const normalized = normalizeOtelContentValue(value);
211
+ if (normalized) attributes[key] = normalized;
212
+ }
213
+ function assignOtelModelContentAttributes(attributes, event, policy) {
214
+ if (policy.inputMessages) assignOtelContentAttribute(attributes, "openclaw.content.input_messages", event.inputMessages);
215
+ if (policy.outputMessages) assignOtelContentAttribute(attributes, "openclaw.content.output_messages", event.outputMessages);
216
+ if (policy.systemPrompt) assignOtelContentAttribute(attributes, "openclaw.content.system_prompt", event.systemPrompt);
217
+ }
218
+ function assignOtelToolContentAttributes(attributes, event, policy) {
219
+ if (policy.toolInputs) assignOtelContentAttribute(attributes, "openclaw.content.tool_input", event.toolInput);
220
+ if (policy.toolOutputs) assignOtelContentAttribute(attributes, "openclaw.content.tool_output", event.toolOutput);
221
+ }
222
+ function assignOtelLogAttribute(attributes, key, value) {
223
+ if (Object.keys(attributes).length >= MAX_OTEL_LOG_ATTRIBUTE_COUNT) return;
224
+ if (BLOCKED_OTEL_LOG_ATTRIBUTE_KEYS.has(key)) return;
225
+ if (redactSensitiveText(key) !== key) return;
226
+ if (!OTEL_LOG_ATTRIBUTE_KEY_RE.test(key)) return;
227
+ if (typeof value === "string") {
228
+ attributes[key] = normalizeOtelLogString(value, MAX_OTEL_LOG_ATTRIBUTE_VALUE_CHARS);
229
+ return;
230
+ }
231
+ if (typeof value === "number" && Number.isFinite(value)) {
232
+ attributes[key] = value;
233
+ return;
234
+ }
235
+ if (typeof value === "boolean") attributes[key] = value;
236
+ }
237
+ function normalizeTraceContext(value) {
238
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
239
+ const candidate = value;
240
+ if (!isValidDiagnosticTraceId(candidate.traceId)) return;
241
+ if (candidate.spanId !== void 0 && !isValidDiagnosticSpanId(candidate.spanId)) return;
242
+ if (candidate.parentSpanId !== void 0 && !isValidDiagnosticSpanId(candidate.parentSpanId)) return;
243
+ if (candidate.traceFlags !== void 0 && !isValidDiagnosticTraceFlags(candidate.traceFlags)) return;
244
+ return {
245
+ traceId: candidate.traceId,
246
+ ...candidate.spanId ? { spanId: candidate.spanId } : {},
247
+ ...candidate.parentSpanId ? { parentSpanId: candidate.parentSpanId } : {},
248
+ ...candidate.traceFlags ? { traceFlags: candidate.traceFlags } : {}
249
+ };
250
+ }
251
+ function assignOtelLogEventAttributes(attributes, eventAttributes) {
252
+ if (!eventAttributes) return;
253
+ for (const rawKey in eventAttributes) {
254
+ if (Object.keys(attributes).length >= MAX_OTEL_LOG_ATTRIBUTE_COUNT) break;
255
+ if (!Object.hasOwn(eventAttributes, rawKey)) continue;
256
+ const key = rawKey.trim();
257
+ if (BLOCKED_OTEL_LOG_ATTRIBUTE_KEYS.has(key)) continue;
258
+ if (redactSensitiveText(key) !== key) continue;
259
+ if (!OTEL_LOG_RAW_ATTRIBUTE_KEY_RE.test(key)) continue;
260
+ assignOtelLogAttribute(attributes, `openclaw.${key}`, eventAttributes[rawKey]);
261
+ }
262
+ }
263
+ function traceFlagsToOtel(traceFlags) {
264
+ return (Number.parseInt(traceFlags ?? "00", 16) & TraceFlags.SAMPLED) !== 0 ? TraceFlags.SAMPLED : TraceFlags.NONE;
265
+ }
266
+ function contextForTraceContext(traceContext) {
267
+ const normalized = normalizeTraceContext(traceContext);
268
+ if (!normalized?.spanId) return;
269
+ return trace.setSpanContext(context.active(), {
270
+ traceId: normalized.traceId,
271
+ spanId: normalized.spanId,
272
+ traceFlags: traceFlagsToOtel(normalized.traceFlags),
273
+ isRemote: true
274
+ });
275
+ }
276
+ function contextForTrustedTraceContext(evt, metadata) {
277
+ return metadata.trusted ? contextForTraceContext(evt.trace) : void 0;
278
+ }
279
+ function addTraceAttributes(attributes, traceContext) {
280
+ const normalized = normalizeTraceContext(traceContext);
281
+ if (!normalized) return;
282
+ attributes["openclaw.traceId"] = normalized.traceId;
283
+ if (normalized.spanId) attributes["openclaw.spanId"] = normalized.spanId;
284
+ if (normalized.parentSpanId) attributes["openclaw.parentSpanId"] = normalized.parentSpanId;
285
+ if (normalized.traceFlags) attributes["openclaw.traceFlags"] = normalized.traceFlags;
286
+ }
287
+ function createDiagnosticsOtelService() {
288
+ let sdk = null;
289
+ let logProvider = null;
290
+ let unsubscribe = null;
291
+ let stopActiveTrustedSpans = null;
292
+ const stopStarted = async () => {
293
+ const currentUnsubscribe = unsubscribe;
294
+ const currentLogProvider = logProvider;
295
+ const currentSdk = sdk;
296
+ const currentStopActiveTrustedSpans = stopActiveTrustedSpans;
297
+ unsubscribe = null;
298
+ logProvider = null;
299
+ sdk = null;
300
+ stopActiveTrustedSpans = null;
301
+ currentUnsubscribe?.();
302
+ currentStopActiveTrustedSpans?.();
303
+ if (currentLogProvider) await currentLogProvider.shutdown().catch(() => void 0);
304
+ if (currentSdk) await currentSdk.shutdown().catch(() => void 0);
305
+ };
306
+ return {
307
+ id: "diagnostics-otel",
308
+ async start(ctx) {
309
+ await stopStarted();
310
+ const cfg = ctx.config.diagnostics;
311
+ const otel = cfg?.otel;
312
+ if (!cfg?.enabled || !otel?.enabled) return;
313
+ const emitExporterEvent = (event) => {
314
+ try {
315
+ ctx.internalDiagnostics?.emit({
316
+ type: "telemetry.exporter",
317
+ ...event
318
+ });
319
+ } catch {}
320
+ };
321
+ const emitForSignals = (signals, event) => {
322
+ for (const signal of signals) emitExporterEvent({
323
+ signal,
324
+ ...event
325
+ });
326
+ };
327
+ const tracesEnabled = otel.traces !== false;
328
+ const metricsEnabled = otel.metrics !== false;
329
+ const logsEnabled = otel.logs === true;
330
+ const enabledSignals = [
331
+ ...tracesEnabled ? ["traces"] : [],
332
+ ...metricsEnabled ? ["metrics"] : [],
333
+ ...logsEnabled ? ["logs"] : []
334
+ ];
335
+ if (enabledSignals.length === 0) return;
336
+ const protocol = otel.protocol ?? process.env.OTEL_EXPORTER_OTLP_PROTOCOL ?? "http/protobuf";
337
+ if (protocol !== "http/protobuf") {
338
+ emitForSignals(enabledSignals, {
339
+ exporter: "diagnostics-otel",
340
+ status: "failure",
341
+ reason: "unsupported_protocol"
342
+ });
343
+ ctx.logger.warn(`diagnostics-otel: unsupported protocol ${protocol}`);
344
+ return;
345
+ }
346
+ const endpoint = normalizeEndpoint(otel.endpoint ?? process.env[OTEL_EXPORTER_OTLP_ENDPOINT_ENV]);
347
+ const headers = otel.headers ?? void 0;
348
+ const serviceName = otel.serviceName?.trim() || process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE_NAME;
349
+ const sampleRate = resolveSampleRate(otel.sampleRate);
350
+ const contentCapturePolicy = resolveContentCapturePolicy(otel.captureContent);
351
+ const sdkPreloaded = hasPreloadedOtelSdk();
352
+ const resource = resourceFromAttributes({ [ATTR_SERVICE_NAME]: serviceName });
353
+ const logUrl = resolveSignalOtelUrl({
354
+ signalEndpoint: otel.logsEndpoint,
355
+ signalEnvEndpoint: process.env[OTEL_EXPORTER_OTLP_LOGS_ENDPOINT_ENV],
356
+ endpoint,
357
+ path: "v1/logs"
358
+ });
359
+ if (!sdkPreloaded && (tracesEnabled || metricsEnabled)) {
360
+ const traceUrl = resolveSignalOtelUrl({
361
+ signalEndpoint: otel.tracesEndpoint,
362
+ signalEnvEndpoint: process.env[OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_ENV],
363
+ endpoint,
364
+ path: "v1/traces"
365
+ });
366
+ const metricUrl = resolveSignalOtelUrl({
367
+ signalEndpoint: otel.metricsEndpoint,
368
+ signalEnvEndpoint: process.env[OTEL_EXPORTER_OTLP_METRICS_ENDPOINT_ENV],
369
+ endpoint,
370
+ path: "v1/metrics"
371
+ });
372
+ const traceExporter = tracesEnabled ? new OTLPTraceExporter({
373
+ ...traceUrl ? { url: traceUrl } : {},
374
+ ...headers ? { headers } : {}
375
+ }) : void 0;
376
+ const metricExporter = metricsEnabled ? new OTLPMetricExporter({
377
+ ...metricUrl ? { url: metricUrl } : {},
378
+ ...headers ? { headers } : {}
379
+ }) : void 0;
380
+ const metricReader = metricExporter ? new PeriodicExportingMetricReader({
381
+ exporter: metricExporter,
382
+ ...typeof otel.flushIntervalMs === "number" ? { exportIntervalMillis: Math.max(1e3, otel.flushIntervalMs) } : {}
383
+ }) : void 0;
384
+ sdk = new NodeSDK({
385
+ resource,
386
+ ...traceExporter ? { traceExporter } : {},
387
+ ...metricReader ? { metricReader } : {},
388
+ ...sampleRate !== void 0 ? { sampler: new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(sampleRate) }) } : {}
389
+ });
390
+ try {
391
+ sdk.start();
392
+ } catch (err) {
393
+ emitForSignals([...tracesEnabled ? ["traces"] : [], ...metricsEnabled ? ["metrics"] : []], {
394
+ exporter: "diagnostics-otel",
395
+ status: "failure",
396
+ reason: "start_failed",
397
+ errorCategory: errorCategory(err)
398
+ });
399
+ await stopStarted();
400
+ ctx.logger.error(`diagnostics-otel: failed to start SDK: ${formatError(err)}`);
401
+ throw err;
402
+ }
403
+ } else if (sdkPreloaded && (tracesEnabled || metricsEnabled)) ctx.logger.info("diagnostics-otel: using preloaded OpenTelemetry SDK");
404
+ const logSeverityMap = {
405
+ TRACE: 1,
406
+ DEBUG: 5,
407
+ INFO: 9,
408
+ WARN: 13,
409
+ ERROR: 17,
410
+ FATAL: 21
411
+ };
412
+ const meter = metrics.getMeter("openclaw");
413
+ const tracer = trace.getTracer("openclaw");
414
+ const activeTrustedSpans = /* @__PURE__ */ new Map();
415
+ const activeTrustedSpanAliases = /* @__PURE__ */ new Map();
416
+ const pendingTrustedRunFinalizers = /* @__PURE__ */ new Map();
417
+ stopActiveTrustedSpans = () => {
418
+ const stopAt = Date.now();
419
+ for (const handle of pendingTrustedRunFinalizers.values()) clearImmediate(handle);
420
+ pendingTrustedRunFinalizers.clear();
421
+ for (const span of new Set([...activeTrustedSpans.values(), ...activeTrustedSpanAliases.values()])) span.end(stopAt);
422
+ activeTrustedSpans.clear();
423
+ activeTrustedSpanAliases.clear();
424
+ };
425
+ const tokensCounter = meter.createCounter("openclaw.tokens", {
426
+ unit: "1",
427
+ description: "Token usage by type"
428
+ });
429
+ const genAiTokenUsageHistogram = meter.createHistogram("gen_ai.client.token.usage", {
430
+ unit: "{token}",
431
+ description: "Number of input and output tokens used by GenAI client operations",
432
+ advice: { explicitBucketBoundaries: GEN_AI_TOKEN_USAGE_BUCKETS }
433
+ });
434
+ const genAiOperationDurationHistogram = meter.createHistogram("gen_ai.client.operation.duration", {
435
+ unit: "s",
436
+ description: "GenAI client operation duration",
437
+ advice: { explicitBucketBoundaries: GEN_AI_OPERATION_DURATION_BUCKETS }
438
+ });
439
+ const costCounter = meter.createCounter("openclaw.cost.usd", {
440
+ unit: "1",
441
+ description: "Estimated model cost (USD)"
442
+ });
443
+ const durationHistogram = meter.createHistogram("openclaw.run.duration_ms", {
444
+ unit: "ms",
445
+ description: "Agent run duration"
446
+ });
447
+ const harnessDurationHistogram = meter.createHistogram("openclaw.harness.duration_ms", {
448
+ unit: "ms",
449
+ description: "Agent harness lifecycle duration"
450
+ });
451
+ const contextHistogram = meter.createHistogram("openclaw.context.tokens", {
452
+ unit: "1",
453
+ description: "Context window size and usage"
454
+ });
455
+ const webhookReceivedCounter = meter.createCounter("openclaw.webhook.received", {
456
+ unit: "1",
457
+ description: "Webhook requests received"
458
+ });
459
+ const webhookErrorCounter = meter.createCounter("openclaw.webhook.error", {
460
+ unit: "1",
461
+ description: "Webhook processing errors"
462
+ });
463
+ const webhookDurationHistogram = meter.createHistogram("openclaw.webhook.duration_ms", {
464
+ unit: "ms",
465
+ description: "Webhook processing duration"
466
+ });
467
+ const messageQueuedCounter = meter.createCounter("openclaw.message.queued", {
468
+ unit: "1",
469
+ description: "Messages queued for processing"
470
+ });
471
+ const messageProcessedCounter = meter.createCounter("openclaw.message.processed", {
472
+ unit: "1",
473
+ description: "Messages processed by outcome"
474
+ });
475
+ const messageDurationHistogram = meter.createHistogram("openclaw.message.duration_ms", {
476
+ unit: "ms",
477
+ description: "Message processing duration"
478
+ });
479
+ const messageDeliveryStartedCounter = meter.createCounter("openclaw.message.delivery.started", {
480
+ unit: "1",
481
+ description: "Outbound message delivery attempts started"
482
+ });
483
+ const messageDeliveryDurationHistogram = meter.createHistogram("openclaw.message.delivery.duration_ms", {
484
+ unit: "ms",
485
+ description: "Outbound message delivery duration"
486
+ });
487
+ const queueDepthHistogram = meter.createHistogram("openclaw.queue.depth", {
488
+ unit: "1",
489
+ description: "Queue depth on enqueue/dequeue"
490
+ });
491
+ const queueWaitHistogram = meter.createHistogram("openclaw.queue.wait_ms", {
492
+ unit: "ms",
493
+ description: "Queue wait time before execution"
494
+ });
495
+ const laneEnqueueCounter = meter.createCounter("openclaw.queue.lane.enqueue", {
496
+ unit: "1",
497
+ description: "Command queue lane enqueue events"
498
+ });
499
+ const laneDequeueCounter = meter.createCounter("openclaw.queue.lane.dequeue", {
500
+ unit: "1",
501
+ description: "Command queue lane dequeue events"
502
+ });
503
+ const sessionStateCounter = meter.createCounter("openclaw.session.state", {
504
+ unit: "1",
505
+ description: "Session state transitions"
506
+ });
507
+ const sessionStuckCounter = meter.createCounter("openclaw.session.stuck", {
508
+ unit: "1",
509
+ description: "Sessions stuck in processing"
510
+ });
511
+ const sessionStuckAgeHistogram = meter.createHistogram("openclaw.session.stuck_age_ms", {
512
+ unit: "ms",
513
+ description: "Age of stuck sessions"
514
+ });
515
+ const runAttemptCounter = meter.createCounter("openclaw.run.attempt", {
516
+ unit: "1",
517
+ description: "Run attempts"
518
+ });
519
+ const toolLoopCounter = meter.createCounter("openclaw.tool.loop", {
520
+ unit: "1",
521
+ description: "Detected repetitive tool-call loop events"
522
+ });
523
+ const modelCallDurationHistogram = meter.createHistogram("openclaw.model_call.duration_ms", {
524
+ unit: "ms",
525
+ description: "Model call duration"
526
+ });
527
+ const modelCallRequestBytesHistogram = meter.createHistogram("openclaw.model_call.request_bytes", {
528
+ unit: "By",
529
+ description: "UTF-8 byte size of sanitized model request payloads"
530
+ });
531
+ const modelCallResponseBytesHistogram = meter.createHistogram("openclaw.model_call.response_bytes", {
532
+ unit: "By",
533
+ description: "UTF-8 byte size of streamed model response events"
534
+ });
535
+ const modelCallTimeToFirstByteHistogram = meter.createHistogram("openclaw.model_call.time_to_first_byte_ms", {
536
+ unit: "ms",
537
+ description: "Elapsed time before the first streamed model response event"
538
+ });
539
+ const toolExecutionDurationHistogram = meter.createHistogram("openclaw.tool.execution.duration_ms", {
540
+ unit: "ms",
541
+ description: "Tool execution duration"
542
+ });
543
+ const execProcessDurationHistogram = meter.createHistogram("openclaw.exec.duration_ms", {
544
+ unit: "ms",
545
+ description: "Exec process duration"
546
+ });
547
+ const memoryRssHistogram = meter.createHistogram("openclaw.memory.rss_bytes", {
548
+ unit: "By",
549
+ description: "Resident set size reported by diagnostic memory samples"
550
+ });
551
+ const memoryHeapUsedHistogram = meter.createHistogram("openclaw.memory.heap_used_bytes", {
552
+ unit: "By",
553
+ description: "Heap used bytes reported by diagnostic memory samples"
554
+ });
555
+ const memoryHeapTotalHistogram = meter.createHistogram("openclaw.memory.heap_total_bytes", {
556
+ unit: "By",
557
+ description: "Heap total bytes reported by diagnostic memory samples"
558
+ });
559
+ const memoryExternalHistogram = meter.createHistogram("openclaw.memory.external_bytes", {
560
+ unit: "By",
561
+ description: "External memory bytes reported by diagnostic memory samples"
562
+ });
563
+ const memoryArrayBuffersHistogram = meter.createHistogram("openclaw.memory.array_buffers_bytes", {
564
+ unit: "By",
565
+ description: "ArrayBuffer bytes reported by diagnostic memory samples"
566
+ });
567
+ const memoryPressureCounter = meter.createCounter("openclaw.memory.pressure", {
568
+ unit: "1",
569
+ description: "Diagnostic memory pressure events"
570
+ });
571
+ const livenessWarningCounter = meter.createCounter("openclaw.liveness.warning", {
572
+ unit: "1",
573
+ description: "Diagnostic liveness warning events"
574
+ });
575
+ const livenessEventLoopDelayP99Histogram = meter.createHistogram("openclaw.liveness.event_loop_delay_p99_ms", {
576
+ unit: "ms",
577
+ description: "P99 event-loop delay reported by diagnostic liveness warnings"
578
+ });
579
+ const livenessEventLoopDelayMaxHistogram = meter.createHistogram("openclaw.liveness.event_loop_delay_max_ms", {
580
+ unit: "ms",
581
+ description: "Maximum event-loop delay reported by diagnostic liveness warnings"
582
+ });
583
+ const livenessEventLoopUtilizationHistogram = meter.createHistogram("openclaw.liveness.event_loop_utilization", {
584
+ unit: "1",
585
+ description: "Event-loop utilization reported by diagnostic liveness warnings"
586
+ });
587
+ const livenessCpuCoreRatioHistogram = meter.createHistogram("openclaw.liveness.cpu_core_ratio", {
588
+ unit: "1",
589
+ description: "CPU core ratio reported by diagnostic liveness warnings"
590
+ });
591
+ const telemetryExporterCounter = meter.createCounter("openclaw.telemetry.exporter.events", {
592
+ unit: "1",
593
+ description: "Diagnostic telemetry exporter lifecycle and failure events"
594
+ });
595
+ let recordLogRecord;
596
+ if (logsEnabled) {
597
+ let logRecordExportFailureLastReportedAt = Number.NEGATIVE_INFINITY;
598
+ logProvider = new LoggerProvider({
599
+ resource,
600
+ processors: [new BatchLogRecordProcessor(new OTLPLogExporter({
601
+ ...logUrl ? { url: logUrl } : {},
602
+ ...headers ? { headers } : {}
603
+ }), typeof otel.flushIntervalMs === "number" ? { scheduledDelayMillis: Math.max(1e3, otel.flushIntervalMs) } : {})]
604
+ });
605
+ const otelLogger = logProvider.getLogger("openclaw");
606
+ recordLogRecord = (evt, metadata) => {
607
+ try {
608
+ const logLevelName = evt.level || "INFO";
609
+ const severityNumber = logSeverityMap[logLevelName] ?? 9;
610
+ const attributes = Object.create(null);
611
+ assignOtelLogAttribute(attributes, "openclaw.log.level", logLevelName);
612
+ if (evt.loggerName) assignOtelLogAttribute(attributes, "openclaw.logger", evt.loggerName);
613
+ if (evt.loggerParents?.length) assignOtelLogAttribute(attributes, "openclaw.logger.parents", evt.loggerParents.join("."));
614
+ assignOtelLogEventAttributes(attributes, evt.attributes);
615
+ if (evt.code?.line) assignOtelLogAttribute(attributes, "code.lineno", evt.code.line);
616
+ if (evt.code?.functionName) assignOtelLogAttribute(attributes, "code.function", evt.code.functionName);
617
+ if (metadata.trusted) addTraceAttributes(attributes, evt.trace);
618
+ const logRecord = {
619
+ body: normalizeOtelLogString(evt.message || "log", MAX_OTEL_LOG_BODY_CHARS),
620
+ severityText: logLevelName,
621
+ severityNumber,
622
+ attributes: redactOtelAttributes(attributes),
623
+ timestamp: evt.ts
624
+ };
625
+ const logContext = contextForTrustedTraceContext(evt, metadata);
626
+ if (logContext) logRecord.context = logContext;
627
+ otelLogger.emit(logRecord);
628
+ } catch (err) {
629
+ emitExporterEvent({
630
+ exporter: "diagnostics-otel",
631
+ signal: "logs",
632
+ status: "failure",
633
+ reason: "emit_failed",
634
+ errorCategory: errorCategory(err)
635
+ });
636
+ const now = Date.now();
637
+ if (now - logRecordExportFailureLastReportedAt >= LOG_RECORD_EXPORT_FAILURE_REPORT_INTERVAL_MS) {
638
+ logRecordExportFailureLastReportedAt = now;
639
+ ctx.logger.error(`diagnostics-otel: log record export failed: ${formatError(err)}`);
640
+ }
641
+ }
642
+ };
643
+ }
644
+ const spanWithDuration = (name, attributes, durationMs, options = {}) => {
645
+ const endTimeMs = options.endTimeMs ?? Date.now();
646
+ const startTime = typeof options.startTimeMs === "number" ? options.startTimeMs : typeof durationMs === "number" && durationMs >= 0 ? endTimeMs - durationMs : void 0;
647
+ const parentContext = "parentContext" in options ? options.parentContext ?? void 0 : void 0;
648
+ return tracer.startSpan(name, {
649
+ attributes: redactOtelAttributes(attributes),
650
+ ...startTime !== void 0 ? { startTime } : {}
651
+ }, parentContext);
652
+ };
653
+ const trustedTraceContext = (evt, metadata) => metadata.trusted ? normalizeTraceContext(evt.trace) : void 0;
654
+ const activeTrustedParentContext = (evt, metadata) => {
655
+ const parentSpanId = trustedTraceContext(evt, metadata)?.parentSpanId;
656
+ if (!parentSpanId) return;
657
+ const activeParentSpan = activeTrustedSpans.get(parentSpanId) ?? activeTrustedSpanAliases.get(parentSpanId);
658
+ if (!activeParentSpan) return;
659
+ return trace.setSpanContext(context.active(), activeParentSpan.spanContext());
660
+ };
661
+ const trackTrustedSpan = (evt, metadata, span) => {
662
+ const spanId = trustedTraceContext(evt, metadata)?.spanId;
663
+ if (spanId) activeTrustedSpans.set(spanId, span);
664
+ return span;
665
+ };
666
+ const takeTrackedTrustedSpan = (evt, metadata) => {
667
+ const spanId = trustedTraceContext(evt, metadata)?.spanId;
668
+ if (!spanId) return;
669
+ const span = activeTrustedSpans.get(spanId);
670
+ if (span) activeTrustedSpans.delete(spanId);
671
+ return span;
672
+ };
673
+ const setSpanAttrs = (span, attributes) => {
674
+ span.setAttributes?.(redactOtelAttributes(attributes));
675
+ };
676
+ const scheduleTrackedRunSpanFinalize = (spanId, parentSpanId, span, endTimeMs) => {
677
+ const existingHandle = pendingTrustedRunFinalizers.get(spanId);
678
+ if (existingHandle) clearImmediate(existingHandle);
679
+ const handle = setImmediate(() => {
680
+ pendingTrustedRunFinalizers.delete(spanId);
681
+ if (activeTrustedSpans.get(spanId) === span) activeTrustedSpans.delete(spanId);
682
+ if (parentSpanId && activeTrustedSpanAliases.get(parentSpanId) === span) activeTrustedSpanAliases.delete(parentSpanId);
683
+ span.end(endTimeMs);
684
+ });
685
+ pendingTrustedRunFinalizers.set(spanId, handle);
686
+ };
687
+ const addRunAttrs = (spanAttrs, evt) => {
688
+ if (evt.provider) spanAttrs["openclaw.provider"] = evt.provider;
689
+ if (evt.model) spanAttrs["openclaw.model"] = evt.model;
690
+ if (evt.channel) spanAttrs["openclaw.channel"] = evt.channel;
691
+ if (evt.trigger) spanAttrs["openclaw.trigger"] = evt.trigger;
692
+ };
693
+ const paramsSummaryAttrs = (summary) => {
694
+ if (!summary) return {};
695
+ return {
696
+ "openclaw.tool.params.kind": summary.kind,
697
+ ..."length" in summary ? { "openclaw.tool.params.length": summary.length } : {}
698
+ };
699
+ };
700
+ const recordModelUsage = (evt, metadata) => {
701
+ const attrs = {
702
+ "openclaw.channel": evt.channel ?? "unknown",
703
+ "openclaw.agent": lowCardinalityAttr(evt.agentId),
704
+ "openclaw.provider": evt.provider ?? "unknown",
705
+ "openclaw.model": evt.model ?? "unknown"
706
+ };
707
+ const genAiAttrs = {
708
+ "gen_ai.operation.name": "chat",
709
+ "gen_ai.provider.name": lowCardinalityAttr(evt.provider),
710
+ "gen_ai.request.model": lowCardinalityAttr(evt.model)
711
+ };
712
+ const usage = evt.usage;
713
+ if (usage.input) {
714
+ tokensCounter.add(usage.input, {
715
+ ...attrs,
716
+ "openclaw.token": "input"
717
+ });
718
+ genAiTokenUsageHistogram.record(usage.input, {
719
+ ...genAiAttrs,
720
+ "gen_ai.token.type": "input"
721
+ });
722
+ }
723
+ if (usage.output) {
724
+ tokensCounter.add(usage.output, {
725
+ ...attrs,
726
+ "openclaw.token": "output"
727
+ });
728
+ genAiTokenUsageHistogram.record(usage.output, {
729
+ ...genAiAttrs,
730
+ "gen_ai.token.type": "output"
731
+ });
732
+ }
733
+ if (usage.cacheRead) tokensCounter.add(usage.cacheRead, {
734
+ ...attrs,
735
+ "openclaw.token": "cache_read"
736
+ });
737
+ if (usage.cacheWrite) tokensCounter.add(usage.cacheWrite, {
738
+ ...attrs,
739
+ "openclaw.token": "cache_write"
740
+ });
741
+ if (usage.promptTokens) tokensCounter.add(usage.promptTokens, {
742
+ ...attrs,
743
+ "openclaw.token": "prompt"
744
+ });
745
+ if (usage.total) tokensCounter.add(usage.total, {
746
+ ...attrs,
747
+ "openclaw.token": "total"
748
+ });
749
+ if (evt.costUsd) costCounter.add(evt.costUsd, attrs);
750
+ if (evt.durationMs) durationHistogram.record(evt.durationMs, attrs);
751
+ if (evt.context?.limit) contextHistogram.record(evt.context.limit, {
752
+ ...attrs,
753
+ "openclaw.context": "limit"
754
+ });
755
+ if (evt.context?.used) contextHistogram.record(evt.context.used, {
756
+ ...attrs,
757
+ "openclaw.context": "used"
758
+ });
759
+ if (!tracesEnabled) return;
760
+ const genAiInputTokens = usage.promptTokens ?? (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
761
+ const spanAttrs = {
762
+ ...attrs,
763
+ "openclaw.tokens.input": usage.input ?? 0,
764
+ "openclaw.tokens.output": usage.output ?? 0,
765
+ "openclaw.tokens.cache_read": usage.cacheRead ?? 0,
766
+ "openclaw.tokens.cache_write": usage.cacheWrite ?? 0,
767
+ "openclaw.tokens.total": usage.total ?? 0
768
+ };
769
+ assignGenAiSpanIdentityAttrs(spanAttrs, evt);
770
+ assignPositiveNumberAttr(spanAttrs, "gen_ai.usage.input_tokens", genAiInputTokens);
771
+ assignPositiveNumberAttr(spanAttrs, "gen_ai.usage.output_tokens", usage.output);
772
+ assignPositiveNumberAttr(spanAttrs, "gen_ai.usage.cache_read.input_tokens", usage.cacheRead);
773
+ assignPositiveNumberAttr(spanAttrs, "gen_ai.usage.cache_creation.input_tokens", usage.cacheWrite);
774
+ spanWithDuration("openclaw.model.usage", spanAttrs, evt.durationMs, {
775
+ parentContext: activeTrustedParentContext(evt, metadata),
776
+ endTimeMs: evt.ts
777
+ }).end(evt.ts);
778
+ };
779
+ const recordWebhookReceived = (evt) => {
780
+ const attrs = {
781
+ "openclaw.channel": evt.channel ?? "unknown",
782
+ "openclaw.webhook": evt.updateType ?? "unknown"
783
+ };
784
+ webhookReceivedCounter.add(1, attrs);
785
+ };
786
+ const recordWebhookProcessed = (evt) => {
787
+ const attrs = {
788
+ "openclaw.channel": evt.channel ?? "unknown",
789
+ "openclaw.webhook": evt.updateType ?? "unknown"
790
+ };
791
+ if (typeof evt.durationMs === "number") webhookDurationHistogram.record(evt.durationMs, attrs);
792
+ if (!tracesEnabled) return;
793
+ const spanAttrs = { ...attrs };
794
+ if (evt.chatId !== void 0) spanAttrs["openclaw.chatId"] = String(evt.chatId);
795
+ spanWithDuration("openclaw.webhook.processed", spanAttrs, evt.durationMs).end();
796
+ };
797
+ const recordWebhookError = (evt) => {
798
+ const attrs = {
799
+ "openclaw.channel": evt.channel ?? "unknown",
800
+ "openclaw.webhook": evt.updateType ?? "unknown"
801
+ };
802
+ webhookErrorCounter.add(1, attrs);
803
+ if (!tracesEnabled) return;
804
+ const redactedError = redactSensitiveText(evt.error);
805
+ const spanAttrs = {
806
+ ...attrs,
807
+ "openclaw.error": redactedError
808
+ };
809
+ if (evt.chatId !== void 0) spanAttrs["openclaw.chatId"] = String(evt.chatId);
810
+ const span = tracer.startSpan("openclaw.webhook.error", { attributes: spanAttrs });
811
+ span.setStatus({
812
+ code: SpanStatusCode.ERROR,
813
+ message: redactedError
814
+ });
815
+ span.end();
816
+ };
817
+ const recordMessageQueued = (evt) => {
818
+ const attrs = {
819
+ "openclaw.channel": evt.channel ?? "unknown",
820
+ "openclaw.source": evt.source ?? "unknown"
821
+ };
822
+ messageQueuedCounter.add(1, attrs);
823
+ if (typeof evt.queueDepth === "number") queueDepthHistogram.record(evt.queueDepth, attrs);
824
+ };
825
+ const recordMessageProcessed = (evt) => {
826
+ const attrs = {
827
+ "openclaw.channel": evt.channel ?? "unknown",
828
+ "openclaw.outcome": evt.outcome ?? "unknown"
829
+ };
830
+ messageProcessedCounter.add(1, attrs);
831
+ if (typeof evt.durationMs === "number") messageDurationHistogram.record(evt.durationMs, attrs);
832
+ if (!tracesEnabled) return;
833
+ const spanAttrs = { ...attrs };
834
+ if (evt.chatId !== void 0) spanAttrs["openclaw.chatId"] = String(evt.chatId);
835
+ if (evt.messageId !== void 0) spanAttrs["openclaw.messageId"] = String(evt.messageId);
836
+ if (evt.reason) spanAttrs["openclaw.reason"] = redactSensitiveText(evt.reason);
837
+ const span = spanWithDuration("openclaw.message.processed", spanAttrs, evt.durationMs);
838
+ if (evt.outcome === "error" && evt.error) span.setStatus({
839
+ code: SpanStatusCode.ERROR,
840
+ message: redactSensitiveText(evt.error)
841
+ });
842
+ span.end();
843
+ };
844
+ const messageDeliveryAttrs = (evt) => ({
845
+ "openclaw.channel": evt.channel,
846
+ "openclaw.delivery.kind": evt.deliveryKind
847
+ });
848
+ const recordMessageDeliveryStarted = (evt) => {
849
+ messageDeliveryStartedCounter.add(1, messageDeliveryAttrs(evt));
850
+ };
851
+ const recordMessageDeliveryCompleted = (evt) => {
852
+ const attrs = {
853
+ ...messageDeliveryAttrs(evt),
854
+ "openclaw.outcome": "completed"
855
+ };
856
+ messageDeliveryDurationHistogram.record(evt.durationMs, attrs);
857
+ if (!tracesEnabled) return;
858
+ spanWithDuration("openclaw.message.delivery", {
859
+ ...attrs,
860
+ "openclaw.delivery.result_count": evt.resultCount
861
+ }, evt.durationMs, { endTimeMs: evt.ts }).end(evt.ts);
862
+ };
863
+ const recordMessageDeliveryError = (evt) => {
864
+ const attrs = {
865
+ ...messageDeliveryAttrs(evt),
866
+ "openclaw.outcome": "error",
867
+ "openclaw.errorCategory": lowCardinalityAttr(evt.errorCategory, "other")
868
+ };
869
+ messageDeliveryDurationHistogram.record(evt.durationMs, attrs);
870
+ if (!tracesEnabled) return;
871
+ const span = spanWithDuration("openclaw.message.delivery", attrs, evt.durationMs, { endTimeMs: evt.ts });
872
+ span.setStatus({
873
+ code: SpanStatusCode.ERROR,
874
+ message: redactSensitiveText(evt.errorCategory)
875
+ });
876
+ span.end(evt.ts);
877
+ };
878
+ const recordRunStarted = (evt, metadata) => {
879
+ if (!tracesEnabled || !metadata.trusted) return;
880
+ const spanAttrs = {};
881
+ addRunAttrs(spanAttrs, evt);
882
+ const span = trackTrustedSpan(evt, metadata, spanWithDuration("openclaw.run", spanAttrs, void 0, {
883
+ parentContext: activeTrustedParentContext(evt, metadata),
884
+ startTimeMs: evt.ts
885
+ }));
886
+ const parentSpanId = trustedTraceContext(evt, metadata)?.parentSpanId;
887
+ if (parentSpanId && !activeTrustedSpans.has(parentSpanId)) activeTrustedSpanAliases.set(parentSpanId, span);
888
+ };
889
+ const recordLaneEnqueue = (evt) => {
890
+ const attrs = { "openclaw.lane": evt.lane };
891
+ laneEnqueueCounter.add(1, attrs);
892
+ queueDepthHistogram.record(evt.queueSize, attrs);
893
+ };
894
+ const recordLaneDequeue = (evt) => {
895
+ const attrs = { "openclaw.lane": evt.lane };
896
+ laneDequeueCounter.add(1, attrs);
897
+ queueDepthHistogram.record(evt.queueSize, attrs);
898
+ if (typeof evt.waitMs === "number") queueWaitHistogram.record(evt.waitMs, attrs);
899
+ };
900
+ const recordSessionState = (evt) => {
901
+ const attrs = { "openclaw.state": evt.state };
902
+ if (evt.reason) attrs["openclaw.reason"] = redactSensitiveText(evt.reason);
903
+ sessionStateCounter.add(1, attrs);
904
+ };
905
+ const recordSessionStuck = (evt) => {
906
+ const attrs = { "openclaw.state": evt.state };
907
+ sessionStuckCounter.add(1, attrs);
908
+ if (typeof evt.ageMs === "number") sessionStuckAgeHistogram.record(evt.ageMs, attrs);
909
+ if (!tracesEnabled) return;
910
+ const spanAttrs = { ...attrs };
911
+ spanAttrs["openclaw.queueDepth"] = evt.queueDepth ?? 0;
912
+ spanAttrs["openclaw.ageMs"] = evt.ageMs;
913
+ const span = tracer.startSpan("openclaw.session.stuck", { attributes: spanAttrs });
914
+ span.setStatus({
915
+ code: SpanStatusCode.ERROR,
916
+ message: "session stuck"
917
+ });
918
+ span.end();
919
+ };
920
+ const recordRunAttempt = (evt) => {
921
+ runAttemptCounter.add(1, { "openclaw.attempt": evt.attempt });
922
+ };
923
+ const toolLoopAttrs = (evt) => ({
924
+ "openclaw.toolName": lowCardinalityAttr(evt.toolName, "tool"),
925
+ "openclaw.loop.level": evt.level,
926
+ "openclaw.loop.action": evt.action,
927
+ "openclaw.loop.detector": evt.detector,
928
+ "openclaw.loop.count": evt.count,
929
+ ...evt.pairedToolName ? { "openclaw.loop.paired_tool": lowCardinalityAttr(evt.pairedToolName, "tool") } : {}
930
+ });
931
+ const recordToolLoop = (evt) => {
932
+ const attrs = toolLoopAttrs(evt);
933
+ toolLoopCounter.add(1, attrs);
934
+ if (!tracesEnabled) return;
935
+ const span = spanWithDuration("openclaw.tool.loop", attrs, 0, { endTimeMs: evt.ts });
936
+ if (evt.level === "critical" || evt.action === "block") span.setStatus({
937
+ code: SpanStatusCode.ERROR,
938
+ message: `${evt.detector}:${evt.action}`
939
+ });
940
+ span.end(evt.ts);
941
+ };
942
+ const recordMemoryUsageMetrics = (evt, attrs = {}) => {
943
+ memoryRssHistogram.record(evt.memory.rssBytes, attrs);
944
+ memoryHeapUsedHistogram.record(evt.memory.heapUsedBytes, attrs);
945
+ memoryHeapTotalHistogram.record(evt.memory.heapTotalBytes, attrs);
946
+ memoryExternalHistogram.record(evt.memory.externalBytes, attrs);
947
+ memoryArrayBuffersHistogram.record(evt.memory.arrayBuffersBytes, attrs);
948
+ };
949
+ const recordMemorySample = (evt) => {
950
+ recordMemoryUsageMetrics(evt);
951
+ };
952
+ const recordMemoryPressure = (evt) => {
953
+ const attrs = {
954
+ "openclaw.memory.level": evt.level,
955
+ "openclaw.memory.reason": evt.reason
956
+ };
957
+ memoryPressureCounter.add(1, attrs);
958
+ recordMemoryUsageMetrics(evt, attrs);
959
+ if (!tracesEnabled) return;
960
+ const span = spanWithDuration("openclaw.memory.pressure", {
961
+ ...attrs,
962
+ "openclaw.memory.rss_bytes": evt.memory.rssBytes,
963
+ "openclaw.memory.heap_used_bytes": evt.memory.heapUsedBytes,
964
+ "openclaw.memory.heap_total_bytes": evt.memory.heapTotalBytes,
965
+ "openclaw.memory.external_bytes": evt.memory.externalBytes,
966
+ "openclaw.memory.array_buffers_bytes": evt.memory.arrayBuffersBytes,
967
+ ...evt.thresholdBytes !== void 0 ? { "openclaw.memory.threshold_bytes": evt.thresholdBytes } : {},
968
+ ...evt.rssGrowthBytes !== void 0 ? { "openclaw.memory.rss_growth_bytes": evt.rssGrowthBytes } : {},
969
+ ...evt.windowMs !== void 0 ? { "openclaw.memory.window_ms": evt.windowMs } : {}
970
+ }, 0, { endTimeMs: evt.ts });
971
+ if (evt.level === "critical") span.setStatus({
972
+ code: SpanStatusCode.ERROR,
973
+ message: evt.reason
974
+ });
975
+ span.end(evt.ts);
976
+ };
977
+ const recordRunCompleted = (evt, metadata) => {
978
+ const attrs = {
979
+ "openclaw.outcome": evt.outcome,
980
+ "openclaw.provider": evt.provider ?? "unknown",
981
+ "openclaw.model": evt.model ?? "unknown"
982
+ };
983
+ if (evt.channel) attrs["openclaw.channel"] = evt.channel;
984
+ durationHistogram.record(evt.durationMs, attrs);
985
+ if (!tracesEnabled) return;
986
+ const spanAttrs = { "openclaw.outcome": evt.outcome };
987
+ addRunAttrs(spanAttrs, evt);
988
+ if (evt.errorCategory) spanAttrs["openclaw.errorCategory"] = lowCardinalityAttr(evt.errorCategory, "other");
989
+ const trustedTrace = trustedTraceContext(evt, metadata);
990
+ const trackedSpan = trustedTrace?.spanId ? activeTrustedSpans.get(trustedTrace.spanId) : void 0;
991
+ const span = trackedSpan ?? spanWithDuration("openclaw.run", spanAttrs, evt.durationMs, {
992
+ parentContext: activeTrustedParentContext(evt, metadata),
993
+ endTimeMs: evt.ts
994
+ });
995
+ setSpanAttrs(span, spanAttrs);
996
+ if (evt.outcome === "error") span.setStatus({
997
+ code: SpanStatusCode.ERROR,
998
+ ...evt.errorCategory ? { message: redactSensitiveText(evt.errorCategory) } : {}
999
+ });
1000
+ if (trackedSpan && trustedTrace?.spanId) {
1001
+ scheduleTrackedRunSpanFinalize(trustedTrace.spanId, trustedTrace.parentSpanId, trackedSpan, evt.ts);
1002
+ return;
1003
+ }
1004
+ span.end(evt.ts);
1005
+ };
1006
+ const harnessRunMetricAttrs = (evt) => ({
1007
+ "openclaw.harness.id": lowCardinalityAttr(evt.harnessId, "unknown"),
1008
+ "openclaw.harness.plugin": lowCardinalityAttr(evt.pluginId),
1009
+ ...evt.type === "harness.run.started" ? {} : { "openclaw.outcome": evt.type === "harness.run.error" ? "error" : evt.outcome },
1010
+ "openclaw.provider": lowCardinalityAttr(evt.provider, "unknown"),
1011
+ "openclaw.model": lowCardinalityAttr(evt.model, "unknown"),
1012
+ ...evt.channel ? { "openclaw.channel": lowCardinalityAttr(evt.channel) } : {}
1013
+ });
1014
+ const recordHarnessRunStarted = (evt, metadata) => {
1015
+ if (!tracesEnabled || !metadata.trusted) return;
1016
+ trackTrustedSpan(evt, metadata, spanWithDuration("openclaw.harness.run", harnessRunMetricAttrs(evt), void 0, {
1017
+ parentContext: activeTrustedParentContext(evt, metadata),
1018
+ startTimeMs: evt.ts
1019
+ }));
1020
+ };
1021
+ const recordHarnessRunCompleted = (evt, metadata) => {
1022
+ harnessDurationHistogram.record(evt.durationMs, harnessRunMetricAttrs(evt));
1023
+ if (!tracesEnabled) return;
1024
+ const spanAttrs = { ...harnessRunMetricAttrs(evt) };
1025
+ if (evt.resultClassification) spanAttrs["openclaw.harness.result_classification"] = lowCardinalityAttr(evt.resultClassification);
1026
+ if (typeof evt.yieldDetected === "boolean") spanAttrs["openclaw.harness.yield_detected"] = evt.yieldDetected;
1027
+ if (evt.itemLifecycle) {
1028
+ spanAttrs["openclaw.harness.items.started"] = evt.itemLifecycle.startedCount;
1029
+ spanAttrs["openclaw.harness.items.completed"] = evt.itemLifecycle.completedCount;
1030
+ spanAttrs["openclaw.harness.items.active"] = evt.itemLifecycle.activeCount;
1031
+ }
1032
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.harness.run", spanAttrs, evt.durationMs, {
1033
+ parentContext: activeTrustedParentContext(evt, metadata),
1034
+ endTimeMs: evt.ts
1035
+ });
1036
+ setSpanAttrs(span, spanAttrs);
1037
+ if (evt.outcome === "error") span.setStatus({
1038
+ code: SpanStatusCode.ERROR,
1039
+ message: "error"
1040
+ });
1041
+ span.end(evt.ts);
1042
+ };
1043
+ const recordHarnessRunError = (evt, metadata) => {
1044
+ const errorType = lowCardinalityAttr(evt.errorCategory, "other");
1045
+ const attrs = {
1046
+ ...harnessRunMetricAttrs(evt),
1047
+ "openclaw.harness.phase": evt.phase,
1048
+ "openclaw.errorCategory": errorType
1049
+ };
1050
+ harnessDurationHistogram.record(evt.durationMs, attrs);
1051
+ if (!tracesEnabled) return;
1052
+ const spanAttrs = {
1053
+ ...attrs,
1054
+ "error.type": errorType,
1055
+ ...evt.cleanupFailed ? { "openclaw.harness.cleanup_failed": true } : {}
1056
+ };
1057
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.harness.run", spanAttrs, evt.durationMs, {
1058
+ parentContext: activeTrustedParentContext(evt, metadata),
1059
+ endTimeMs: evt.ts
1060
+ });
1061
+ setSpanAttrs(span, spanAttrs);
1062
+ span.setStatus({
1063
+ code: SpanStatusCode.ERROR,
1064
+ message: errorType
1065
+ });
1066
+ span.end(evt.ts);
1067
+ };
1068
+ const recordContextAssembled = (evt, metadata) => {
1069
+ if (!tracesEnabled) return;
1070
+ const spanAttrs = {
1071
+ "openclaw.context.message_count": evt.messageCount,
1072
+ "openclaw.context.history_text_chars": evt.historyTextChars,
1073
+ "openclaw.context.history_image_blocks": evt.historyImageBlocks,
1074
+ "openclaw.context.max_message_text_chars": evt.maxMessageTextChars,
1075
+ "openclaw.context.system_prompt_chars": evt.systemPromptChars,
1076
+ "openclaw.context.prompt_chars": evt.promptChars,
1077
+ "openclaw.context.prompt_images": evt.promptImages
1078
+ };
1079
+ addRunAttrs(spanAttrs, evt);
1080
+ if (evt.contextTokenBudget !== void 0) spanAttrs["openclaw.context.token_budget"] = evt.contextTokenBudget;
1081
+ if (evt.reserveTokens !== void 0) spanAttrs["openclaw.context.reserve_tokens"] = evt.reserveTokens;
1082
+ spanWithDuration("openclaw.context.assembled", spanAttrs, 0, {
1083
+ parentContext: activeTrustedParentContext(evt, metadata),
1084
+ endTimeMs: evt.ts
1085
+ }).end(evt.ts);
1086
+ };
1087
+ const modelCallMetricAttrs = (evt) => ({
1088
+ "openclaw.provider": evt.provider,
1089
+ "openclaw.model": evt.model,
1090
+ "openclaw.api": lowCardinalityAttr(evt.api),
1091
+ "openclaw.transport": lowCardinalityAttr(evt.transport)
1092
+ });
1093
+ const genAiModelCallMetricAttrs = (evt, errorType) => ({
1094
+ "gen_ai.operation.name": genAiOperationName(evt.api),
1095
+ "gen_ai.provider.name": lowCardinalityAttr(evt.provider),
1096
+ "gen_ai.request.model": lowCardinalityAttr(evt.model),
1097
+ ...errorType ? { "error.type": errorType } : {}
1098
+ });
1099
+ const recordModelCallSizeTimingMetrics = (evt, attrs) => {
1100
+ const requestPayloadBytes = positiveFiniteNumber(evt.requestPayloadBytes);
1101
+ if (requestPayloadBytes !== void 0) modelCallRequestBytesHistogram.record(requestPayloadBytes, attrs);
1102
+ const responseStreamBytes = positiveFiniteNumber(evt.responseStreamBytes);
1103
+ if (responseStreamBytes !== void 0) modelCallResponseBytesHistogram.record(responseStreamBytes, attrs);
1104
+ const timeToFirstByteMs = positiveFiniteNumber(evt.timeToFirstByteMs);
1105
+ if (timeToFirstByteMs !== void 0) modelCallTimeToFirstByteHistogram.record(timeToFirstByteMs, attrs);
1106
+ };
1107
+ const recordModelCallStarted = (evt, metadata) => {
1108
+ if (!tracesEnabled || !metadata.trusted) return;
1109
+ const spanAttrs = {
1110
+ "openclaw.provider": evt.provider,
1111
+ "openclaw.model": evt.model
1112
+ };
1113
+ assignGenAiModelCallAttrs(spanAttrs, evt);
1114
+ if (evt.api) spanAttrs["openclaw.api"] = evt.api;
1115
+ if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
1116
+ trackTrustedSpan(evt, metadata, spanWithDuration("openclaw.model.call", spanAttrs, void 0, {
1117
+ parentContext: activeTrustedParentContext(evt, metadata),
1118
+ startTimeMs: evt.ts
1119
+ }));
1120
+ };
1121
+ const recordModelCallCompleted = (evt, metadata) => {
1122
+ const metricAttrs = modelCallMetricAttrs(evt);
1123
+ modelCallDurationHistogram.record(evt.durationMs, metricAttrs);
1124
+ recordModelCallSizeTimingMetrics(evt, metricAttrs);
1125
+ genAiOperationDurationHistogram.record(evt.durationMs / 1e3, genAiModelCallMetricAttrs(evt));
1126
+ if (!tracesEnabled) return;
1127
+ const spanAttrs = {
1128
+ "openclaw.provider": evt.provider,
1129
+ "openclaw.model": evt.model
1130
+ };
1131
+ assignGenAiModelCallAttrs(spanAttrs, evt);
1132
+ if (evt.api) spanAttrs["openclaw.api"] = evt.api;
1133
+ if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
1134
+ assignModelCallSizeTimingAttrs(spanAttrs, evt);
1135
+ assignOtelModelContentAttributes(spanAttrs, evt, contentCapturePolicy);
1136
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.model.call", spanAttrs, evt.durationMs, {
1137
+ parentContext: activeTrustedParentContext(evt, metadata),
1138
+ endTimeMs: evt.ts
1139
+ });
1140
+ setSpanAttrs(span, spanAttrs);
1141
+ addUpstreamRequestIdSpanEvent(span, evt.upstreamRequestIdHash);
1142
+ span.end(evt.ts);
1143
+ };
1144
+ const recordModelCallError = (evt, metadata) => {
1145
+ const errorType = lowCardinalityAttr(evt.errorCategory, "other");
1146
+ const metricAttrs = {
1147
+ ...modelCallMetricAttrs(evt),
1148
+ "openclaw.errorCategory": errorType,
1149
+ ...evt.failureKind ? { "openclaw.failureKind": lowCardinalityAttr(evt.failureKind, "other") } : {}
1150
+ };
1151
+ modelCallDurationHistogram.record(evt.durationMs, metricAttrs);
1152
+ recordModelCallSizeTimingMetrics(evt, metricAttrs);
1153
+ genAiOperationDurationHistogram.record(evt.durationMs / 1e3, genAiModelCallMetricAttrs(evt, errorType));
1154
+ if (!tracesEnabled) return;
1155
+ const spanAttrs = {
1156
+ "openclaw.provider": evt.provider,
1157
+ "openclaw.model": evt.model,
1158
+ "openclaw.errorCategory": errorType,
1159
+ "error.type": errorType
1160
+ };
1161
+ if (evt.failureKind) spanAttrs["openclaw.failureKind"] = lowCardinalityAttr(evt.failureKind, "other");
1162
+ assignGenAiModelCallAttrs(spanAttrs, evt);
1163
+ if (evt.api) spanAttrs["openclaw.api"] = evt.api;
1164
+ if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
1165
+ assignModelCallSizeTimingAttrs(spanAttrs, evt);
1166
+ assignOtelModelContentAttributes(spanAttrs, evt, contentCapturePolicy);
1167
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.model.call", spanAttrs, evt.durationMs, {
1168
+ parentContext: activeTrustedParentContext(evt, metadata),
1169
+ endTimeMs: evt.ts
1170
+ });
1171
+ setSpanAttrs(span, spanAttrs);
1172
+ addUpstreamRequestIdSpanEvent(span, evt.upstreamRequestIdHash);
1173
+ span.setStatus({
1174
+ code: SpanStatusCode.ERROR,
1175
+ message: redactSensitiveText(evt.errorCategory)
1176
+ });
1177
+ span.end(evt.ts);
1178
+ };
1179
+ const toolExecutionBaseAttrs = (evt) => ({
1180
+ "openclaw.toolName": evt.toolName,
1181
+ "gen_ai.tool.name": evt.toolName,
1182
+ ...paramsSummaryAttrs(evt.paramsSummary)
1183
+ });
1184
+ const recordToolExecutionStarted = (evt, metadata) => {
1185
+ if (!tracesEnabled || !metadata.trusted) return;
1186
+ trackTrustedSpan(evt, metadata, spanWithDuration("openclaw.tool.execution", toolExecutionBaseAttrs(evt), void 0, {
1187
+ parentContext: activeTrustedParentContext(evt, metadata),
1188
+ startTimeMs: evt.ts
1189
+ }));
1190
+ };
1191
+ const recordToolExecutionCompleted = (evt, metadata) => {
1192
+ const attrs = {
1193
+ "openclaw.toolName": evt.toolName,
1194
+ ...paramsSummaryAttrs(evt.paramsSummary)
1195
+ };
1196
+ toolExecutionDurationHistogram.record(evt.durationMs, attrs);
1197
+ if (!tracesEnabled) return;
1198
+ const spanAttrs = { ...toolExecutionBaseAttrs(evt) };
1199
+ addRunAttrs(spanAttrs, evt);
1200
+ assignOtelToolContentAttributes(spanAttrs, evt, contentCapturePolicy);
1201
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.tool.execution", spanAttrs, evt.durationMs, {
1202
+ parentContext: activeTrustedParentContext(evt, metadata),
1203
+ endTimeMs: evt.ts
1204
+ });
1205
+ setSpanAttrs(span, spanAttrs);
1206
+ span.end(evt.ts);
1207
+ };
1208
+ const recordToolExecutionError = (evt, metadata) => {
1209
+ const attrs = {
1210
+ "openclaw.toolName": evt.toolName,
1211
+ "openclaw.errorCategory": lowCardinalityAttr(evt.errorCategory, "other"),
1212
+ ...paramsSummaryAttrs(evt.paramsSummary)
1213
+ };
1214
+ toolExecutionDurationHistogram.record(evt.durationMs, attrs);
1215
+ if (!tracesEnabled) return;
1216
+ const spanAttrs = {
1217
+ ...toolExecutionBaseAttrs(evt),
1218
+ "openclaw.errorCategory": lowCardinalityAttr(evt.errorCategory, "other")
1219
+ };
1220
+ addRunAttrs(spanAttrs, evt);
1221
+ if (evt.errorCode) spanAttrs["openclaw.errorCode"] = lowCardinalityAttr(evt.errorCode, "other");
1222
+ assignOtelToolContentAttributes(spanAttrs, evt, contentCapturePolicy);
1223
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.tool.execution", spanAttrs, evt.durationMs, {
1224
+ parentContext: activeTrustedParentContext(evt, metadata),
1225
+ endTimeMs: evt.ts
1226
+ });
1227
+ setSpanAttrs(span, spanAttrs);
1228
+ span.setStatus({
1229
+ code: SpanStatusCode.ERROR,
1230
+ message: redactSensitiveText(evt.errorCategory)
1231
+ });
1232
+ span.end(evt.ts);
1233
+ };
1234
+ const recordToolExecutionBlocked = (evt, metadata) => {
1235
+ if (!tracesEnabled) return;
1236
+ const spanAttrs = {
1237
+ ...toolExecutionBaseAttrs(evt),
1238
+ "openclaw.outcome": "blocked",
1239
+ "openclaw.deniedReason": lowCardinalityAttr(evt.deniedReason, "other")
1240
+ };
1241
+ addRunAttrs(spanAttrs, evt);
1242
+ const span = spanWithDuration("openclaw.tool.execution", spanAttrs, 0, {
1243
+ parentContext: activeTrustedParentContext(evt, metadata),
1244
+ endTimeMs: evt.ts
1245
+ });
1246
+ setSpanAttrs(span, spanAttrs);
1247
+ span.end(evt.ts);
1248
+ };
1249
+ const recordExecProcessCompleted = (evt) => {
1250
+ const attrs = {
1251
+ "openclaw.exec.target": evt.target,
1252
+ "openclaw.exec.mode": evt.mode,
1253
+ "openclaw.outcome": evt.outcome
1254
+ };
1255
+ if (evt.failureKind) attrs["openclaw.failureKind"] = evt.failureKind;
1256
+ execProcessDurationHistogram.record(evt.durationMs, attrs);
1257
+ if (!tracesEnabled) return;
1258
+ const spanAttrs = {
1259
+ ...attrs,
1260
+ "openclaw.exec.command_length": evt.commandLength
1261
+ };
1262
+ if (typeof evt.exitCode === "number") spanAttrs["openclaw.exec.exit_code"] = evt.exitCode;
1263
+ if (evt.exitSignal) spanAttrs["openclaw.exec.exit_signal"] = lowCardinalityAttr(evt.exitSignal, "other");
1264
+ if (evt.timedOut !== void 0) spanAttrs["openclaw.exec.timed_out"] = evt.timedOut;
1265
+ const span = spanWithDuration("openclaw.exec", spanAttrs, evt.durationMs, { endTimeMs: evt.ts });
1266
+ if (evt.outcome === "failed") span.setStatus({
1267
+ code: SpanStatusCode.ERROR,
1268
+ ...evt.failureKind ? { message: evt.failureKind } : {}
1269
+ });
1270
+ span.end(evt.ts);
1271
+ };
1272
+ const recordHeartbeat = (evt) => {
1273
+ queueDepthHistogram.record(evt.queued, { "openclaw.channel": "heartbeat" });
1274
+ };
1275
+ const recordLivenessWarning = (evt) => {
1276
+ const reason = evt.reasons.join(":");
1277
+ const attrs = { "openclaw.liveness.reason": lowCardinalityAttr(reason, "unknown") };
1278
+ livenessWarningCounter.add(1, attrs);
1279
+ queueDepthHistogram.record(evt.queued, { "openclaw.channel": "liveness" });
1280
+ if (evt.eventLoopDelayP99Ms !== void 0) livenessEventLoopDelayP99Histogram.record(evt.eventLoopDelayP99Ms, attrs);
1281
+ if (evt.eventLoopDelayMaxMs !== void 0) livenessEventLoopDelayMaxHistogram.record(evt.eventLoopDelayMaxMs, attrs);
1282
+ if (evt.eventLoopUtilization !== void 0) livenessEventLoopUtilizationHistogram.record(evt.eventLoopUtilization, attrs);
1283
+ if (evt.cpuCoreRatio !== void 0) livenessCpuCoreRatioHistogram.record(evt.cpuCoreRatio, attrs);
1284
+ if (!tracesEnabled) return;
1285
+ const span = spanWithDuration("openclaw.liveness.warning", {
1286
+ ...attrs,
1287
+ "openclaw.liveness.active": evt.active,
1288
+ "openclaw.liveness.waiting": evt.waiting,
1289
+ "openclaw.liveness.queued": evt.queued,
1290
+ "openclaw.liveness.interval_ms": evt.intervalMs,
1291
+ ...evt.eventLoopDelayP99Ms !== void 0 ? { "openclaw.liveness.event_loop_delay_p99_ms": evt.eventLoopDelayP99Ms } : {},
1292
+ ...evt.eventLoopDelayMaxMs !== void 0 ? { "openclaw.liveness.event_loop_delay_max_ms": evt.eventLoopDelayMaxMs } : {},
1293
+ ...evt.eventLoopUtilization !== void 0 ? { "openclaw.liveness.event_loop_utilization": evt.eventLoopUtilization } : {},
1294
+ ...evt.cpuUserMs !== void 0 ? { "openclaw.liveness.cpu_user_ms": evt.cpuUserMs } : {},
1295
+ ...evt.cpuSystemMs !== void 0 ? { "openclaw.liveness.cpu_system_ms": evt.cpuSystemMs } : {},
1296
+ ...evt.cpuTotalMs !== void 0 ? { "openclaw.liveness.cpu_total_ms": evt.cpuTotalMs } : {},
1297
+ ...evt.cpuCoreRatio !== void 0 ? { "openclaw.liveness.cpu_core_ratio": evt.cpuCoreRatio } : {}
1298
+ }, 0, { endTimeMs: evt.ts });
1299
+ span.setStatus({
1300
+ code: SpanStatusCode.ERROR,
1301
+ message: reason
1302
+ });
1303
+ span.end(evt.ts);
1304
+ };
1305
+ const recordTelemetryExporter = (evt, metadata) => {
1306
+ if (!metadata.trusted) return;
1307
+ telemetryExporterCounter.add(1, {
1308
+ "openclaw.exporter": lowCardinalityAttr(evt.exporter, "unknown"),
1309
+ "openclaw.signal": evt.signal,
1310
+ "openclaw.status": evt.status,
1311
+ ...evt.reason ? { "openclaw.reason": evt.reason } : {},
1312
+ ...evt.errorCategory ? { "openclaw.errorCategory": lowCardinalityAttr(evt.errorCategory, "other") } : {}
1313
+ });
1314
+ };
1315
+ const subscribe = ctx.internalDiagnostics?.onEvent;
1316
+ if (!subscribe) {
1317
+ ctx.logger.error("diagnostics-otel: internal diagnostics capability unavailable");
1318
+ return;
1319
+ }
1320
+ unsubscribe = subscribe((evt, metadata) => {
1321
+ try {
1322
+ switch (evt.type) {
1323
+ case "model.usage":
1324
+ recordModelUsage(evt, metadata);
1325
+ return;
1326
+ case "webhook.received":
1327
+ recordWebhookReceived(evt);
1328
+ return;
1329
+ case "webhook.processed":
1330
+ recordWebhookProcessed(evt);
1331
+ return;
1332
+ case "webhook.error":
1333
+ recordWebhookError(evt);
1334
+ return;
1335
+ case "message.queued":
1336
+ recordMessageQueued(evt);
1337
+ return;
1338
+ case "message.processed":
1339
+ recordMessageProcessed(evt);
1340
+ return;
1341
+ case "message.delivery.started":
1342
+ recordMessageDeliveryStarted(evt);
1343
+ return;
1344
+ case "message.delivery.completed":
1345
+ recordMessageDeliveryCompleted(evt);
1346
+ return;
1347
+ case "message.delivery.error":
1348
+ recordMessageDeliveryError(evt);
1349
+ return;
1350
+ case "queue.lane.enqueue":
1351
+ recordLaneEnqueue(evt);
1352
+ return;
1353
+ case "queue.lane.dequeue":
1354
+ recordLaneDequeue(evt);
1355
+ return;
1356
+ case "session.state":
1357
+ recordSessionState(evt);
1358
+ return;
1359
+ case "session.long_running":
1360
+ case "session.stalled": return;
1361
+ case "session.stuck":
1362
+ recordSessionStuck(evt);
1363
+ return;
1364
+ case "run.attempt":
1365
+ recordRunAttempt(evt);
1366
+ return;
1367
+ case "run.progress": return;
1368
+ case "diagnostic.heartbeat":
1369
+ recordHeartbeat(evt);
1370
+ return;
1371
+ case "diagnostic.liveness.warning":
1372
+ recordLivenessWarning(evt);
1373
+ return;
1374
+ case "run.started":
1375
+ recordRunStarted(evt, metadata);
1376
+ return;
1377
+ case "run.completed":
1378
+ recordRunCompleted(evt, metadata);
1379
+ return;
1380
+ case "harness.run.started":
1381
+ recordHarnessRunStarted(evt, metadata);
1382
+ return;
1383
+ case "harness.run.completed":
1384
+ recordHarnessRunCompleted(evt, metadata);
1385
+ return;
1386
+ case "harness.run.error":
1387
+ recordHarnessRunError(evt, metadata);
1388
+ return;
1389
+ case "context.assembled":
1390
+ recordContextAssembled(evt, metadata);
1391
+ return;
1392
+ case "model.call.started":
1393
+ recordModelCallStarted(evt, metadata);
1394
+ return;
1395
+ case "model.call.completed":
1396
+ recordModelCallCompleted(evt, metadata);
1397
+ return;
1398
+ case "model.call.error":
1399
+ recordModelCallError(evt, metadata);
1400
+ return;
1401
+ case "tool.execution.started":
1402
+ recordToolExecutionStarted(evt, metadata);
1403
+ return;
1404
+ case "tool.execution.completed":
1405
+ recordToolExecutionCompleted(evt, metadata);
1406
+ return;
1407
+ case "tool.execution.error":
1408
+ recordToolExecutionError(evt, metadata);
1409
+ return;
1410
+ case "tool.execution.blocked":
1411
+ recordToolExecutionBlocked(evt, metadata);
1412
+ return;
1413
+ case "exec.process.completed":
1414
+ recordExecProcessCompleted(evt);
1415
+ return;
1416
+ case "log.record":
1417
+ recordLogRecord?.(evt, metadata);
1418
+ return;
1419
+ case "tool.loop":
1420
+ recordToolLoop(evt);
1421
+ return;
1422
+ case "diagnostic.memory.sample":
1423
+ recordMemorySample(evt);
1424
+ return;
1425
+ case "diagnostic.memory.pressure":
1426
+ recordMemoryPressure(evt);
1427
+ return;
1428
+ case "telemetry.exporter":
1429
+ recordTelemetryExporter(evt, metadata);
1430
+ return;
1431
+ case "payload.large": return;
1432
+ }
1433
+ } catch (err) {
1434
+ ctx.logger.error(`diagnostics-otel: event handler failed (${evt.type}): ${formatError(err)}`);
1435
+ }
1436
+ });
1437
+ emitForSignals(enabledSignals, {
1438
+ exporter: "diagnostics-otel",
1439
+ status: "started",
1440
+ reason: "configured"
1441
+ });
1442
+ if (logsEnabled) ctx.logger.info("diagnostics-otel: logs exporter enabled (OTLP/Protobuf)");
1443
+ },
1444
+ async stop() {
1445
+ await stopStarted();
1446
+ }
1447
+ };
1448
+ }
1449
+ //#endregion
1450
+ //#region extensions/diagnostics-otel/index.ts
1451
+ var diagnostics_otel_default = definePluginEntry({
1452
+ id: "diagnostics-otel",
1453
+ name: "Diagnostics OpenTelemetry",
1454
+ description: "Export diagnostics events to OpenTelemetry",
1455
+ register(api) {
1456
+ api.registerService(createDiagnosticsOtelService());
1457
+ }
1458
+ });
1459
+ //#endregion
1460
+ export { diagnostics_otel_default as default };