






















@@ -111,6 +111,10 @@ vi.mock("@opentelemetry/semantic-conventions", () => ({
111111ATTR_SERVICE_NAME: "service.name",
112112}));
113113114+import {
115+emitTrustedDiagnosticEvent,
116+onInternalDiagnosticEvent,
117+} from "../../../src/infra/diagnostic-events.js";
114118import type { OpenClawPluginServiceContext } from "../api.js";
115119import { emitDiagnosticEvent } from "../api.js";
116120import { createDiagnosticsOtelService } from "./service.js";
@@ -122,6 +126,7 @@ const TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736";
122126const SPAN_ID = "00f067aa0ba902b7";
123127const CHILD_SPAN_ID = "1111111111111111";
124128const GRANDCHILD_SPAN_ID = "2222222222222222";
129+const TOOL_SPAN_ID = "3333333333333333";
125130const PROTO_KEY = "__proto__";
126131const MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS = 4096;
127132const OTEL_TRUNCATED_SUFFIX_MAX_CHARS = 20;
@@ -165,6 +170,7 @@ function createOtelContext(
165170},
166171logger: createLogger(),
167172stateDir: OTEL_TEST_STATE_DIR,
173+internalDiagnostics: { onEvent: onInternalDiagnosticEvent },
168174};
169175}
170176@@ -174,11 +180,13 @@ function createTraceOnlyContext(endpoint: string): OpenClawPluginServiceContext
174180175181async function emitAndCaptureLog(
176182event: Omit<Extract<Parameters<typeof emitDiagnosticEvent>[0], { type: "log.record" }>, "type">,
183+options: { trusted?: boolean } = {},
177184) {
178185const service = createDiagnosticsOtelService();
179186const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { logs: true });
180187await service.start(ctx);
181-emitDiagnosticEvent({
188+const emit = options.trusted ? emitTrustedDiagnosticEvent : emitDiagnosticEvent;
189+emit({
182190type: "log.record",
183191 ...event,
184192});
@@ -499,7 +507,7 @@ describe("diagnostics-otel service", () => {
499507}
500508});
501509502-test("attaches diagnostic trace context to exported logs", async () => {
510+test("does not attach untrusted diagnostic trace context to exported logs", async () => {
503511const emitCall = await emitAndCaptureLog({
504512level: "INFO",
505513message: "traceable log",
@@ -513,15 +521,31 @@ describe("diagnostics-otel service", () => {
513521},
514522});
515523516-expect(emitCall?.attributes).toMatchObject({
517-"openclaw.traceFlags": "01",
518-});
519524expect(emitCall?.attributes).toEqual(
520525expect.not.objectContaining({
521526"openclaw.traceId": expect.anything(),
522527"openclaw.spanId": expect.anything(),
528+"openclaw.traceFlags": expect.anything(),
523529}),
524530);
531+expect(telemetryState.tracer.setSpanContext).not.toHaveBeenCalled();
532+expect(emitCall?.context).toBeUndefined();
533+});
534+535+test("attaches trusted diagnostic trace context to exported logs", async () => {
536+const emitCall = await emitAndCaptureLog(
537+{
538+level: "INFO",
539+message: "traceable log",
540+trace: {
541+traceId: TRACE_ID,
542+spanId: SPAN_ID,
543+traceFlags: "01",
544+},
545+},
546+{ trusted: true },
547+);
548+525549expect(telemetryState.tracer.setSpanContext).toHaveBeenCalledWith(
526550expect.anything(),
527551expect.objectContaining({
@@ -817,6 +841,75 @@ describe("diagnostics-otel service", () => {
817841await service.stop?.(ctx);
818842});
819843844+test("parents trusted diagnostic lifecycle spans from explicit parent ids", async () => {
845+const service = createDiagnosticsOtelService();
846+const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
847+await service.start(ctx);
848+849+emitTrustedDiagnosticEvent({
850+type: "run.completed",
851+runId: "run-1",
852+provider: "openai",
853+model: "gpt-5.4",
854+outcome: "completed",
855+durationMs: 100,
856+trace: {
857+traceId: TRACE_ID,
858+spanId: CHILD_SPAN_ID,
859+parentSpanId: SPAN_ID,
860+traceFlags: "01",
861+},
862+});
863+emitTrustedDiagnosticEvent({
864+type: "model.call.completed",
865+runId: "run-1",
866+callId: "call-1",
867+provider: "openai",
868+model: "gpt-5.4",
869+durationMs: 80,
870+trace: {
871+traceId: TRACE_ID,
872+spanId: GRANDCHILD_SPAN_ID,
873+parentSpanId: CHILD_SPAN_ID,
874+traceFlags: "01",
875+},
876+});
877+emitTrustedDiagnosticEvent({
878+type: "tool.execution.error",
879+runId: "run-1",
880+toolName: "read",
881+durationMs: 20,
882+errorCategory: "TypeError",
883+trace: {
884+traceId: TRACE_ID,
885+spanId: TOOL_SPAN_ID,
886+parentSpanId: GRANDCHILD_SPAN_ID,
887+traceFlags: "01",
888+},
889+});
890+await flushDiagnosticEvents();
891+892+expect(telemetryState.tracer.setSpanContext).toHaveBeenCalledTimes(3);
893+expect(telemetryState.tracer.setSpanContext.mock.calls.map((call) => call[1])).toEqual([
894+expect.objectContaining({ traceId: TRACE_ID, spanId: SPAN_ID }),
895+expect.objectContaining({ traceId: TRACE_ID, spanId: CHILD_SPAN_ID }),
896+expect.objectContaining({ traceId: TRACE_ID, spanId: GRANDCHILD_SPAN_ID }),
897+]);
898+899+const parentBySpanName = Object.fromEntries(
900+telemetryState.tracer.startSpan.mock.calls.map((call) => [
901+call[0],
902+(call[2] as { spanContext?: { spanId?: string } } | undefined)?.spanContext?.spanId,
903+]),
904+);
905+expect(parentBySpanName).toMatchObject({
906+"openclaw.run": SPAN_ID,
907+"openclaw.model.call": CHILD_SPAN_ID,
908+"openclaw.tool.execution": GRANDCHILD_SPAN_ID,
909+});
910+await service.stop?.(ctx);
911+});
912+820913test("exports exec process spans without command text", async () => {
821914const service = createDiagnosticsOtelService();
822915const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。