Skip to content

Commit 2dea8de

Browse files
authored
Implement simple 7 day log retention and blank states (#976)
1 parent 252f5b3 commit 2dea8de

File tree

8 files changed

+103
-28
lines changed

8 files changed

+103
-28
lines changed

apps/webapp/app/env.server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ const EnvironmentSchema = z.object({
101101
OBJECT_STORE_BASE_URL: z.string().optional(),
102102
OBJECT_STORE_ACCESS_KEY_ID: z.string().optional(),
103103
OBJECT_STORE_SECRET_ACCESS_KEY: z.string().optional(),
104+
EVENTS_BATCH_SIZE: z.coerce.number().int().default(100),
105+
EVENTS_BATCH_INTERVAL: z.coerce.number().int().default(1000),
106+
EVENTS_DEFAULT_LOG_RETENTION: z.coerce.number().int().default(7),
104107
});
105108

106109
export type Environment = z.infer<typeof EnvironmentSchema>;

apps/webapp/app/presenters/v3/RunPresenter.server.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { eventRepository } from "~/v3/eventRepository.server";
66

77
type Result = Awaited<ReturnType<RunPresenter["call"]>>;
88
export type Run = Result["run"];
9-
export type RunEvent = Result["events"][0];
9+
export type RunEvent = NonNullable<Result["trace"]>["events"][0];
1010

1111
export class RunPresenter {
1212
#prismaClient: PrismaClient;
@@ -64,7 +64,19 @@ export class RunPresenter {
6464
const traceSummary = await eventRepository.getTraceSummary(run.traceId);
6565

6666
if (!traceSummary) {
67-
throw new Error("Trace not found");
67+
return {
68+
run: {
69+
number: run.number,
70+
friendlyId: run.friendlyId,
71+
environment: {
72+
type: run.runtimeEnvironment.type,
73+
slug: run.runtimeEnvironment.slug,
74+
userId: run.runtimeEnvironment.orgMember?.user.id,
75+
userName: getUsername(run.runtimeEnvironment.orgMember?.user),
76+
},
77+
},
78+
trace: undefined,
79+
};
6880
}
6981

7082
//this tree starts at the passed in span (hides parent elements if there are any)
@@ -115,12 +127,14 @@ export class RunPresenter {
115127
userName: getUsername(run.runtimeEnvironment.orgMember?.user),
116128
},
117129
},
118-
rootSpanStatus,
119-
events: events,
120-
parentRunFriendlyId:
121-
tree?.id === traceSummary.rootSpan.id ? undefined : traceSummary.rootSpan.runId,
122-
duration: totalDuration,
123-
rootStartedAt: tree?.data.startTime,
130+
trace: {
131+
rootSpanStatus,
132+
events: events,
133+
parentRunFriendlyId:
134+
tree?.id === traceSummary.rootSpan.id ? undefined : traceSummary.rootSpan.runId,
135+
duration: totalDuration,
136+
rootStartedAt: tree?.data.startTime,
137+
},
124138
};
125139
}
126140
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { PrismaClient, prisma } from "~/db.server";
33
import { eventRepository } from "~/v3/eventRepository.server";
44

55
type Result = Awaited<ReturnType<SpanPresenter["call"]>>;
6-
export type Span = Result["event"];
6+
export type Span = NonNullable<Result>["event"];
77

