Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/publish-webapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,8 @@ jobs:
BUILD_GIT_SHA=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
BUILD_GIT_REF_NAME=${{ steps.set_build_info.outputs.BUILD_GIT_REF_NAME }}
BUILD_TIMESTAMP_SECONDS=${{ steps.set_build_info.outputs.BUILD_TIMESTAMP_SECONDS }}
SENTRY_RELEASE=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
SENTRY_ORG=triggerdev
SENTRY_PROJECT=trigger-cloud
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
19 changes: 16 additions & 3 deletions apps/webapp/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "./components/primitives/OperatingSystemProvider";
import { singleton } from "./utils/singleton";
import { bootstrap } from "./bootstrap";
import { wrapHandleErrorWithSentry } from "@sentry/remix";

const ABORT_DELAY = 30000;

Expand Down Expand Up @@ -170,9 +171,21 @@ function handleBrowserRequest(
});
}

export function handleError(error: unknown, { request, params, context }: DataFunctionArgs) {
logError(error, request);
}
export const handleError = wrapHandleErrorWithSentry((error, { request }) => {
if (request instanceof Request) {
logger.error("Error in handleError", {
error,
request: {
url: request.url,
method: request.method,
},
});
} else {
logger.error("Error in handleError", {
error,
});
}
});

Worker.init().catch((error) => {
logError(error);
Expand Down
12 changes: 12 additions & 0 deletions apps/webapp/app/routes/admin.api.v1.simulate-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type DataFunctionArgs } from "@remix-run/node";
import { requireUser } from "~/services/session.server";

export async function loader({ request }: DataFunctionArgs) {
const user = await requireUser(request);

if (!user.admin) {
throw new Response("You must be an admin to perform this action", { status: 403 });
}

throw new Error("Test error");
}
36 changes: 36 additions & 0 deletions apps/webapp/app/services/logger.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,49 @@ import { Logger } from "@trigger.dev/core/logger";
import { sensitiveDataReplacer } from "./sensitiveDataReplacer";
import { AsyncLocalStorage } from "async_hooks";
import { getHttpContext } from "./httpAsyncStorage.server";
import { captureException, captureMessage } from "@sentry/remix";

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

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

Logger.onError = (message, ...args) => {
const error = extractErrorFromArgs(args);

if (error) {
captureException(error, {
extra: {
message,
...flattenArgs(args),
},
});
} else {
captureMessage(message, {
level: "error",
extra: flattenArgs(args),
});
}
};

function extractErrorFromArgs(args: Array<Record<string, unknown> | undefined>) {
for (const arg of args) {
if (arg && "error" in arg && arg.error instanceof Error) {
return arg.error;
}
}
return;
}

function flattenArgs(args: Array<Record<string, unknown> | undefined>) {
return args.reduce((acc, arg) => {
if (arg) {
return { ...acc, ...arg };
}
return acc;
}, {});
}
export const logger = new Logger(
"webapp",
(process.env.APP_LOG_LEVEL ?? "debug") as LogLevel,
Expand Down
18 changes: 9 additions & 9 deletions apps/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"version": "1.0.0",
"sideEffects": false,
"scripts": {
"build": "run-s build:**",
"build": "run-s build:** && pnpm run upload:sourcemaps",
"build:db:seed": "esbuild --platform=node --bundle --minify --format=cjs ./prisma/seed.ts --outdir=prisma",
"build:remix": "remix build",
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build",
"build:remix": "remix build --sourcemap",
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build --sourcemap",
"build:sentry": "esbuild --platform=node --format=cjs ./sentry.server.ts --outdir=build --sourcemap",
"dev": "cross-env PORT=3030 remix dev -c \"node ./build/server.js\"",
"dev:worker": "cross-env NODE_PATH=../../node_modules/.pnpm/node_modules node ./build/server.js",
"format": "prettier --write .",
Expand All @@ -19,10 +20,7 @@
"db:seed:local": "ts-node prisma/seed.ts",
"build:db:populate": "esbuild --platform=node --bundle --minify --format=cjs ./prisma/populate.ts --outdir=prisma",
"db:populate": "node prisma/populate.js --",
"generate:sourcemaps": "remix build --sourcemap",
"clean:sourcemaps": "run-s clean:sourcemaps:*",
"clean:sourcemaps:public": "rimraf ./build/**/*.map",
"clean:sourcemaps:build": "rimraf ./public/build/**/*.map",
"upload:sourcemaps": "bash ./upload-sourcemaps.sh",
"test": "vitest --no-file-parallelism",
"eval:dev": "evalite watch"
},
Expand Down Expand Up @@ -103,6 +101,8 @@
"@remix-run/serve": "2.1.0",
"@remix-run/server-runtime": "2.1.0",
"@remix-run/v1-meta": "^0.1.3",
"@sentry/node-native": "^9.40.0",
"@sentry/remix": "^9.40.0",
"@slack/web-api": "7.9.1",
"@socket.io/redis-adapter": "^8.3.0",
"@splinetool/react-spline": "^2.2.6",
Expand Down Expand Up @@ -142,7 +142,6 @@
"express": "4.20.0",
"framer-motion": "^10.12.11",
"graphile-worker": "0.16.6",
"highlight.run": "^7.3.4",
"humanize-duration": "^3.27.3",
"input-otp": "^1.4.2",
"intl-parse-accept-language": "^1.0.0",
Expand Down Expand Up @@ -218,6 +217,7 @@
"@remix-run/testing": "^2.1.0",
"@swc/core": "^1.3.4",
"@swc/helpers": "^0.4.11",
"@sentry/cli": "2.50.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@total-typescript/ts-reset": "^0.4.2",
Expand Down Expand Up @@ -279,4 +279,4 @@
"engines": {
"node": ">=16.0.0"
}
}
}
27 changes: 27 additions & 0 deletions apps/webapp/sentry.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as Sentry from "@sentry/remix";
import { eventLoopBlockIntegration } from "@sentry/node-native";

