



















@@ -123,6 +123,8 @@ const SPAN_ID = "00f067aa0ba902b7";
123123const CHILD_SPAN_ID = "1111111111111111";
124124const GRANDCHILD_SPAN_ID = "2222222222222222";
125125const PROTO_KEY = "__proto__";
126+const MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS = 4096;
127+const OTEL_TRUNCATED_SUFFIX_MAX_CHARS = 20;
126128127129function createLogger() {
128130return {
@@ -137,10 +139,13 @@ type OtelContextFlags = {
137139traces?: boolean;
138140metrics?: boolean;
139141logs?: boolean;
142+captureContent?: NonNullable<
143+NonNullable<OpenClawPluginServiceContext["config"]["diagnostics"]>["otel"]
144+>["captureContent"];
140145};
141146function createOtelContext(
142147endpoint: string,
143-{ traces = false, metrics = false, logs = false }: OtelContextFlags = {},
148+{ traces = false, metrics = false, logs = false, captureContent }: OtelContextFlags = {},
144149): OpenClawPluginServiceContext {
145150return {
146151config: {
@@ -153,6 +158,7 @@ function createOtelContext(
153158 traces,
154159 metrics,
155160 logs,
161+ ...(captureContent !== undefined ? { captureContent } : {}),
156162},
157163},
158164},
@@ -723,6 +729,122 @@ describe("diagnostics-otel service", () => {
723729await service.stop?.(ctx);
724730});
725731732+test("does not export model or tool content unless capture is explicitly enabled", async () => {
733+const service = createDiagnosticsOtelService();
734+const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
735+await service.start(ctx);
736+737+emitDiagnosticEvent({
738+type: "model.call.completed",
739+runId: "run-1",
740+callId: "call-1",
741+provider: "openai",
742+model: "gpt-5.4",
743+durationMs: 80,
744+inputMessages: ["private user prompt"],
745+outputMessages: ["private model reply"],
746+systemPrompt: "private system prompt",
747+} as Parameters<typeof emitDiagnosticEvent>[0]);
748+emitDiagnosticEvent({
749+type: "tool.execution.completed",
750+runId: "run-1",
751+toolName: "read",
752+toolCallId: "tool-1",
753+durationMs: 20,
754+toolInput: "private tool input",
755+toolOutput: "private tool output",
756+} as Parameters<typeof emitDiagnosticEvent>[0]);
757+await flushDiagnosticEvents();
758+759+const modelCall = telemetryState.tracer.startSpan.mock.calls.find(
760+(call) => call[0] === "openclaw.model.call",
761+);
762+const toolCall = telemetryState.tracer.startSpan.mock.calls.find(
763+(call) => call[0] === "openclaw.tool.execution",
764+);
765+expect(modelCall?.[1]).toEqual({
766+attributes: expect.not.objectContaining({
767+"openclaw.content.input_messages": expect.anything(),
768+"openclaw.content.output_messages": expect.anything(),
769+"openclaw.content.system_prompt": expect.anything(),
770+}),
771+startTime: expect.any(Number),
772+});
773+expect(toolCall?.[1]).toEqual({
774+attributes: expect.not.objectContaining({
775+"openclaw.content.tool_input": expect.anything(),
776+"openclaw.content.tool_output": expect.anything(),
777+}),
778+startTime: expect.any(Number),
779+});
780+await service.stop?.(ctx);
781+});
782+783+test("exports bounded redacted content when capture fields are opted in", async () => {
784+const service = createDiagnosticsOtelService();
785+const ctx = createOtelContext(OTEL_TEST_ENDPOINT, {
786+traces: true,
787+metrics: true,
788+captureContent: {
789+enabled: true,
790+inputMessages: true,
791+outputMessages: true,
792+toolInputs: true,
793+toolOutputs: true,
794+systemPrompt: true,
795+},
796+});
797+await service.start(ctx);
798+799+emitDiagnosticEvent({
800+type: "model.call.completed",
801+runId: "run-1",
802+callId: "call-1",
803+provider: "openai",
804+model: "gpt-5.4",
805+durationMs: 80,
806+inputMessages: ["use key sk-1234567890abcdef1234567890abcdef"], // pragma: allowlist secret
807+outputMessages: ["model reply"],
808+systemPrompt: "system prompt",
809+} as Parameters<typeof emitDiagnosticEvent>[0]);
810+emitDiagnosticEvent({
811+type: "tool.execution.completed",
812+runId: "run-1",
813+toolName: "read",
814+toolCallId: "tool-1",
815+durationMs: 20,
816+toolInput: "tool input",
817+toolOutput: "x".repeat(6000),
818+} as Parameters<typeof emitDiagnosticEvent>[0]);
819+await flushDiagnosticEvents();
820+821+const modelCall = telemetryState.tracer.startSpan.mock.calls.find(
822+(call) => call[0] === "openclaw.model.call",
823+);
824+const toolCall = telemetryState.tracer.startSpan.mock.calls.find(
825+(call) => call[0] === "openclaw.tool.execution",
826+);
827+const modelAttrs = (modelCall?.[1] as { attributes?: Record<string, unknown> } | undefined)
828+?.attributes;
829+const toolAttrs = (toolCall?.[1] as { attributes?: Record<string, unknown> } | undefined)
830+?.attributes;
831+832+expect(modelAttrs).toMatchObject({
833+"openclaw.content.output_messages": "model reply",
834+"openclaw.content.system_prompt": "system prompt",
835+});
836+expect(String(modelAttrs?.["openclaw.content.input_messages"])).not.toContain(
837+"sk-1234567890abcdef1234567890abcdef", // pragma: allowlist secret
838+);
839+expect(toolAttrs).toMatchObject({
840+"openclaw.content.tool_input": "tool input",
841+});
842+expect(String(toolAttrs?.["openclaw.content.tool_output"]).length).toBeLessThanOrEqual(
843+MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS + OTEL_TRUNCATED_SUFFIX_MAX_CHARS,
844+);
845+await service.stop?.(ctx);
846+});
847+726848test("ignores invalid diagnostic event trace parents", async () => {
727849const service = createDiagnosticsOtelService();
728850const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。