Merge pull request #2295 from pikasTech/issue-2274-opencode-eventsource-start-otel

fix: trace OpenCode event stream start
This commit is contained in:
Lyon
2026-06-30 15:04:31 +08:00
committed by GitHub
2 changed files with 71 additions and 2 deletions
@@ -800,6 +800,18 @@ async function proxyOpencodeRequest({ request, response, url, cloudApiBaseUrl, c
const target = opencodeTargetUrl(url, opencodeUpstreamUrl);
const streamTransform = opencodeEventDirectoryTransformForTarget(target, opencodeEventDirectoryRewrite);
if (streamTransform?.stats?.enabled) {
emitOpencodeProxyStreamStartSpanAsync({
traceContext,
serviceId,
request,
url,
target,
ticketAuth,
sessionStatusCode: session.statusCode || 0,
streamTransformStats: streamTransform.stats
});
}
const extraResponseHeaders = {
...cloudWebTraceHeaders(traceContext, serviceId),
...(ticketAuth.ticket ? { "set-cookie": opencodeTicketSetCookie(ticketAuth.ticket) } : {})
@@ -1043,6 +1055,50 @@ function opencodeStreamTransformStatsAttributes(stats) {
};
}
function emitOpencodeProxyStreamStartSpanAsync({ traceContext, serviceId, request, url, target = null, ticketAuth = null, sessionStatusCode = 0, streamTransformStats = null } = {}) {
const endpoint = otelTracesEndpoint(process.env);
if (!endpoint || !traceContext?.traceId) return;
const observedAtMs = Date.now();
const span = {
traceId: traceContext.traceId,
spanId: randomOtelSpanId(),
parentSpanId: traceContext.spanId || traceContext.parentSpanId || "",
name: "opencode.proxy.stream.start",
kind: 2,
startTimeMs: observedAtMs,
endTimeMs: observedAtMs,
statusCode: 0,
errorCode: "",
attributes: {
"http.request.method": request?.method || "GET",
"http.route": opencodeProxyRoute(url?.pathname || ""),
"url.scheme": target?.protocol ? target.protocol.replace(":", "") : undefined,
"server.address": target?.hostname || undefined,
"server.port": target ? Number(target.port || (target.protocol === "https:" ? 443 : 80)) : undefined,
"opencode.proxy.phase": "stream_start",
"opencode.proxy.ticket_present": ticketAuth?.ticketPresent === true,
"opencode.proxy.ticket_accepted": Boolean(ticketAuth?.ticket),
"opencode.proxy.session_status_code": sessionStatusCode || undefined,
"opencode.proxy.streaming": true,
...opencodeStreamTransformStatsAttributes(streamTransformStats),
valuesPrinted: false
}
};
setTimeout(() => {
emitOtelSpan(endpoint, span, serviceId || "hwlab-cloud-web", "hwlab.cloud_web.opencode_proxy").catch((error) => {
process.stderr.write(JSON.stringify({
event: "cloud-web-opencode-otel-emit-failed",
serviceId: serviceId || "hwlab-cloud-web",
traceId: traceContext.traceId,
requestId: traceContext.requestId,
span: "opencode.proxy.stream.start",
error: error instanceof Error ? error.message : String(error),
valuesPrinted: false
}) + "\n");
});
}, 0);
}
function opencodeProxyRoute(pathname) {
const path = String(pathname || "/");
if (path === "/global/event") return "/global/event";
@@ -882,10 +882,23 @@ test("cloud web OpenCode event stream emits rewrite OTLP stats when the client c
assert.equal(first.done, false);
const text = new TextDecoder().decode(first.value);
assert.match(text, /"directory":"\/"/u);
const collectSpans = () => otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []);
await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.stream.start"), "expected OpenCode event stream start OTel span");
const startSpan = collectSpans().find((item) => item.name === "opencode.proxy.stream.start");
assert.ok(startSpan, "expected opencode.proxy.stream.start span");
const startAttrs = new Map((startSpan.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue]));
assert.equal(startAttrs.get("http.route"), "/global/event");
assert.equal(startAttrs.get("opencode.proxy.phase"), "stream_start");
assert.equal(startAttrs.get("opencode.proxy.streaming"), true);
assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_enabled"), true);
assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_from"), "/workspace");
assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_to"), "/");
await reader.cancel();
await waitFor(() => otelBodies.length > 0, "expected OpenCode event stream OTel span");
const spans = otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []);
await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.request"), "expected OpenCode event stream completion OTel span");
const spans = collectSpans();
const span = spans.find((item) => item.name === "opencode.proxy.request");
assert.ok(span, "expected opencode.proxy.request span");
const attrs = new Map((span.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue]));