if (process.env.SENTRY_DSN) {
console.log("🔭 Initializing Sentry");

Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.BUILD_GIT_SHA,

// Adds request headers and IP for users, for more info visit: and captures action formData attributes
// https://docs.sentry.io/platforms/javascript/guides/remix/configuration/options/#sendDefaultPii
sendDefaultPii: false,

skipOpenTelemetrySetup: true,
registerEsmLoaderHooks: false,
disableInstrumentationWarnings: true,

maxBreadcrumbs: 0,
shutdownTimeout: 10,

serverName: process.env.SERVICE_NAME,
environment: process.env.APP_ENV,

integrations: [eventLoopBlockIntegration({ threshold: 1000 })],
});
}
2 changes: 2 additions & 0 deletions apps/webapp/server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "./sentry.server";

import { createRequestHandler } from "@remix-run/express";
import { broadcastDevReady, logDevReady } from "@remix-run/server-runtime";
import compression from "compression";
Expand Down
13 changes: 13 additions & 0 deletions apps/webapp/upload-sourcemaps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
set -eo pipefail

if [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_PROJECT" ] && [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_RELEASE" ]; then
sentry-cli releases new $SENTRY_RELEASE
sentry-cli sourcemaps inject ./build
sentry-cli sourcemaps upload ./build --release $SENTRY_RELEASE
# Now we need to delete the sourcemaps from the build directory
rm -rf ./build/*.map
else
echo "Skipping sourcemap upload: Missing required environment variables"
echo "Required: SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN, SENTRY_RELEASE"
fi
13 changes: 12 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,19 @@ RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpx prisma@

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

ARG SENTRY_RELEASE
ARG SENTRY_ORG
ARG SENTRY_PROJECT
ENV SENTRY_RELEASE=${SENTRY_RELEASE} \
SENTRY_ORG=${SENTRY_ORG} \
SENTRY_PROJECT=${SENTRY_PROJECT}

# Goose and schemas
COPY --from=goose_builder /go/bin/goose /usr/local/bin/goose
RUN chmod +x /usr/local/bin/goose
Expand All @@ -60,7 +69,9 @@ RUN chmod +x ./scripts/entrypoint.sh
COPY --chown=node:node .configs/tsconfig.base.json .configs/tsconfig.base.json
COPY --chown=node:node scripts/updateVersion.ts scripts/updateVersion.ts
RUN pnpm run generate
RUN pnpm run build --filter=webapp...
RUN --mount=type=secret,id=sentry_auth_token \
SENTRY_AUTH_TOKEN=$(cat /run/secrets/sentry_auth_token) \
pnpm run build --filter=webapp...

# Runner
FROM ${NODE_IMAGE} AS runner
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export class Logger {
#jsonReplacer?: (key: string, value: unknown) => unknown;
#additionalFields: () => Record<string, unknown>;

// Add a static "onError" method that will be called when an error is logged
static onError: (message: string, ...args: Array<Record<string, unknown> | undefined>) => void;

constructor(
name: string,
level: LogLevel = "info",
Expand Down Expand Up @@ -67,6 +70,10 @@ export class Logger {
if (this.#level < 1) return;

this.#structuredLog(console.error, message, "error", ...args);

if (Logger.onError) {
Logger.onError(message, ...args);
}
}

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