Skip to content

Commit b4e4728

Browse files
committed
Adapt deployments logs to use the new s2 format
1 parent ec3a050 commit b4e4728

File tree

4 files changed

+71
-46
lines changed

4 files changed

+71
-46
lines changed

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,23 +163,25 @@ export class DeploymentPresenter {
163163
? ExternalBuildData.safeParse(deployment.externalBuildData)
164164
: undefined;
165165

166-
let s2Logs = undefined;
166+
let eventStream = undefined;
167167
if (env.S2_ENABLED === "1" && gitMetadata?.source === "trigger_github_app") {
168168
const [error, accessToken] = await tryCatch(this.getS2AccessToken(project.externalRef));
169169

170170
if (error) {
171171
logger.error("Failed getting S2 access token", { error });
172172
} else {
173-
s2Logs = {
174-
basin: env.S2_DEPLOYMENT_LOGS_BASIN_NAME,
175-
stream: `projects/${project.externalRef}/deployments/${deployment.shortCode}`,
176-
accessToken,
173+
eventStream = {
174+
s2: {
175+
basin: env.S2_DEPLOYMENT_LOGS_BASIN_NAME,
176+
stream: `projects/${project.externalRef}/deployments/${deployment.shortCode}`,
177+
accessToken,
178+
},
177179
};
178180
}
179181
}
180182

181183
return {
182-
s2Logs,
184+
eventStream,
183185
deployment: {
184186
id: deployment.id,
185187
shortCode: deployment.shortCode,

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { cn } from "~/utils/cn";
4040
import { v3DeploymentParams, v3DeploymentsPath, v3RunsPath } from "~/utils/pathBuilder";
4141
import { capitalizeWord } from "~/utils/string";
4242
import { UserTag } from "../_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route";
43+
import { DeploymentEventFromString } from "@trigger.dev/core/v3/schemas";
4344

4445
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
4546
const userId = await requireUserId(request);
@@ -48,15 +49,15 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
4849

4950
try {
5051
const presenter = new DeploymentPresenter();
51-
const { deployment, s2Logs } = await presenter.call({
52+
const { deployment, eventStream } = await presenter.call({
5253
userId,
5354
organizationSlug,
5455
projectSlug: projectParam,
5556
environmentSlug: envParam,
5657
deploymentShortCode: deploymentParam,
5758
});
5859

59-
return typedjson({ deployment, s2Logs });
60+
return typedjson({ deployment, eventStream });
6061
} catch (error) {
6162
console.error(error);
6263
throw new Response(undefined, {
@@ -69,18 +70,18 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
6970
type LogEntry = {
7071
message: string;
7172
timestamp: Date;
72-
level: "info" | "error" | "warn";
73+
level: "info" | "error" | "warn" | "debug";
7374
};
7475

7576
export default function Page() {
76-
const { deployment, s2Logs } = useTypedLoaderData<typeof loader>();
77+
const { deployment, eventStream } = useTypedLoaderData<typeof loader>();
7778
const organization = useOrganization();
7879
const project = useProject();
7980
const environment = useEnvironment();
8081
const location = useLocation();
8182
const page = new URLSearchParams(location.search).get("page");
8283

83-
const logsDisabled = s2Logs === undefined;
84+
const logsDisabled = eventStream === undefined;
8485
const [logs, setLogs] = useState<LogEntry[]>([]);
8586
const [isStreaming, setIsStreaming] = useState(true);
8687
const [streamError, setStreamError] = useState<string | null>(null);
@@ -97,9 +98,9 @@ export default function Page() {
9798

9899
const streamLogs = async () => {
99100
try {
100-
const s2 = new S2({ accessToken: s2Logs.accessToken });
101-
const basin = s2.basin(s2Logs.basin);
102-
const stream = basin.stream(s2Logs.stream);
101+
const s2 = new S2({ accessToken: eventStream.s2.accessToken });
102+
const basin = s2.basin(eventStream.s2.basin);
103+
const stream = basin.stream(eventStream.s2.stream);
103104

104105
const readSession = await stream.readSession(
105106
{
@@ -113,27 +114,49 @@ export default function Page() {
113114
const decoder = new TextDecoder();
114115

115116
for await (const record of readSession) {
116-
try {
117-
const headers: Record<string, string> = {};
118-
119-
if (record.headers) {
120-
for (const [nameBytes, valueBytes] of record.headers) {
121-
headers[decoder.decode(nameBytes)] = decoder.decode(valueBytes);
117+
const decoded = decoder.decode(record.body);
118+
const result = DeploymentEventFromString.safeParse(decoded);
119+
120+
if (!result.success) {
121+
// fallback to the previous format in s2 logs for compatibility
122+
try {
123+
const headers: Record<string, string> = {};
124+
125+
if (record.headers) {
126+
for (const [nameBytes, valueBytes] of record.headers) {
127+
headers[decoder.decode(nameBytes)] = decoder.decode(valueBytes);
128+
}
122129
}
130+
const level = (headers["level"]?.toLowerCase() as LogEntry["level"]) ?? "info";
131+
132+
setLogs((prevLogs) => [
133+
...prevLogs,
134+
{
135+
timestamp: new Date(record.timestamp),
136+
message: decoded,
137+
level,
138+
},
139+
]);
140+
} catch (err) {
141+
console.error("Failed to parse log record:", err);
123142
}
124-
const level = (headers["level"]?.toLowerCase() as LogEntry["level"]) ?? "info";
125-
126-
setLogs((prevLogs) => [
127-
...prevLogs,
128-
{
129-
timestamp: new Date(record.timestamp),
130-
message: decoder.decode(record.body),
131-
level,
132-
},
133-
]);
134-
} catch (err) {
135-
console.error("Failed to parse log record:", err);
143+
144+
continue;
136145
}
146+
147+
const event = result.data;
148+
if (event.type !== "log") {
149+
continue;
150+
}
151+
152+
setLogs((prevLogs) => [
153+
...prevLogs,
154+
{
155+
timestamp: new Date(record.timestamp),
156+
message: event.data.message,
157+
level: event.data.level,
158+
},
159+
]);
137160
}
138161
} catch (error) {
139162
if (abortController.signal.aborted) return;
@@ -158,7 +181,7 @@ export default function Page() {
158181
return () => {
159182
abortController.abort();
160183
};
161-
}, [s2Logs?.basin, s2Logs?.stream, s2Logs?.accessToken, isPending]);
184+
}, [eventStream?.s2?.basin, eventStream?.s2?.stream, eventStream?.s2?.accessToken, isPending]);
162185

163186
return (
164187
<div className="grid h-full max-h-full grid-rows-[2.5rem_1fr] overflow-hidden bg-background-bright">

packages/cli-v3/src/commands/deploy.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { getBranch, prepareDeploymentError, tryCatch } from "@trigger.dev/core/v
33
import {
44
InitializeDeploymentRequestBody,
55
InitializeDeploymentResponseBody,
6-
DeploymentEvent,
76
GitMeta,
87
DeploymentFinalizedEvent,
8+
DeploymentEventFromString,
99
} from "@trigger.dev/core/v3/schemas";
1010
import { Command, Option as CommandOption } from "commander";
1111
import { join, relative, resolve } from "node:path";
@@ -1314,15 +1314,3 @@ export function verifyDirectory(dir: string, projectPath: string) {
13141314
throw new Error(`Directory "${dir}" not found at ${projectPath}`);
13151315
}
13161316
}
1317-
1318-
const DeploymentEventFromString = z
1319-
.string()
1320-
.transform((s, ctx) => {
1321-
try {
1322-
return JSON.parse(s);
1323-
} catch {
1324-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Invalid JSON" });
1325-
return z.NEVER;
1326-
}
1327-
})
1328-
.pipe(DeploymentEvent);

packages/core/src/v3/schemas/api.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,18 @@ export type DeploymentEvent = z.infer<typeof DeploymentEvent>;
606606
export type DeploymentLogEvent = z.infer<typeof DeploymentLogEvent>;
607607
export type DeploymentFinalizedEvent = z.infer<typeof DeploymentFinalizedEvent>;
608608

609+
export const DeploymentEventFromString = z
610+
.string()
611+
.transform((s, ctx) => {
612+
try {
613+
return JSON.parse(s);
614+
} catch {
615+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Invalid JSON" });
616+
return z.NEVER;
617+
}
618+
})
619+
.pipe(DeploymentEvent);
620+
609621
export const CreateUploadPayloadUrlResponseBody = z.object({
610622
presignedUrl: z.string(),
611623
});

0 commit comments

Comments
 (0)