88
export class SpanPresenter {
99
#prismaClient: PrismaClient;
@@ -36,7 +36,7 @@ export class SpanPresenter {
3636
const span = await eventRepository.getSpan(spanId);
3737

3838
if (!span) {
39-
throw new Error("Event not found");
39+
return;
4040
}
4141

4242
const output =

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { TaskPath } from "~/components/runs/v3/TaskPath";
2121
import { TaskRunAttemptStatusCombo } from "~/components/runs/v3/TaskRunAttemptStatus";
2222
import { useOrganization } from "~/hooks/useOrganizations";
2323
import { useProject } from "~/hooks/useProject";
24+
import { redirectWithErrorMessage } from "~/models/message.server";
2425
import { SpanPresenter } from "~/presenters/v3/SpanPresenter.server";
2526
import { requireUserId } from "~/services/session.server";
2627
import { cn } from "~/utils/cn";
@@ -29,7 +30,7 @@ import { SpanLink } from "~/v3/eventRepository.server";
2930

3031
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
3132
const userId = await requireUserId(request);
32-
const { projectParam, organizationSlug, spanParam } = v3SpanParamsSchema.parse(params);
33+
const { projectParam, organizationSlug, runParam, spanParam } = v3SpanParamsSchema.parse(params);
3334

3435
const presenter = new SpanPresenter();
3536
const span = await presenter.call({
@@ -39,6 +40,15 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
3940
spanId: spanParam,
4041
});
4142

43+
if (!span) {
44+
// We're going to redirect to the run page
45+
return redirectWithErrorMessage(
46+
v3RunPath({ slug: organizationSlug }, { slug: projectParam }, { friendlyId: runParam }),
47+
request,
48+
`Event not found.`
49+
);
50+
}
51+
4252
return typedjson({ span });
4353
};
4454

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import { useEffect, useRef, useState } from "react";
1717
import { typedjson, useTypedLoaderData } from "remix-typedjson";
1818
import { ShowParentIcon, ShowParentIconSelected } from "~/assets/icons/ShowParentIcon";
1919
import tileBgPath from "~/assets/images/[email protected]";
20+
import { BlankstateInstructions } from "~/components/BlankstateInstructions";
2021
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
21-
import { PageBody } from "~/components/layout/AppLayout";
22+
import { MainCenteredContainer, PageBody } from "~/components/layout/AppLayout";
2223
import { Badge } from "~/components/primitives/Badge";
2324
import { LinkButton } from "~/components/primitives/Buttons";
2425
import { Input } from "~/components/primitives/Input";
@@ -85,29 +86,56 @@ function getSpanId(path: string): string | undefined {
8586
}
8687

8788
export default function Page() {
88-
const {
89-
run,
90-
events,
91-
parentRunFriendlyId,
92-
resizeSettings,
93-
duration,
94-
rootSpanStatus,
95-
rootStartedAt,
96-
} = useTypedLoaderData<typeof loader>();
89+
const { run, trace, resizeSettings } = useTypedLoaderData<typeof loader>();
9790
const navigate = useNavigate();
9891
const organization = useOrganization();
9992
const pathName = usePathName();
10093
const project = useProject();
10194
const user = useUser();
10295

96+
const usernameForEnv = user.id !== run.environment.userId ? run.environment.userName : undefined;
97+
98+
if (!trace) {
99+
return (
100+
<>
101+
<NavBar>
102+
<PageTitle
103+
backButton={{
104+
to: v3RunsPath(organization, project),
105+
text: "Runs",
106+
}}
107+
title={`Run #${run.number}`}
108+
/>
109+
<PageAccessories>
110+
<EnvironmentLabel
111+
size="large"
112+
environment={run.environment}
113+
userName={usernameForEnv}
114+
/>
115+
</PageAccessories>
116+
</NavBar>
117+
<PageBody>
118+
<MainCenteredContainer className="max-w-prose">
119+
<BlankstateInstructions title="These logs have taken a walk">
120+
<Paragraph spacing>
121+
Looks like the logs from this run have wandered off after their 7-day stay. We tidy
122+
up older logs to keep things running smoothly.
123+
</Paragraph>
124+
</BlankstateInstructions>
125+
</MainCenteredContainer>
126+
</PageBody>
127+
</>
128+
);
129+
}
130+
131+
const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace;
132+
103133
const selectedSpanId = getSpanId(pathName);
104134

105135
const changeToSpan = useDebounce((selectedSpan: string) => {
106136
navigate(v3RunSpanPath(organization, project, run, { spanId: selectedSpan }));
107137
}, 250);
108138

109-
const usernameForEnv = user.id !== run.environment.userId ? run.environment.userName : undefined;
110-
111139
const revalidator = useRevalidator();
112140
const streamedEvents = useEventSource(v3RunStreamingPath(organization, project, run), {
113141
event: "message",

apps/webapp/app/services/worker.server.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { ResumeTaskRunDependenciesService } from "~/v3/services/resumeTaskRunDep
3434
import { ResumeBatchRunService } from "~/v3/services/resumeBatchRun.server";
3535
import { ResumeTaskDependencyService } from "~/v3/services/resumeTaskDependency.server";
3636
import { TimeoutDeploymentService } from "~/v3/services/timeoutDeployment.server";
37+
import { eventRepository } from "~/v3/eventRepository.server";
3738

3839
const workerCatalog = {
3940
indexEndpoint: z.object({
@@ -250,6 +251,13 @@ function getWorkerQueue() {
250251
});
251252
},
252253
},
254+
// Run this every hour at the 13 minute mark
255+
purgeOldTaskEvents: {
256+
pattern: "47 * * * *",
257+
handler: async (payload, job) => {
258+
await eventRepository.truncateEvents();
259+
},
260+
},
253261
},
254262
tasks: {
255263
"events.invokeDispatcher": {
@@ -301,8 +309,8 @@ function getWorkerQueue() {
301309
graphileJob.id,
302310
payload.orphanedEvents
303311
? {
304-
event: payload.orphanedEvents,
305-
}
312+
event: payload.orphanedEvents,
313+
}
306314
: undefined
307315
);
308316
break;

apps/webapp/app/utils/pathBuilder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export const v3RunParamsSchema = ProjectParamSchema.extend({
8282
runParam: z.string(),
8383
});
8484

85-
export const v3SpanParamsSchema = ProjectParamSchema.extend({
85+
export const v3SpanParamsSchema = v3RunParamsSchema.extend({
8686
spanParam: z.string(),
8787
});
8888

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export type EventRepoConfig = {
8787
batchSize: number;
8888
batchInterval: number;
8989
redis: RedisOptions;
90+
retentionInDays: number;
9091
};
9192

9293
export type QueryOptions = Prisma.TaskEventWhereInput;
@@ -723,6 +724,16 @@ export class EventRepository {
723724
return this._randomIdGenerator.generateSpanId();
724725
}
725726

727+
public async truncateEvents() {
728+
await this.db.taskEvent.deleteMany({
729+
where: {
730+
createdAt: {
731+
lt: new Date(Date.now() - this._config.retentionInDays * 24 * 60 * 60 * 1000),
732+
},
733+
},
734+
});
735+
}
736+
726737
/**
727738
* Returns a deterministically random 8-byte span ID formatted/encoded as a 16 lowercase hex
728739
* characters corresponding to 64 bits, based on the trace ID and seed.
@@ -743,8 +754,9 @@ export class EventRepository {
743754
}
744755

745756
export const eventRepository = new EventRepository(prisma, {
746-
batchSize: 100,
747-
batchInterval: 1000,
757+
batchSize: env.EVENTS_BATCH_SIZE,
758+
batchInterval: env.EVENTS_BATCH_INTERVAL,
759+
retentionInDays: env.EVENTS_DEFAULT_LOG_RETENTION,
748760
redis: {
749761
port: env.REDIS_PORT,
750762
host: env.REDIS_HOST,

0 commit comments

Comments
 (0)