Skip to content

Commit 09d0e80

Browse files
authored
Add sentry error reporting (#2309)
* Sentry WIP * Configure sentry for uploading and releasing during the publish webapp step * Delete source maps after uploading * Forward logger.error calls to sentry through Logger.onError * Couple tweaks to the dockerfile
1 parent 74808d7 commit 09d0e80

File tree

16 files changed

+965
-80
lines changed

16 files changed

+965
-80
lines changed

.github/workflows/publish-webapp.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,8 @@ jobs:
8686
BUILD_GIT_SHA=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
8787
BUILD_GIT_REF_NAME=${{ steps.set_build_info.outputs.BUILD_GIT_REF_NAME }}
8888
BUILD_TIMESTAMP_SECONDS=${{ steps.set_build_info.outputs.BUILD_TIMESTAMP_SECONDS }}
89+
SENTRY_RELEASE=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
90+
SENTRY_ORG=triggerdev
91+
SENTRY_PROJECT=trigger-cloud
92+
secrets: |
93+
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}

apps/webapp/app/entry.server.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from "./components/primitives/OperatingSystemProvider";
1717
import { singleton } from "./utils/singleton";
1818
import { bootstrap } from "./bootstrap";
19+
import { wrapHandleErrorWithSentry } from "@sentry/remix";
1920

2021
const ABORT_DELAY = 30000;
2122

@@ -170,9 +171,21 @@ function handleBrowserRequest(
170171
});
171172
}
172173

173-
export function handleError(error: unknown, { request, params, context }: DataFunctionArgs) {
174-
logError(error, request);
175-
}
174+
export const handleError = wrapHandleErrorWithSentry((error, { request }) => {
175+
if (request instanceof Request) {
176+
logger.error("Error in handleError", {
177+
error,
178+
request: {
179+
url: request.url,
180+
method: request.method,
181+
},
182+
});
183+
} else {
184+
logger.error("Error in handleError", {
185+
error,
186+
});
187+
}
188+
});
176189

