Merge pull request #2295 from pikasTech/issue-2274-opencode-eventsource-start-otel
fix: trace OpenCode event stream start
This commit is contained in:
@@ -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]));
|
||||
|
||||
Reference in New Issue
Block a user