mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:30:43 +00:00
fix(diagnostics): gate traceparent propagation on trusted metadata
This commit is contained in:
@@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Diagnostics/OTEL: keep model-usage span GenAI provider attributes aligned with the existing semantic-convention opt-in policy, using legacy `gen_ai.system` unless latest experimental GenAI conventions are enabled. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: keep `gen_ai.request.model` present on GenAI token usage metrics with a bounded `unknown` fallback when model usage events do not include a model. Thanks @vincentkoc.
|
||||
- Docs/OTEL: document the GenAI token and model-call duration metrics, model-usage span attributes, and `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental` provider-attribute behavior. Thanks @vincentkoc.
|
||||
- Diagnostics/trace: add an internal traceparent propagation helper that only formats trusted dispatcher metadata, keeping plugin-emitted diagnostic traces out of outbound propagation by default. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: add bounded outbound message delivery lifecycle diagnostics and export them as low-cardinality delivery spans/metrics without message body, recipient, room, or media-path data. (#71471) Thanks @vincentkoc and @jlapenna.
|
||||
- Diagnostics/OTEL: emit bounded exec-process diagnostics and export them as `openclaw.exec` spans without exposing command text, working directories, or container identifiers. (#71451) Thanks @vincentkoc and @jlapenna.
|
||||
- Diagnostics/OTEL: support `OPENCLAW_OTEL_PRELOADED=1` so the plugin can reuse an already-registered OpenTelemetry SDK while keeping OpenClaw diagnostic listeners wired. (#71450) Thanks @vincentkoc and @jlapenna.
|
||||
|
||||
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
emitDiagnosticEvent,
|
||||
emitTrustedDiagnosticEvent,
|
||||
formatDiagnosticTraceparentForPropagation,
|
||||
isDiagnosticsEnabled,
|
||||
onInternalDiagnosticEvent,
|
||||
onDiagnosticEvent,
|
||||
@@ -147,6 +148,32 @@ describe("diagnostic-events", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("formats traceparent for propagation only from dispatcher-trusted metadata", () => {
|
||||
const trace = createDiagnosticTraceContext({
|
||||
traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
|
||||
spanId: "00f067aa0ba902b7",
|
||||
traceFlags: "01",
|
||||
});
|
||||
const traceparents: Array<string | undefined> = [];
|
||||
onInternalDiagnosticEvent((event, metadata) => {
|
||||
traceparents.push(formatDiagnosticTraceparentForPropagation(event, metadata));
|
||||
});
|
||||
|
||||
emitDiagnosticEvent({
|
||||
type: "message.queued",
|
||||
source: "plugin",
|
||||
trace,
|
||||
});
|
||||
emitTrustedDiagnosticEvent({
|
||||
type: "model.usage",
|
||||
usage: { total: 1 },
|
||||
trace,
|
||||
});
|
||||
|
||||
expect(traceparents).toEqual([undefined, `00-${trace.traceId}-${trace.spanId}-01`]);
|
||||
expect(formatDiagnosticTraceparentForPropagation({ trace }, { trusted: true })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("shares diagnostic state across duplicate module instances", async () => {
|
||||
const events: string[] = [];
|
||||
onDiagnosticEvent((event) => {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { DiagnosticTraceContext } from "./diagnostic-trace-context.js";
|
||||
import {
|
||||
formatDiagnosticTraceparent,
|
||||
type DiagnosticTraceContext,
|
||||
} from "./diagnostic-trace-context.js";
|
||||
import { isBlockedObjectKey } from "./prototype-keys.js";
|
||||
|
||||
export type DiagnosticSessionState = "idle" | "processing" | "waiting";
|
||||
@@ -413,6 +416,7 @@ type DiagnosticEventsGlobalState = {
|
||||
|
||||
const MAX_ASYNC_DIAGNOSTIC_EVENTS = 10_000;
|
||||
const DIAGNOSTIC_EVENTS_STATE_KEY = Symbol.for("openclaw.diagnosticEvents.state.v1");
|
||||
const dispatchedTrustedDiagnosticMetadata = new WeakSet<object>();
|
||||
const ASYNC_DIAGNOSTIC_EVENT_TYPES = new Set<DiagnosticEventPayload["type"]>([
|
||||
"tool.execution.started",
|
||||
"tool.execution.completed",
|
||||
@@ -500,7 +504,10 @@ function dispatchDiagnosticEvent(
|
||||
try {
|
||||
for (const listener of state.listeners) {
|
||||
try {
|
||||
listener(cloneDiagnosticEventForListener(enriched), Object.freeze({ ...metadata }));
|
||||
listener(
|
||||
cloneDiagnosticEventForListener(enriched),
|
||||
createDiagnosticMetadataForListener(metadata),
|
||||
);
|
||||
} catch (err) {
|
||||
const errorMessage =
|
||||
err instanceof Error
|
||||
@@ -519,6 +526,16 @@ function dispatchDiagnosticEvent(
|
||||
}
|
||||
}
|
||||
|
||||
function createDiagnosticMetadataForListener(
|
||||
metadata: DiagnosticEventMetadata,
|
||||
): DiagnosticEventMetadata {
|
||||
const listenerMetadata = Object.freeze({ ...metadata });
|
||||
if (listenerMetadata.trusted) {
|
||||
dispatchedTrustedDiagnosticMetadata.add(listenerMetadata);
|
||||
}
|
||||
return listenerMetadata;
|
||||
}
|
||||
|
||||
function cloneDiagnosticEventForListener(event: DiagnosticEventPayload): DiagnosticEventPayload {
|
||||
return deepFreezeDiagnosticValue(structuredClone(event)) as DiagnosticEventPayload;
|
||||
}
|
||||
@@ -623,6 +640,16 @@ export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => voi
|
||||
});
|
||||
}
|
||||
|
||||
export function formatDiagnosticTraceparentForPropagation(
|
||||
event: { trace?: DiagnosticTraceContext },
|
||||
metadata: DiagnosticEventMetadata,
|
||||
): string | undefined {
|
||||
if (!metadata.trusted || !dispatchedTrustedDiagnosticMetadata.has(metadata)) {
|
||||
return undefined;
|
||||
}
|
||||
return formatDiagnosticTraceparent(event.trace);
|
||||
}
|
||||
|
||||
export function resetDiagnosticEventsForTest(): void {
|
||||
const state = getDiagnosticEventsState();
|
||||
state.enabled = true;
|
||||
|
||||
Reference in New Issue
Block a user