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 target = opencodeTargetUrl(url, opencodeUpstreamUrl);
|
||||||
const streamTransform = opencodeEventDirectoryTransformForTarget(target, opencodeEventDirectoryRewrite);
|
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 = {
|
const extraResponseHeaders = {
|
||||||
...cloudWebTraceHeaders(traceContext, serviceId),
|
...cloudWebTraceHeaders(traceContext, serviceId),
|
||||||
...(ticketAuth.ticket ? { "set-cookie": opencodeTicketSetCookie(ticketAuth.ticket) } : {})
|
...(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) {
|
function opencodeProxyRoute(pathname) {
|
||||||
const path = String(pathname || "/");
|
const path = String(pathname || "/");
|
||||||
if (path === "/global/event") return "/global/event";
|
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);
|
assert.equal(first.done, false);
|
||||||
const text = new TextDecoder().decode(first.value);
|
const text = new TextDecoder().decode(first.value);
|
||||||
assert.match(text, /"directory":"\/"/u);
|
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 reader.cancel();
|
||||||
|
|
||||||
await waitFor(() => otelBodies.length > 0, "expected OpenCode event stream OTel span");
|
await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.request"), "expected OpenCode event stream completion OTel span");
|
||||||
const spans = otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []);
|
const spans = collectSpans();
|
||||||
const span = spans.find((item) => item.name === "opencode.proxy.request");
|
const span = spans.find((item) => item.name === "opencode.proxy.request");
|
||||||
assert.ok(span, "expected opencode.proxy.request span");
|
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]));
|
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