@openclaw/diagnostics-otel 2026.5.24-beta.1 → 2026.5.25-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 +344 -19
- package/npm-shrinkwrap.json +2 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isValidDiagnosticSpanId, isValidDiagnosticTraceFlags, isValidDiagnosticTraceId, redactSensitiveText } from "./api.js";
|
|
2
2
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
-
import { SpanStatusCode, TraceFlags, context, metrics, trace } from "@opentelemetry/api";
|
|
3
|
+
import { SpanKind, SpanStatusCode, TraceFlags, context, metrics, trace } from "@opentelemetry/api";
|
|
4
4
|
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
|
|
5
5
|
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
|
|
6
6
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
@@ -10,6 +10,7 @@ import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
|
10
10
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
11
11
|
import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
|
|
12
12
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
13
|
+
import { ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, ATTR_GEN_AI_TOOL_DEFINITIONS } from "@opentelemetry/semantic-conventions/incubating";
|
|
13
14
|
import { registerUnhandledRejectionHandler } from "openclaw/plugin-sdk/runtime-env";
|
|
14
15
|
//#region extensions/diagnostics-otel/src/service.ts
|
|
15
16
|
const DEFAULT_SERVICE_NAME = "openclaw";
|
|
@@ -36,8 +37,8 @@ const DROPPED_OTEL_ATTRIBUTE_KEYS = new Set([
|
|
|
36
37
|
"openclaw.trace_id"
|
|
37
38
|
]);
|
|
38
39
|
const LOW_CARDINALITY_VALUE_RE = /^[A-Za-z0-9_.:-]{1,120}$/u;
|
|
39
|
-
const MAX_OTEL_CONTENT_ATTRIBUTE_CHARS =
|
|
40
|
-
const MAX_OTEL_CONTENT_ARRAY_ITEMS =
|
|
40
|
+
const MAX_OTEL_CONTENT_ATTRIBUTE_CHARS = 128 * 1024;
|
|
41
|
+
const MAX_OTEL_CONTENT_ARRAY_ITEMS = 200;
|
|
41
42
|
const MAX_OTEL_LOG_BODY_CHARS = 4 * 1024;
|
|
42
43
|
const MAX_OTEL_LOG_ATTRIBUTE_COUNT = 64;
|
|
43
44
|
const MAX_OTEL_LOG_ATTRIBUTE_VALUE_CHARS = 4 * 1024;
|
|
@@ -94,6 +95,7 @@ const NO_CONTENT_CAPTURE = {
|
|
|
94
95
|
toolInputs: false,
|
|
95
96
|
toolOutputs: false,
|
|
96
97
|
systemPrompt: false,
|
|
98
|
+
toolDefinitions: false,
|
|
97
99
|
logBodies: false
|
|
98
100
|
};
|
|
99
101
|
function normalizeEndpoint(endpoint) {
|
|
@@ -104,6 +106,11 @@ function resolveOtelUrl(endpoint, path) {
|
|
|
104
106
|
if (!endpoint) return;
|
|
105
107
|
const endpointWithoutQueryOrFragment = endpoint.split(/[?#]/, 1)[0] ?? endpoint;
|
|
106
108
|
if (/\/v1\/(?:traces|metrics|logs)$/i.test(endpointWithoutQueryOrFragment)) return endpoint;
|
|
109
|
+
if (/[?#]/u.test(endpoint)) try {
|
|
110
|
+
const url = new URL(endpoint);
|
|
111
|
+
url.pathname = `${url.pathname.replace(/\/+$/u, "")}/${path}`;
|
|
112
|
+
return url.toString();
|
|
113
|
+
} catch {}
|
|
107
114
|
return `${endpoint}/${path}`;
|
|
108
115
|
}
|
|
109
116
|
function resolveSignalOtelUrl(params) {
|
|
@@ -231,6 +238,13 @@ function assignGenAiSpanIdentityAttrs(attrs, input) {
|
|
|
231
238
|
function assignGenAiModelCallAttrs(attrs, evt) {
|
|
232
239
|
assignGenAiSpanIdentityAttrs(attrs, evt);
|
|
233
240
|
}
|
|
241
|
+
function modelCallSpanName(evt) {
|
|
242
|
+
if (!emitLatestGenAiSemconv()) return "openclaw.model.call";
|
|
243
|
+
return `${genAiOperationName(evt.api)} ${lowCardinalityAttr(evt.model)}`;
|
|
244
|
+
}
|
|
245
|
+
function modelCallSpanKind() {
|
|
246
|
+
return emitLatestGenAiSemconv() ? SpanKind.CLIENT : void 0;
|
|
247
|
+
}
|
|
234
248
|
function addUpstreamRequestIdSpanEvent(span, upstreamRequestIdHash) {
|
|
235
249
|
if (!upstreamRequestIdHash) return;
|
|
236
250
|
const boundedHash = lowCardinalityAttr(upstreamRequestIdHash);
|
|
@@ -250,6 +264,7 @@ function resolveContentCapturePolicy(value) {
|
|
|
250
264
|
toolInputs: true,
|
|
251
265
|
toolOutputs: true,
|
|
252
266
|
systemPrompt: false,
|
|
267
|
+
toolDefinitions: true,
|
|
253
268
|
logBodies: true
|
|
254
269
|
};
|
|
255
270
|
if (!value || typeof value !== "object" || Array.isArray(value)) return NO_CONTENT_CAPTURE;
|
|
@@ -261,6 +276,7 @@ function resolveContentCapturePolicy(value) {
|
|
|
261
276
|
toolInputs: config.toolInputs === true,
|
|
262
277
|
toolOutputs: config.toolOutputs === true,
|
|
263
278
|
systemPrompt: config.systemPrompt === true,
|
|
279
|
+
toolDefinitions: config.toolDefinitions === true,
|
|
264
280
|
logBodies: false
|
|
265
281
|
};
|
|
266
282
|
}
|
|
@@ -274,15 +290,276 @@ function normalizeOtelContentValue(value) {
|
|
|
274
290
|
for (const item of value.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) if (typeof item === "string") items.push(item);
|
|
275
291
|
if (items.length > 0) return normalizeOtelLogString(items.join("\n"), MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
|
|
276
292
|
}
|
|
293
|
+
const json = safeJsonString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
|
|
294
|
+
if (json) return json;
|
|
295
|
+
}
|
|
296
|
+
const TRUNCATED_JSON_TEXT_SUFFIX = "...(truncated)";
|
|
297
|
+
const JSON_TRUNCATION_STRING_BUDGETS = [
|
|
298
|
+
8192,
|
|
299
|
+
4096,
|
|
300
|
+
2048,
|
|
301
|
+
1024,
|
|
302
|
+
512,
|
|
303
|
+
256,
|
|
304
|
+
128,
|
|
305
|
+
64,
|
|
306
|
+
32
|
|
307
|
+
];
|
|
308
|
+
const JSON_TRUNCATION_ARRAY_ITEM_BUDGETS = [
|
|
309
|
+
MAX_OTEL_CONTENT_ARRAY_ITEMS,
|
|
310
|
+
100,
|
|
311
|
+
50,
|
|
312
|
+
25,
|
|
313
|
+
10,
|
|
314
|
+
5,
|
|
315
|
+
1
|
|
316
|
+
];
|
|
317
|
+
const JSON_TRUNCATION_MAX_OBJECT_FIELDS = 64;
|
|
318
|
+
const JSON_TRUNCATION_MAX_DEPTH = 8;
|
|
319
|
+
function safeJsonString(value, maxChars) {
|
|
320
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") return;
|
|
321
|
+
const exact = stringifyJsonForOtelAttribute(value);
|
|
322
|
+
if (exact && exact.length <= maxChars) return exact;
|
|
323
|
+
for (const maxArrayItems of JSON_TRUNCATION_ARRAY_ITEM_BUDGETS) for (const maxStringChars of JSON_TRUNCATION_STRING_BUDGETS) {
|
|
324
|
+
const json = stringifyJsonForOtelAttribute(truncateJsonValueForOtelAttribute(value, {
|
|
325
|
+
maxArrayItems,
|
|
326
|
+
maxDepth: JSON_TRUNCATION_MAX_DEPTH,
|
|
327
|
+
maxObjectFields: JSON_TRUNCATION_MAX_OBJECT_FIELDS,
|
|
328
|
+
maxStringChars,
|
|
329
|
+
seen: /* @__PURE__ */ new WeakSet()
|
|
330
|
+
}));
|
|
331
|
+
if (json && json.length <= maxChars) return json;
|
|
332
|
+
}
|
|
333
|
+
const summary = stringifyJsonForOtelAttribute({
|
|
334
|
+
truncated: true,
|
|
335
|
+
reason: exact ? "max_attribute_size" : "unserializable_value",
|
|
336
|
+
type: describeJsonValue(value)
|
|
337
|
+
});
|
|
338
|
+
return summary && summary.length <= maxChars ? summary : void 0;
|
|
339
|
+
}
|
|
340
|
+
function stringifyJsonForOtelAttribute(value) {
|
|
341
|
+
try {
|
|
342
|
+
const json = JSON.stringify(value);
|
|
343
|
+
if (!json) return;
|
|
344
|
+
return redactSensitiveText(json);
|
|
345
|
+
} catch {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function truncateJsonValueForOtelAttribute(value, options) {
|
|
350
|
+
if (typeof value === "string") return truncateJsonTextForOtelAttribute(value, options.maxStringChars);
|
|
351
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
352
|
+
if (typeof value === "bigint") return truncateJsonTextForOtelAttribute(String(value), options.maxStringChars);
|
|
353
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") return;
|
|
354
|
+
if (options.maxDepth <= 0) return {
|
|
355
|
+
truncated: true,
|
|
356
|
+
reason: "max_depth"
|
|
357
|
+
};
|
|
358
|
+
if (Array.isArray(value)) return truncateJsonArrayForOtelAttribute(value, options);
|
|
359
|
+
if (typeof value === "object") return truncateJsonObjectForOtelAttribute(value, options);
|
|
360
|
+
}
|
|
361
|
+
function truncateJsonArrayForOtelAttribute(value, options) {
|
|
362
|
+
if (options.seen.has(value)) return [{
|
|
363
|
+
truncated: true,
|
|
364
|
+
reason: "circular_reference"
|
|
365
|
+
}];
|
|
366
|
+
options.seen.add(value);
|
|
367
|
+
const nextOptions = {
|
|
368
|
+
...options,
|
|
369
|
+
maxDepth: options.maxDepth - 1
|
|
370
|
+
};
|
|
371
|
+
const items = value.slice(0, options.maxArrayItems).map((item) => truncateJsonValueForOtelAttribute(item, nextOptions));
|
|
372
|
+
if (value.length > items.length) items.push({
|
|
373
|
+
truncated: true,
|
|
374
|
+
omittedItems: value.length - items.length
|
|
375
|
+
});
|
|
376
|
+
options.seen.delete(value);
|
|
377
|
+
return items;
|
|
378
|
+
}
|
|
379
|
+
function truncateJsonObjectForOtelAttribute(value, options) {
|
|
380
|
+
if (options.seen.has(value)) return {
|
|
381
|
+
truncated: true,
|
|
382
|
+
reason: "circular_reference"
|
|
383
|
+
};
|
|
384
|
+
options.seen.add(value);
|
|
385
|
+
const nextOptions = {
|
|
386
|
+
...options,
|
|
387
|
+
maxDepth: options.maxDepth - 1
|
|
388
|
+
};
|
|
389
|
+
const result = {};
|
|
390
|
+
const entries = Object.entries(value).filter(([, field]) => field !== void 0 && typeof field !== "function" && typeof field !== "symbol");
|
|
391
|
+
for (const [key, field] of entries.slice(0, options.maxObjectFields)) result[key] = truncateJsonValueForOtelAttribute(field, nextOptions);
|
|
392
|
+
if (entries.length > options.maxObjectFields) {
|
|
393
|
+
result.truncated = true;
|
|
394
|
+
result.omittedFields = entries.length - options.maxObjectFields;
|
|
395
|
+
}
|
|
396
|
+
options.seen.delete(value);
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
function truncateJsonTextForOtelAttribute(value, maxChars) {
|
|
400
|
+
const redacted = redactSensitiveText(value);
|
|
401
|
+
if (redacted.length <= maxChars) return redacted;
|
|
402
|
+
const suffixBudget = Math.min(14, maxChars);
|
|
403
|
+
const prefixBudget = Math.max(0, maxChars - suffixBudget);
|
|
404
|
+
return `${redacted.slice(0, prefixBudget)}${TRUNCATED_JSON_TEXT_SUFFIX.slice(14 - suffixBudget)}`;
|
|
405
|
+
}
|
|
406
|
+
function describeJsonValue(value) {
|
|
407
|
+
if (Array.isArray(value)) return "array";
|
|
408
|
+
if (value === null) return "null";
|
|
409
|
+
return typeof value;
|
|
410
|
+
}
|
|
411
|
+
function isRecord(value) {
|
|
412
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
413
|
+
}
|
|
414
|
+
function textPart(content) {
|
|
415
|
+
return {
|
|
416
|
+
type: "text",
|
|
417
|
+
content
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function toolCallResponsePart(part) {
|
|
421
|
+
return {
|
|
422
|
+
type: "tool_call_response",
|
|
423
|
+
...typeof part.id === "string" ? { id: part.id } : {},
|
|
424
|
+
result: part.result ?? part.response ?? part.content ?? part.details ?? ""
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function contentParts(value) {
|
|
428
|
+
if (typeof value === "string") return value.length > 0 ? [textPart(value)] : [];
|
|
429
|
+
if (!Array.isArray(value)) {
|
|
430
|
+
if (value === void 0 || value === null) return [];
|
|
431
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return [textPart(String(value))];
|
|
432
|
+
const json = safeJsonString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
|
|
433
|
+
return json ? [textPart(json)] : [];
|
|
434
|
+
}
|
|
435
|
+
const parts = [];
|
|
436
|
+
for (const part of value) {
|
|
437
|
+
if (typeof part === "string") {
|
|
438
|
+
if (part.length > 0) parts.push(textPart(part));
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (!isRecord(part)) continue;
|
|
442
|
+
if (part.type === "text" && typeof part.text === "string") parts.push(textPart(part.text));
|
|
443
|
+
else if (part.type === "text" && typeof part.content === "string") parts.push(textPart(part.content));
|
|
444
|
+
else if (part.type === "thinking" && typeof part.thinking === "string") parts.push({
|
|
445
|
+
type: "reasoning",
|
|
446
|
+
content: part.thinking
|
|
447
|
+
});
|
|
448
|
+
else if (part.type === "toolCall" && typeof part.name === "string") parts.push({
|
|
449
|
+
type: "tool_call",
|
|
450
|
+
name: part.name,
|
|
451
|
+
...typeof part.id === "string" ? { id: part.id } : {},
|
|
452
|
+
...part.arguments !== void 0 ? { arguments: part.arguments } : {}
|
|
453
|
+
});
|
|
454
|
+
else if (part.type === "tool_call" && typeof part.name === "string") parts.push({
|
|
455
|
+
type: "tool_call",
|
|
456
|
+
name: part.name,
|
|
457
|
+
...typeof part.id === "string" ? { id: part.id } : {},
|
|
458
|
+
...part.arguments !== void 0 ? { arguments: part.arguments } : {}
|
|
459
|
+
});
|
|
460
|
+
else if (part.type === "tool_call_response") parts.push(toolCallResponsePart(part));
|
|
461
|
+
else if (part.type === "image") {
|
|
462
|
+
const data = typeof part.data === "string" ? part.data : void 0;
|
|
463
|
+
parts.push({
|
|
464
|
+
type: "blob",
|
|
465
|
+
modality: "image",
|
|
466
|
+
...typeof part.mimeType === "string" ? { mime_type: part.mimeType } : {},
|
|
467
|
+
...typeof part.mime_type === "string" ? { mime_type: part.mime_type } : {},
|
|
468
|
+
...data ? { content: data } : {}
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return parts;
|
|
473
|
+
}
|
|
474
|
+
function normalizeGenAiMessage(value, fallbackRole = "user") {
|
|
475
|
+
if (typeof value === "string") return {
|
|
476
|
+
role: fallbackRole,
|
|
477
|
+
parts: [textPart(value)]
|
|
478
|
+
};
|
|
479
|
+
if (!isRecord(value)) return;
|
|
480
|
+
const rawRole = typeof value.role === "string" ? value.role : fallbackRole;
|
|
481
|
+
const role = rawRole === "toolResult" ? "tool" : rawRole;
|
|
482
|
+
let parts;
|
|
483
|
+
if (role === "tool") {
|
|
484
|
+
const explicitParts = contentParts(value.parts);
|
|
485
|
+
parts = explicitParts.length > 0 ? explicitParts : [toolCallResponsePart({
|
|
486
|
+
id: value.toolCallId,
|
|
487
|
+
result: value.content ?? value.details ?? ""
|
|
488
|
+
})];
|
|
489
|
+
} else parts = contentParts(value.parts ?? value.content);
|
|
490
|
+
if (parts.length === 0) return;
|
|
491
|
+
return {
|
|
492
|
+
role,
|
|
493
|
+
parts,
|
|
494
|
+
...typeof value.name === "string" ? { name: value.name } : {},
|
|
495
|
+
...typeof value.finish_reason === "string" ? { finish_reason: value.finish_reason } : {},
|
|
496
|
+
...typeof value.stopReason === "string" ? { finish_reason: value.stopReason } : {}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function normalizeGenAiMessages(value, fallbackRole) {
|
|
500
|
+
const source = Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
501
|
+
const messages = [];
|
|
502
|
+
for (const item of source.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) {
|
|
503
|
+
const message = normalizeGenAiMessage(item, fallbackRole);
|
|
504
|
+
if (message) messages.push(message);
|
|
505
|
+
}
|
|
506
|
+
return messages;
|
|
507
|
+
}
|
|
508
|
+
function normalizeGenAiToolDefinition(value) {
|
|
509
|
+
if (!isRecord(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
|
|
510
|
+
return {
|
|
511
|
+
type: typeof value.type === "string" ? value.type : "function",
|
|
512
|
+
name: value.name,
|
|
513
|
+
...typeof value.description === "string" ? { description: value.description } : {},
|
|
514
|
+
...value.parameters !== void 0 ? { parameters: value.parameters } : {}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function normalizeGenAiToolDefinitions(value) {
|
|
518
|
+
if (!Array.isArray(value)) return [];
|
|
519
|
+
const definitions = [];
|
|
520
|
+
for (const item of value.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) {
|
|
521
|
+
const definition = normalizeGenAiToolDefinition(item);
|
|
522
|
+
if (definition) definitions.push(definition);
|
|
523
|
+
}
|
|
524
|
+
return definitions;
|
|
525
|
+
}
|
|
526
|
+
function assignJsonAttribute(attributes, key, value) {
|
|
527
|
+
const json = safeJsonString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
|
|
528
|
+
if (json) attributes[key] = json;
|
|
529
|
+
}
|
|
530
|
+
function assignGenAiModelContentAttributes(attributes, content, policy) {
|
|
531
|
+
if (policy.systemPrompt && typeof content?.systemPrompt === "string") assignJsonAttribute(attributes, ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, [textPart(content.systemPrompt)]);
|
|
532
|
+
if (policy.inputMessages) {
|
|
533
|
+
const inputMessages = normalizeGenAiMessages(content?.inputMessages, "user");
|
|
534
|
+
if (inputMessages.length > 0) {
|
|
535
|
+
assignJsonAttribute(attributes, ATTR_GEN_AI_INPUT_MESSAGES, inputMessages);
|
|
536
|
+
assignJsonAttribute(attributes, "input.value", inputMessages);
|
|
537
|
+
attributes["input.mime_type"] = "application/json";
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (policy.toolDefinitions) {
|
|
541
|
+
const toolDefinitions = normalizeGenAiToolDefinitions(content?.toolDefinitions);
|
|
542
|
+
if (toolDefinitions.length > 0) assignJsonAttribute(attributes, ATTR_GEN_AI_TOOL_DEFINITIONS, toolDefinitions);
|
|
543
|
+
}
|
|
544
|
+
if (policy.outputMessages) {
|
|
545
|
+
const outputMessages = normalizeGenAiMessages(content?.outputMessages, "assistant");
|
|
546
|
+
if (outputMessages.length > 0) {
|
|
547
|
+
assignJsonAttribute(attributes, ATTR_GEN_AI_OUTPUT_MESSAGES, outputMessages);
|
|
548
|
+
assignJsonAttribute(attributes, "output.value", outputMessages);
|
|
549
|
+
attributes["output.mime_type"] = "application/json";
|
|
550
|
+
}
|
|
551
|
+
}
|
|
277
552
|
}
|
|
278
553
|
function assignOtelContentAttribute(attributes, key, value) {
|
|
279
554
|
const normalized = normalizeOtelContentValue(value);
|
|
280
555
|
if (normalized) attributes[key] = normalized;
|
|
281
556
|
}
|
|
282
|
-
function assignOtelModelContentAttributes(attributes,
|
|
283
|
-
|
|
284
|
-
if (policy.
|
|
285
|
-
if (policy.
|
|
557
|
+
function assignOtelModelContentAttributes(attributes, content, policy) {
|
|
558
|
+
assignGenAiModelContentAttributes(attributes, content, policy);
|
|
559
|
+
if (policy.inputMessages) assignOtelContentAttribute(attributes, "openclaw.content.input_messages", content?.inputMessages);
|
|
560
|
+
if (policy.toolDefinitions) assignOtelContentAttribute(attributes, "openclaw.content.tool_definitions", content?.toolDefinitions);
|
|
561
|
+
if (policy.outputMessages) assignOtelContentAttribute(attributes, "openclaw.content.output_messages", content?.outputMessages);
|
|
562
|
+
if (policy.systemPrompt) assignOtelContentAttribute(attributes, "openclaw.content.system_prompt", content?.systemPrompt);
|
|
286
563
|
}
|
|
287
564
|
function assignOtelToolContentAttributes(attributes, event, policy) {
|
|
288
565
|
if (policy.toolInputs) assignOtelContentAttribute(attributes, "openclaw.content.tool_input", event.toolInput);
|
|
@@ -382,7 +659,7 @@ function createDiagnosticsOtelService() {
|
|
|
382
659
|
await stopStarted();
|
|
383
660
|
const cfg = ctx.config.diagnostics;
|
|
384
661
|
const otel = cfg?.otel;
|
|
385
|
-
if (!cfg
|
|
662
|
+
if (!cfg || cfg.enabled === false || !otel?.enabled) return;
|
|
386
663
|
const emitExporterEvent = (event) => {
|
|
387
664
|
try {
|
|
388
665
|
ctx.internalDiagnostics?.emit({
|
|
@@ -657,10 +934,18 @@ function createDiagnosticsOtelService() {
|
|
|
657
934
|
unit: "ms",
|
|
658
935
|
description: "Elapsed time before the first streamed model response event"
|
|
659
936
|
});
|
|
937
|
+
const modelFailoverCounter = meter.createCounter("openclaw.model.failover", {
|
|
938
|
+
unit: "1",
|
|
939
|
+
description: "Model failovers by source, destination, lane, and reason"
|
|
940
|
+
});
|
|
660
941
|
const toolExecutionDurationHistogram = meter.createHistogram("openclaw.tool.execution.duration_ms", {
|
|
661
942
|
unit: "ms",
|
|
662
943
|
description: "Tool execution duration"
|
|
663
944
|
});
|
|
945
|
+
const toolExecutionBlockedCounter = meter.createCounter("openclaw.tool.execution.blocked", {
|
|
946
|
+
unit: "1",
|
|
947
|
+
description: "Tool executions blocked by policy or sandbox diagnostics"
|
|
948
|
+
});
|
|
664
949
|
const execProcessDurationHistogram = meter.createHistogram("openclaw.exec.duration_ms", {
|
|
665
950
|
unit: "ms",
|
|
666
951
|
description: "Exec process duration"
|
|
@@ -693,6 +978,14 @@ function createDiagnosticsOtelService() {
|
|
|
693
978
|
unit: "1",
|
|
694
979
|
description: "Async diagnostic queue drops by dropped event class"
|
|
695
980
|
});
|
|
981
|
+
const payloadLargeCounter = meter.createCounter("openclaw.payload.large", {
|
|
982
|
+
unit: "1",
|
|
983
|
+
description: "Oversized payload diagnostics by surface and action"
|
|
984
|
+
});
|
|
985
|
+
const payloadLargeBytesHistogram = meter.createHistogram("openclaw.payload.large_bytes", {
|
|
986
|
+
unit: "By",
|
|
987
|
+
description: "Oversized payload byte sizes by surface and action"
|
|
988
|
+
});
|
|
696
989
|
const livenessWarningCounter = meter.createCounter("openclaw.liveness.warning", {
|
|
697
990
|
unit: "1",
|
|
698
991
|
description: "Diagnostic liveness warning events"
|
|
@@ -773,6 +1066,7 @@ function createDiagnosticsOtelService() {
|
|
|
773
1066
|
const parentContext = "parentContext" in options ? options.parentContext ?? void 0 : void 0;
|
|
774
1067
|
return tracer.startSpan(name, {
|
|
775
1068
|
attributes: redactOtelAttributes(attributes),
|
|
1069
|
+
...options.kind !== void 0 ? { kind: options.kind } : {},
|
|
776
1070
|
...startTime !== void 0 ? { startTime } : {}
|
|
777
1071
|
}, parentContext);
|
|
778
1072
|
};
|
|
@@ -1277,6 +1571,16 @@ function createDiagnosticsOtelService() {
|
|
|
1277
1571
|
}).end(evt.ts);
|
|
1278
1572
|
};
|
|
1279
1573
|
const recordModelFailover = (evt, metadata) => {
|
|
1574
|
+
const metricAttrs = {
|
|
1575
|
+
"openclaw.failover.reason": lowCardinalityAttr(evt.reason, "unknown"),
|
|
1576
|
+
"openclaw.failover.suspended": evt.suspended === void 0 ? "unknown" : String(evt.suspended),
|
|
1577
|
+
"openclaw.lane": lowCardinalityQueueLaneAttr(evt.lane, "unknown"),
|
|
1578
|
+
"openclaw.model": lowCardinalityAttr(evt.fromModel),
|
|
1579
|
+
"openclaw.provider": lowCardinalityAttr(evt.fromProvider),
|
|
1580
|
+
"openclaw.failover.to_model": lowCardinalityAttr(evt.toModel),
|
|
1581
|
+
"openclaw.failover.to_provider": lowCardinalityAttr(evt.toProvider)
|
|
1582
|
+
};
|
|
1583
|
+
modelFailoverCounter.add(1, metricAttrs);
|
|
1280
1584
|
if (!tracesEnabled) return;
|
|
1281
1585
|
const spanAttrs = { "openclaw.failover.reason": lowCardinalityAttr(evt.reason, "unknown") };
|
|
1282
1586
|
if (evt.fromProvider) spanAttrs["openclaw.provider"] = evt.fromProvider;
|
|
@@ -1320,12 +1624,13 @@ function createDiagnosticsOtelService() {
|
|
|
1320
1624
|
assignGenAiModelCallAttrs(spanAttrs, evt);
|
|
1321
1625
|
if (evt.api) spanAttrs["openclaw.api"] = evt.api;
|
|
1322
1626
|
if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
|
|
1323
|
-
trackTrustedSpan(evt, metadata, spanWithDuration(
|
|
1627
|
+
trackTrustedSpan(evt, metadata, spanWithDuration(modelCallSpanName(evt), spanAttrs, void 0, {
|
|
1628
|
+
kind: modelCallSpanKind(),
|
|
1324
1629
|
parentContext: activeTrustedParentContext(evt, metadata),
|
|
1325
1630
|
startTimeMs: evt.ts
|
|
1326
1631
|
}));
|
|
1327
1632
|
};
|
|
1328
|
-
const recordModelCallCompleted = (evt, metadata) => {
|
|
1633
|
+
const recordModelCallCompleted = (evt, metadata, modelContent) => {
|
|
1329
1634
|
const metricAttrs = modelCallMetricAttrs(evt);
|
|
1330
1635
|
modelCallDurationHistogram.record(evt.durationMs, metricAttrs);
|
|
1331
1636
|
recordModelCallSizeTimingMetrics(evt, metricAttrs);
|
|
@@ -1339,8 +1644,9 @@ function createDiagnosticsOtelService() {
|
|
|
1339
1644
|
if (evt.api) spanAttrs["openclaw.api"] = evt.api;
|
|
1340
1645
|
if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
|
|
1341
1646
|
assignModelCallSizeTimingAttrs(spanAttrs, evt);
|
|
1342
|
-
assignOtelModelContentAttributes(spanAttrs,
|
|
1343
|
-
const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration(
|
|
1647
|
+
assignOtelModelContentAttributes(spanAttrs, modelContent, contentCapturePolicy);
|
|
1648
|
+
const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration(modelCallSpanName(evt), spanAttrs, evt.durationMs, {
|
|
1649
|
+
kind: modelCallSpanKind(),
|
|
1344
1650
|
parentContext: activeTrustedParentContext(evt, metadata),
|
|
1345
1651
|
endTimeMs: evt.ts
|
|
1346
1652
|
});
|
|
@@ -1348,7 +1654,7 @@ function createDiagnosticsOtelService() {
|
|
|
1348
1654
|
addUpstreamRequestIdSpanEvent(span, evt.upstreamRequestIdHash);
|
|
1349
1655
|
span.end(evt.ts);
|
|
1350
1656
|
};
|
|
1351
|
-
const recordModelCallError = (evt, metadata) => {
|
|
1657
|
+
const recordModelCallError = (evt, metadata, modelContent) => {
|
|
1352
1658
|
const errorType = lowCardinalityAttr(evt.errorCategory, "other");
|
|
1353
1659
|
const metricAttrs = {
|
|
1354
1660
|
...modelCallMetricAttrs(evt),
|
|
@@ -1370,8 +1676,9 @@ function createDiagnosticsOtelService() {
|
|
|
1370
1676
|
if (evt.api) spanAttrs["openclaw.api"] = evt.api;
|
|
1371
1677
|
if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
|
|
1372
1678
|
assignModelCallSizeTimingAttrs(spanAttrs, evt);
|
|
1373
|
-
assignOtelModelContentAttributes(spanAttrs,
|
|
1374
|
-
const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration(
|
|
1679
|
+
assignOtelModelContentAttributes(spanAttrs, modelContent, contentCapturePolicy);
|
|
1680
|
+
const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration(modelCallSpanName(evt), spanAttrs, evt.durationMs, {
|
|
1681
|
+
kind: modelCallSpanKind(),
|
|
1375
1682
|
parentContext: activeTrustedParentContext(evt, metadata),
|
|
1376
1683
|
endTimeMs: evt.ts
|
|
1377
1684
|
});
|
|
@@ -1458,6 +1765,10 @@ function createDiagnosticsOtelService() {
|
|
|
1458
1765
|
span.end(evt.ts);
|
|
1459
1766
|
};
|
|
1460
1767
|
const recordToolExecutionBlocked = (evt, metadata) => {
|
|
1768
|
+
toolExecutionBlockedCounter.add(1, {
|
|
1769
|
+
...toolExecutionBaseAttrs(evt),
|
|
1770
|
+
"openclaw.deniedReason": lowCardinalityAttr(evt.deniedReason, "other")
|
|
1771
|
+
});
|
|
1461
1772
|
if (!tracesEnabled) return;
|
|
1462
1773
|
const spanAttrs = {
|
|
1463
1774
|
...toolExecutionBaseAttrs(evt),
|
|
@@ -1472,6 +1783,18 @@ function createDiagnosticsOtelService() {
|
|
|
1472
1783
|
setSpanAttrs(span, spanAttrs);
|
|
1473
1784
|
span.end(evt.ts);
|
|
1474
1785
|
};
|
|
1786
|
+
const recordPayloadLarge = (evt) => {
|
|
1787
|
+
const attrs = {
|
|
1788
|
+
"openclaw.payload.action": evt.action,
|
|
1789
|
+
"openclaw.payload.surface": lowCardinalityAttr(evt.surface, "unknown"),
|
|
1790
|
+
"openclaw.channel": lowCardinalityAttr(evt.channel, "none"),
|
|
1791
|
+
"openclaw.plugin": lowCardinalityAttr(evt.pluginId, "none"),
|
|
1792
|
+
"openclaw.reason": lowCardinalityAttr(evt.reason, "none")
|
|
1793
|
+
};
|
|
1794
|
+
payloadLargeCounter.add(1, attrs);
|
|
1795
|
+
const bytes = positiveFiniteNumber(evt.bytes);
|
|
1796
|
+
if (bytes !== void 0) payloadLargeBytesHistogram.record(bytes, attrs);
|
|
1797
|
+
};
|
|
1475
1798
|
const recordExecProcessCompleted = (evt) => {
|
|
1476
1799
|
const attrs = {
|
|
1477
1800
|
"openclaw.exec.target": evt.target,
|
|
@@ -1555,7 +1878,7 @@ function createDiagnosticsOtelService() {
|
|
|
1555
1878
|
ctx.logger.error("diagnostics-otel: internal diagnostics capability unavailable");
|
|
1556
1879
|
return;
|
|
1557
1880
|
}
|
|
1558
|
-
unsubscribe = subscribe((evt, metadata) => {
|
|
1881
|
+
unsubscribe = subscribe((evt, metadata, privateData) => {
|
|
1559
1882
|
try {
|
|
1560
1883
|
switch (evt.type) {
|
|
1561
1884
|
case "model.usage":
|
|
@@ -1655,10 +1978,10 @@ function createDiagnosticsOtelService() {
|
|
|
1655
1978
|
recordModelCallStarted(evt, metadata);
|
|
1656
1979
|
return;
|
|
1657
1980
|
case "model.call.completed":
|
|
1658
|
-
recordModelCallCompleted(evt, metadata);
|
|
1981
|
+
recordModelCallCompleted(evt, metadata, privateData.modelContent);
|
|
1659
1982
|
return;
|
|
1660
1983
|
case "model.call.error":
|
|
1661
|
-
recordModelCallError(evt, metadata);
|
|
1984
|
+
recordModelCallError(evt, metadata, privateData.modelContent);
|
|
1662
1985
|
return;
|
|
1663
1986
|
case "tool.execution.started":
|
|
1664
1987
|
recordToolExecutionStarted(evt, metadata);
|
|
@@ -1696,7 +2019,9 @@ function createDiagnosticsOtelService() {
|
|
|
1696
2019
|
case "telemetry.exporter":
|
|
1697
2020
|
recordTelemetryExporter(evt, metadata);
|
|
1698
2021
|
return;
|
|
1699
|
-
case "payload.large":
|
|
2022
|
+
case "payload.large":
|
|
2023
|
+
recordPayloadLarge(evt);
|
|
2024
|
+
return;
|
|
1700
2025
|
case "model.failover":
|
|
1701
2026
|
recordModelFailover(evt, metadata);
|
|
1702
2027
|
return;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/diagnostics-otel",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.25-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/diagnostics-otel",
|
|
9
|
-
"version": "2026.5.
|
|
9
|
+
"version": "2026.5.25-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@opentelemetry/api": "1.9.1",
|
|
12
12
|
"@opentelemetry/api-logs": "0.218.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/diagnostics-otel",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.25-beta.1",
|
|
4
4
|
"description": "OpenClaw diagnostics OpenTelemetry exporter",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"minHostVersion": ">=2026.4.25"
|
|
32
32
|
},
|
|
33
33
|
"compat": {
|
|
34
|
-
"pluginApi": ">=2026.5.
|
|
34
|
+
"pluginApi": ">=2026.5.25-beta.1"
|
|
35
35
|
},
|
|
36
36
|
"build": {
|
|
37
|
-
"openclawVersion": "2026.5.
|
|
37
|
+
"openclawVersion": "2026.5.25-beta.1"
|
|
38
38
|
},
|
|
39
39
|
"release": {
|
|
40
40
|
"publishToClawHub": true,
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"npm-shrinkwrap.json"
|
|
51
51
|
],
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"openclaw": ">=2026.5.
|
|
53
|
+
"openclaw": ">=2026.5.25-beta.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"openclaw": {
|