177190
Worker.init().catch((error) => {
178191
logError(error);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { type DataFunctionArgs } from "@remix-run/node";
2+
import { requireUser } from "~/services/session.server";
3+
4+
export async function loader({ request }: DataFunctionArgs) {
5+
const user = await requireUser(request);
6+
7+
if (!user.admin) {
8+
throw new Response("You must be an admin to perform this action", { status: 403 });
9+
}
10+
11+
throw new Error("Test error");
12+
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,49 @@ import { Logger } from "@trigger.dev/core/logger";
33
import { sensitiveDataReplacer } from "./sensitiveDataReplacer";
44
import { AsyncLocalStorage } from "async_hooks";
55
import { getHttpContext } from "./httpAsyncStorage.server";
6+
import { captureException, captureMessage } from "@sentry/remix";
67

78
const currentFieldsStore = new AsyncLocalStorage<Record<string, unknown>>();
89

910
export function trace<T>(fields: Record<string, unknown>, fn: () => T): T {
1011
return currentFieldsStore.run(fields, fn);
1112
}
1213

14+
Logger.onError = (message, ...args) => {
15+
const error = extractErrorFromArgs(args);
16+
17+
if (error) {
18+
captureException(error, {
19+
extra: {
20+
message,
21+
...flattenArgs(args),
22+
},
23+
});
24+
} else {
25+
captureMessage(message, {
26+
level: "error",
27+
extra: flattenArgs(args),
28+
});
29+
}
30+
};
31+
32+
function extractErrorFromArgs(args: Array<Record<string, unknown> | undefined>) {
33+
for (const arg of args) {
34+
if (arg && "error" in arg && arg.error instanceof Error) {
35+
return arg.error;
36+
}
37+
}
38+
return;
39+
}
40+
41+
function flattenArgs(args: Array<Record<string, unknown> | undefined>) {
42+
return args.reduce((acc, arg) => {
43+
if (arg) {
44+
return { ...acc, ...arg };
45+
}
46+
return acc;
47+
}, {});
48+
}
1349
export const logger = new Logger(
1450
"webapp",
1551
(process.env.APP_LOG_LEVEL ?? "debug") as LogLevel,

apps/webapp/package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
"version": "1.0.0",
55
"sideEffects": false,
66
"scripts": {
7-
"build": "run-s build:**",
7+
"build": "run-s build:** && pnpm run upload:sourcemaps",
88
"build:db:seed": "esbuild --platform=node --bundle --minify --format=cjs ./prisma/seed.ts --outdir=prisma",
9-
"build:remix": "remix build",
10-
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build",
9+
"build:remix": "remix build --sourcemap",
10+
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build --sourcemap",
11+
"build:sentry": "esbuild --platform=node --format=cjs ./sentry.server.ts --outdir=build --sourcemap",
1112
"dev": "cross-env PORT=3030 remix dev -c \"node ./build/server.js\"",
1213
"dev:worker": "cross-env NODE_PATH=../../node_modules/.pnpm/node_modules node ./build/server.js",
1314
"format": "prettier --write .",
@@ -19,10 +20,7 @@
1920
"db:seed:local": "ts-node prisma/seed.ts",
2021
"build:db:populate": "esbuild --platform=node --bundle --minify --format=cjs ./prisma/populate.ts --outdir=prisma",
2122
"db:populate": "node prisma/populate.js --",
22-
"generate:sourcemaps": "remix build --sourcemap",
23-
"clean:sourcemaps": "run-s clean:sourcemaps:*",
24-
"clean:sourcemaps:public": "rimraf ./build/**/*.map",
25-
"clean:sourcemaps:build": "rimraf ./public/build/**/*.map",
23+
"upload:sourcemaps": "bash ./upload-sourcemaps.sh",
2624
"test": "vitest --no-file-parallelism",
2725
"eval:dev": "evalite watch"
2826
},
@@ -103,6 +101,8 @@
103101
"@remix-run/serve": "2.1.0",
104102
"@remix-run/server-runtime": "2.1.0",
105103
"@remix-run/v1-meta": "^0.1.3",
104+
"@sentry/node-native": "^9.40.0",
105+
"@sentry/remix": "^9.40.0",
106106
"@slack/web-api": "7.9.1",
107107
"@socket.io/redis-adapter": "^8.3.0",
108108
"@splinetool/react-spline": "^2.2.6",
@@ -142,7 +142,6 @@
142142
"express": "4.20.0",
143143
"framer-motion": "^10.12.11",
144144
"graphile-worker": "0.16.6",
145-
"highlight.run": "^7.3.4",
146145
"humanize-duration": "^3.27.3",
147146
"input-otp": "^1.4.2",
148147
"intl-parse-accept-language": "^1.0.0",
@@ -218,6 +217,7 @@
218217
"@remix-run/testing": "^2.1.0",
219218
"@swc/core": "^1.3.4",
220219
"@swc/helpers": "^0.4.11",
220+
"@sentry/cli": "2.50.2",
221221
"@tailwindcss/forms": "^0.5.3",
222222
"@tailwindcss/typography": "^0.5.9",
223223
"@total-typescript/ts-reset": "^0.4.2",
@@ -279,4 +279,4 @@
279279
"engines": {
280280
"node": ">=16.0.0"
281281
}
282-
}
282+
}

apps/webapp/sentry.server.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as Sentry from "@sentry/remix";
2+
import { eventLoopBlockIntegration } from "@sentry/node-native";
3+
4+
if (process.env.SENTRY_DSN) {
5+
console.log("🔭 Initializing Sentry");
6+
7+
Sentry.init({
8+
dsn: process.env.SENTRY_DSN,
9+
release: process.env.BUILD_GIT_SHA,
10+
11+
// Adds request headers and IP for users, for more info visit: and captures action formData attributes
12+
// https://docs.sentry.io/platforms/javascript/guides/remix/configuration/options/#sendDefaultPii
13+
sendDefaultPii: false,
14+
15+
skipOpenTelemetrySetup: true,
16+
registerEsmLoaderHooks: false,
17+
disableInstrumentationWarnings: true,
18+
19+
maxBreadcrumbs: 0,
20+
shutdownTimeout: 10,
21+
22+
serverName: process.env.SERVICE_NAME,
23+
environment: process.env.APP_ENV,
24+
25+
integrations: [eventLoopBlockIntegration({ threshold: 1000 })],
26+
});
27+
}

apps/webapp/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import "./sentry.server";
2+
13
import { createRequestHandler } from "@remix-run/express";
24
import { broadcastDevReady, logDevReady } from "@remix-run/server-runtime";
35
import compression from "compression";

apps/webapp/upload-sourcemaps.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
set -eo pipefail
3+
4+
if [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_PROJECT" ] && [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_RELEASE" ]; then
5+
sentry-cli releases new $SENTRY_RELEASE
6+
sentry-cli sourcemaps inject ./build
7+
sentry-cli sourcemaps upload ./build --release $SENTRY_RELEASE
8+
# Now we need to delete the sourcemaps from the build directory
9+
rm -rf ./build/*.map
10+
else
11+
echo "Skipping sourcemap upload: Missing required environment variables"
12+
echo "Required: SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN, SENTRY_RELEASE"
13+
fi

docker/Dockerfile

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,19 @@ RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpx prisma@
4242

4343
## Builder (builds the webapp)
4444
FROM base AS builder
45+
# This is needed for the sentry-cli binary while building the webapp
46+
RUN apt-get update && apt-get install -y openssl dumb-init ca-certificates
4547
WORKDIR /triggerdotdev
4648
# Corepack is used to install pnpm
4749
RUN corepack enable
4850

51+
ARG SENTRY_RELEASE
52+
ARG SENTRY_ORG
53+
ARG SENTRY_PROJECT
54+
ENV SENTRY_RELEASE=${SENTRY_RELEASE} \
55+
SENTRY_ORG=${SENTRY_ORG} \
56+
SENTRY_PROJECT=${SENTRY_PROJECT}
57+
4958
# Goose and schemas
5059
COPY --from=goose_builder /go/bin/goose /usr/local/bin/goose
5160
RUN chmod +x /usr/local/bin/goose
@@ -60,7 +69,9 @@ RUN chmod +x ./scripts/entrypoint.sh
6069
COPY --chown=node:node .configs/tsconfig.base.json .configs/tsconfig.base.json
6170
COPY --chown=node:node scripts/updateVersion.ts scripts/updateVersion.ts
6271
RUN pnpm run generate
63-
RUN pnpm run build --filter=webapp...
72+
RUN --mount=type=secret,id=sentry_auth_token \
73+
SENTRY_AUTH_TOKEN=$(cat /run/secrets/sentry_auth_token) \
74+
pnpm run build --filter=webapp...
6475

6576
# Runner
6677
FROM ${NODE_IMAGE} AS runner

packages/core/src/logger.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export class Logger {
2323
#jsonReplacer?: (key: string, value: unknown) => unknown;
2424
#additionalFields: () => Record<string, unknown>;
2525

26+
// Add a static "onError" method that will be called when an error is logged
27+
static onError: (message: string, ...args: Array<Record<string, unknown> | undefined>) => void;
28+
2629
constructor(
2730
name: string,
2831
level: LogLevel = "info",
@@ -67,6 +70,10 @@ export class Logger {
6770
if (this.#level < 1) return;
6871

6972
this.#structuredLog(console.error, message, "error", ...args);
73+
74+
if (Logger.onError) {
75+
Logger.onError(message, ...args);
76+
}
7077
}
7178

7279
warn(message: string, ...args: Array<Record<string, unknown> | undefined>) {

0 commit comments

Comments
 (0)