
























@@ -878,6 +878,107 @@ describe("diagnostics-otel service", () => {
878878await service.stop?.(ctx);
879879});
880880881+test("exports message delivery spans and metrics with low-cardinality attributes", async () => {
882+const service = createDiagnosticsOtelService();
883+const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
884+await service.start(ctx);
885+886+emitDiagnosticEvent({
887+type: "message.delivery.started",
888+channel: "matrix",
889+deliveryKind: "text",
890+sessionKey: "session-secret",
891+});
892+emitDiagnosticEvent({
893+type: "message.delivery.completed",
894+channel: "matrix",
895+deliveryKind: "text",
896+durationMs: 25,
897+resultCount: 1,
898+sessionKey: "session-secret",
899+});
900+emitDiagnosticEvent({
901+type: "message.delivery.error",
902+channel: "discord",
903+deliveryKind: "media",
904+durationMs: 40,
905+errorCategory: "TypeError",
906+sessionKey: "session-secret",
907+});
908+await flushDiagnosticEvents();
909+910+expect(
911+telemetryState.counters.get("openclaw.message.delivery.started")?.add,
912+).toHaveBeenCalledWith(1, {
913+"openclaw.channel": "matrix",
914+"openclaw.delivery.kind": "text",
915+});
916+expect(
917+telemetryState.histograms.get("openclaw.message.delivery.duration_ms")?.record,
918+).toHaveBeenCalledWith(
919+25,
920+expect.objectContaining({
921+"openclaw.channel": "matrix",
922+"openclaw.delivery.kind": "text",
923+"openclaw.outcome": "completed",
924+}),
925+);
926+expect(
927+telemetryState.histograms.get("openclaw.message.delivery.duration_ms")?.record,
928+).toHaveBeenCalledWith(
929+40,
930+expect.objectContaining({
931+"openclaw.channel": "discord",
932+"openclaw.delivery.kind": "media",
933+"openclaw.outcome": "error",
934+"openclaw.errorCategory": "TypeError",
935+}),
936+);
937+938+const deliverySpanCalls = telemetryState.tracer.startSpan.mock.calls.filter(
939+(call) => call[0] === "openclaw.message.delivery",
940+);
941+expect(deliverySpanCalls).toHaveLength(2);
942+expect(deliverySpanCalls[0]?.[1]).toMatchObject({
943+attributes: {
944+"openclaw.channel": "matrix",
945+"openclaw.delivery.kind": "text",
946+"openclaw.outcome": "completed",
947+"openclaw.delivery.result_count": 1,
948+},
949+startTime: expect.any(Number),
950+});
951+expect(deliverySpanCalls[1]?.[1]).toMatchObject({
952+attributes: {
953+"openclaw.channel": "discord",
954+"openclaw.delivery.kind": "media",
955+"openclaw.outcome": "error",
956+"openclaw.errorCategory": "TypeError",
957+},
958+startTime: expect.any(Number),
959+});
960+for (const call of deliverySpanCalls) {
961+expect(call[1]).toEqual({
962+attributes: expect.not.objectContaining({
963+"openclaw.sessionKey": expect.anything(),
964+"openclaw.messageId": expect.anything(),
965+"openclaw.conversationId": expect.anything(),
966+"openclaw.content": expect.anything(),
967+"openclaw.to": expect.anything(),
968+}),
969+startTime: expect.any(Number),
970+});
971+}
972+const errorSpan = telemetryState.spans.find(
973+(span) => span.name === "openclaw.message.delivery" && span.setStatus.mock.calls.length > 0,
974+);
975+expect(errorSpan?.setStatus).toHaveBeenCalledWith({
976+code: 2,
977+message: "TypeError",
978+});
979+await service.stop?.(ctx);
980+});
981+881982test("does not export model or tool content unless capture is explicitly enabled", async () => {
882983const service = createDiagnosticsOtelService();
883984const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。