fix: harden workbench trace timeout observability

This commit is contained in:
lyon
2026-06-20 01:16:45 +08:00
parent faca69ec01
commit 8f9bdbd5fd
5 changed files with 185 additions and 46 deletions
+48 -1
View File
@@ -5,7 +5,7 @@ import assert from "node:assert/strict";
import { test } from "bun:test";
import { createCloudApiServer } from "./server.ts";
import { createWebPerformanceStore, webPerformanceRouteTemplate } from "./web-performance.ts";
import { createWebPerformanceStore, emitWorkbenchUiOtelSpans, webPerformanceRouteTemplate } from "./web-performance.ts";
test("web performance store aggregates browser-perceived durations as Prometheus histograms", () => {
const store = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v02", HWLAB_GITOPS_TARGET: "v02" } });
@@ -202,6 +202,53 @@ test("web performance store accepts Workbench UI lifecycle events without high-c
assert.doesNotMatch(text, /ses_secret|trc_secret|ui_secret|0123456789abcdef|sessionId|traceId|otelTraceId|prompt|api key/iu);
});
test("Workbench UI OTel spans include timeout activity diagnostics without raw trace ids", async () => {
const calls: any[] = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = async (_url: string | URL | Request, init?: RequestInit) => {
calls.push(JSON.parse(String(init?.body ?? "{}")));
return new Response(null, { status: 200 });
};
try {
const result = await emitWorkbenchUiOtelSpans({
schemaVersion: "hwlab-web-performance-v2",
page: "/workbench/sessions/ses_secret",
events: [{
kind: "workbench_ui_event",
eventType: "api_request",
loadingScope: "api",
state: "request",
reason: "api_request",
route: "/v1/workbench/turns/trc_secret",
method: "GET",
status: 0,
outcome: "timeout",
valueMs: 8001,
uiTraceId: "ui_secret",
otelTraceId: "0123456789abcdef0123456789abcdef",
timeoutName: "workbench turn",
activityWaitingFor: "code-agent",
activityLastEventLabel: "realtime:heartbeat",
activityIdleMs: 13042,
traceId: "trc_secret"
}]
} as Record<string, unknown>, { OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel.example/v1/traces" });
assert.equal(result.submitted, 1);
assert.equal(calls.length, 1);
const span = calls[0].resourceSpans[0].scopeSpans[0].spans[0];
const attrs = Object.fromEntries(span.attributes.map((item: any) => [item.key, item.value.stringValue ?? Number(item.value.intValue) ?? item.value.boolValue]));
assert.equal(attrs["http.route"], "/v1/workbench/turns/:id");
assert.equal(attrs["workbench.ui.timeout_name"], "workbench turn");
assert.equal(attrs["workbench.ui.activity_waiting_for"], "code-agent");
assert.equal(attrs["workbench.ui.activity_last_event_label"], "realtime:heartbeat");
assert.equal(attrs["workbench.ui.activity_idle_ms"], 13042);
assert.doesNotMatch(JSON.stringify(calls[0]), /trc_secret|ses_secret|prompt|api key/iu);
} finally {
globalThis.fetch = originalFetch;
}
});
test("web performance store drops unsupported Workbench labels and counts series limit drops", () => {
const store = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v03", HWLAB_GITOPS_TARGET: "v03" }, maxSeries: 1 });
const first = store.record({