Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
14 changes: 13 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,20 @@ RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpx prisma@

## Builder (builds the webapp)
FROM base AS builder
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
# Remove SENTRY_AUTH_TOKEN from build args since we'll use secrets
ENV SENTRY_RELEASE=${SENTRY_RELEASE} \
SENTRY_ORG=${SENTRY_ORG} \
SENTRY_PROJECT=${SENTRY_PROJECT}
# Remove SENTRY_AUTH_TOKEN from env vars since it will be mounted as secret

# 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 +70,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
Loading