Skip to content

Commit 434b7f0

Browse files
1stvampTrigger.dev RepoOps
authored andcommitted
fix(webapp): stop returning internal debug events from the run trace API
The run trace API no longer returns internal debug events. Trace reads now leave them out unless the caller explicitly asks for them. Mono-RevId: 763e6666f5abf00fa478f439ec3db9e7cd515b11
1 parent 01bc5f7 commit 434b7f0

6 files changed

Lines changed: 243 additions & 5 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Run traces returned by the API no longer include internal debug events.

‎apps/webapp/app/presenters/v3/SpanPresenter.server.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export class SpanPresenter extends BasePresenter {
116116
spanId,
117117
runFriendlyId,
118118
linkedRunId,
119+
showDebug = false,
119120
}: {
120121
userId: string;
121122
projectSlug: string;
@@ -127,6 +128,7 @@ export class SpanPresenter extends BasePresenter {
127128
spanId: string;
128129
runFriendlyId: string;
129130
linkedRunId?: string;
131+
showDebug?: boolean;
130132
}) {
131133
const project = await this._replica.project.findFirst({
132134
where: {
@@ -227,6 +229,7 @@ export class SpanPresenter extends BasePresenter {
227229
createdAt: parentRun.createdAt,
228230
completedAt: parentRun.completedAt,
229231
eventRepository: repository,
232+
showDebug,
230233
});
231234

232235
if (!span) {
@@ -691,6 +694,7 @@ export class SpanPresenter extends BasePresenter {
691694
projectId,
692695
createdAt,
693696
completedAt,
697+
showDebug,
694698
}: {
695699
eventRepository: IEventRepository;
696700
traceId: string;
@@ -700,6 +704,7 @@ export class SpanPresenter extends BasePresenter {
700704
eventStore: TaskEventStoreTable;
701705
createdAt: Date;
702706
completedAt: Date | null;
707+
showDebug: boolean;
703708
}) {
704709
const span = await eventRepository.getSpan(
705710
eventStore,
@@ -708,7 +713,7 @@ export class SpanPresenter extends BasePresenter {
708713
traceId,
709714
createdAt,
710715
completedAt ?? undefined,
711-
{ includeDebugLogs: true }
716+
{ includeDebugLogs: showDebug }
712717
);
713718

714719
if (!span) {

‎apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
164164
runFriendlyId: runParam,
165165
userId,
166166
linkedRunId,
167+
showDebug: user.admin || user.isImpersonating,
167168
});
168169

169170
if (!result) {

‎apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,7 +1959,7 @@ export class ClickhouseEventRepository implements IEventRepository {
19591959
}
19601960
}
19611961

1962-
if (options?.includeDebugLogs === false) {
1962+
if (options?.includeDebugLogs !== true) {
19631963
queryBuilder.where("kind != {kind: String}", { kind: "DEBUG_EVENT" });
19641964
}
19651965
}
@@ -2417,7 +2417,7 @@ export class ClickhouseEventRepository implements IEventRepository {
24172417
}
24182418
}
24192419

2420-
if (options?.includeDebugLogs === false) {
2420+
if (options?.includeDebugLogs !== true) {
24212421
queryBuilder.where("kind != {kind: String}", { kind: "DEBUG_EVENT" });
24222422
}
24232423

@@ -2539,6 +2539,10 @@ export class ClickhouseEventRepository implements IEventRepository {
25392539
});
25402540
}
25412541

2542+
if (options?.includeDebugLogs !== true) {
2543+
queryBuilder.where("kind != {kind: String}", { kind: "DEBUG_EVENT" });
2544+
}
2545+
25422546
queryBuilder.orderBy("start_time ASC");
25432547

25442548
const [queryError, records] = await queryBuilder.execute();
@@ -3029,7 +3033,7 @@ export class ClickhouseEventRepository implements IEventRepository {
30293033
}
30303034
}
30313035

3032-
if (options?.includeDebugLogs === false) {
3036+
if (options?.includeDebugLogs !== true) {
30333037
queryBuilder.where("kind != {kind: String}", { kind: "DEBUG_EVENT" });
30343038
}
30353039

‎apps/webapp/test/runTracePage.test.ts‎

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,69 @@ describe("getRunTracePage", () => {
221221
expect(paged).toEqual(await unpaged(repository, run, "root"));
222222
expect(paged.grandchild.isCancelled).toBe(true);
223223
expect(paged.done).toEqual({ isError: false, isPartial: false, isCancelled: false });
224-
expect(paged.debug).toBeDefined();
224+
expect(paged.debug).toBeUndefined();
225+
} finally {
226+
await shutdownRepository(repository);
227+
}
228+
}
229+
);
230+
231+
clickhouseTest(
232+
"returns debug events only when the caller asks for them",
233+
async ({ clickhouseContainer }) => {
234+
const { clickhouse, repository } = createRepository(clickhouseContainer.getConnectionUrl());
235+
const traceId = "trace_debug_events";
236+
237+
try {
238+
const [insertError] = await clickhouse.taskEventsV2.insert([
239+
row(traceId, "root", { durationMs: 10_000 }),
240+
row(traceId, "child", { parentSpanId: "root", offsetMs: 1_000 }),
241+
row(traceId, "debug", { parentSpanId: "root", offsetMs: 2_000, kind: "DEBUG_EVENT" }),
242+
]);
243+
expect(insertError).toBeNull();
244+
245+
const run = rootRun(traceId);
246+
const pagedIds = (await pageAll(repository, run, 2)).flatMap((page) =>
247+
page.data.map((span) => span.id)
248+
);
249+
expect(pagedIds).toContain("child");
250+
expect(pagedIds).not.toContain("debug");
251+
expect(Object.keys(await unpaged(repository, run, "root"))).not.toContain("debug");
252+
253+
const chunk = await repository.getTraceChunk(
254+
"taskEventPartitioned",
255+
ENV_ID,
256+
traceId,
257+
run.createdAt,
258+
run.completedAt ?? undefined,
259+
undefined,
260+
{ includeDebugLogs: true }
261+
);
262+
expect(chunk?.events.map((event) => event.spanId)).toContain("debug");
263+
264+
const summary = await repository.getTraceDetailedSubtreeSummary(
265+
"taskEventPartitioned",
266+
ENV_ID,
267+
traceId,
268+
"root",
269+
run.createdAt,
270+
run.completedAt ?? undefined,
271+
{ includeDebugLogs: true }
272+
);
273+
expect(summary && Object.keys(unpagedStates(summary.rootSpan))).toContain("debug");
274+
275+
const getDebugSpan = (includeDebugLogs?: boolean) =>
276+
repository.getSpan(
277+
"taskEventPartitioned",
278+
ENV_ID,
279+
"debug",
280+
traceId,
281+
run.createdAt,
282+
run.completedAt ?? undefined,
283+
{ includeDebugLogs }
284+
);
285+
expect(await getDebugSpan()).toBeUndefined();
286+
expect(await getDebugSpan(true)).toBeDefined();
225287
} finally {
226288
await shutdownRepository(repository);
227289
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { describe, expect, vi } from "vitest";
2+
3+
// Wiring overrides only, as in SpanPresenter.readthrough.test.ts: the presenter is given its
4+
// Postgres client through the constructor, and the run store and event repository singletons are
5+
// swapped for instances over the test containers. Every read runs against a real database.
6+
vi.mock("~/db.server", () => ({
7+
prisma: {},
8+
$replica: {},
9+
}));
10+
11+
const wiring = vi.hoisted(() => ({
12+
runStore: undefined as unknown,
13+
eventRepository: undefined as unknown,
14+
}));
15+
vi.mock("~/v3/runStore.server", () => ({
16+
get runStore() {
17+
return wiring.runStore;
18+
},
19+
}));
20+
vi.mock("~/v3/eventRepository/index.server", () => ({
21+
getEventRepositoryForStore: async () => wiring.eventRepository,
22+
}));
23+
24+
import { ClickHouse } from "@internal/clickhouse";
25+
import { PostgresRunStore } from "@internal/run-store";
26+
import { containerTestWithClickhouse } from "@internal/testcontainers";
27+
import { SpanPresenter } from "~/presenters/v3/SpanPresenter.server";
28+
import {
29+
ClickhouseEventRepository,
30+
convertDateToClickhouseDateTime,
31+
} from "~/v3/eventRepository/clickhouseEventRepository.server";
32+
33+
vi.setConfig({ testTimeout: 90_000 });
34+
35+
function clickhouseDate(value: Date) {
36+
return value.toISOString().replace("T", " ").replace("Z", "");
37+
}
38+
39+
describe("SpanPresenter debug spans", () => {
40+
containerTestWithClickhouse(
41+
"opens a debug span only when debug events are shown",
42+
async ({ prisma, clickhouseContainer }) => {
43+
const user = await prisma.user.create({
44+
data: { email: "span-debug@example.com", authenticationMethod: "MAGIC_LINK" },
45+
});
46+
const organization = await prisma.organization.create({
47+
data: { title: "span-debug", slug: "span-debug" },
48+
});
49+
await prisma.orgMember.create({
50+
data: { organizationId: organization.id, userId: user.id, role: "MEMBER" },
51+
});
52+
const project = await prisma.project.create({
53+
data: {
54+
name: "span-debug",
55+
slug: "span-debug",
56+
organizationId: organization.id,
57+
externalRef: "proj_span_debug",
58+
},
59+
});
60+
const environment = await prisma.runtimeEnvironment.create({
61+
data: {
62+
slug: "prod",
63+
type: "PRODUCTION",
64+
projectId: project.id,
65+
organizationId: organization.id,
66+
apiKey: "tr_prod_span_debug",
67+
pkApiKey: "pk_prod_span_debug",
68+
shortcode: "sc-span-debug",
69+
},
70+
});
71+
72+
const traceId = "d".repeat(32);
73+
const createdAt = new Date(Date.now() - 60_000);
74+
await prisma.taskRun.create({
75+
data: {
76+
friendlyId: "run_span_debug",
77+
taskIdentifier: "my-task",
78+
status: "COMPLETED_SUCCESSFULLY",
79+
payload: "{}",
80+
payloadType: "application/json",
81+
traceId,
82+
spanId: "rootspan00000001",
83+
queue: "task/my-task",
84+
runtimeEnvironmentId: environment.id,
85+
projectId: project.id,
86+
organizationId: organization.id,
87+
environmentType: "PRODUCTION",
88+
engine: "V2",
89+
taskEventStore: "clickhouse_v2",
90+
createdAt,
91+
completedAt: new Date(createdAt.getTime() + 10_000),
92+
},
93+
});
94+
95+
const clickhouse = new ClickHouse({
96+
url: clickhouseContainer.getConnectionUrl(),
97+
name: "test",
98+
});
99+
const repository = new ClickhouseEventRepository({
100+
clickhouse,
101+
version: "v2",
102+
insertStrategy: "insert",
103+
});
104+
const startTime = new Date(createdAt.getTime() + 1_000);
105+
const [insertError] = await clickhouse.taskEventsV2.insert([
106+
{
107+
environment_id: environment.id,
108+
organization_id: organization.id,
109+
project_id: project.id,
110+
task_identifier: "my-task",
111+
run_id: "run_span_debug",
112+
start_time: `${clickhouseDate(startTime).slice(0, 19)}.${String(
113+
startTime.getUTCMilliseconds() * 1_000_000
114+
).padStart(9, "0")}`,
115+
inserted_at: convertDateToClickhouseDateTime(startTime),
116+
duration: "0",
117+
trace_id: traceId,
118+
span_id: "debugspan0000001",
119+
parent_span_id: "rootspan00000001",
120+
message: "debug event",
121+
kind: "DEBUG_EVENT",
122+
status: "OK",
123+
attributes: {},
124+
metadata: "{}",
125+
expires_at: clickhouseDate(new Date(Date.now() + 86_400_000)),
126+
},
127+
]);
128+
expect(insertError).toBeNull();
129+
130+
wiring.runStore = new PostgresRunStore({ prisma, readOnlyPrisma: prisma });
131+
wiring.eventRepository = repository;
132+
133+
const presenter = new SpanPresenter(prisma, prisma);
134+
const call = (showDebug?: boolean) =>
135+
presenter.call({
136+
userId: user.id,
137+
projectSlug: project.slug,
138+
envSlug: environment.slug,
139+
spanId: "debugspan0000001",
140+
runFriendlyId: "run_span_debug",
141+
showDebug,
142+
});
143+
144+
try {
145+
await expect(call()).rejects.toThrow("Span not found");
146+
await expect(call(false)).rejects.toThrow("Span not found");
147+
expect(await call(true)).toMatchObject({
148+
type: "span",
149+
span: { spanId: "debugspan0000001" },
150+
});
151+
} finally {
152+
await Promise.all([
153+
(repository as any)._flushScheduler.shutdown(),
154+
(repository as any)._llmMetricsFlushScheduler.shutdown(),
155+
(repository as any)._otlpMetricsFlushScheduler.shutdown(),
156+
]);
157+
}
158+
}
159+
);
160+
});

0 commit comments

Comments
 (0)