sentry-javascript
sentry-javascript copied to clipboard
[Remix] Can't get source mapping to work consistently
Is there an existing issue for this?
- [X] I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- [X] I have reviewed the documentation https://docs.sentry.io/
- [X] I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/remix
SDK Version
7.93
Framework Version
7.79
Link to Sentry event
https://hello-travel.sentry.io/issues/4949583427/events/236fed44801a4b4f8b4ad78bd27886b2/
SDK Setup
Client side configuration
// file: app/entry.client.tsx
import * as Sentry from "@sentry/remix";
Sentry.init({
dsn: "xxxxxxx",
release: getPublicEnv("SENTRY_RELEASE"),
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.remixRouterInstrumentation(
useEffect,
useLocation,
useMatches,
),
}),
// Replay is only available in the client
new Sentry.Replay(),
],
environment: process.env.NODE_ENV,
debug: false,
enabled: "development" !== process.env.NODE_ENV,
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1,
// Set `tracePropagationTargets` to control for which URLs distribud tracing should be enabled
tracePropagationTargets: [
"localhost",
/^http:\/\/dev\.socialtrip\.com:3333/,
getPublicEnv("SITE_URL"),
],
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 1,
});
// file: app/root.tsx
import { withSentry } from "@sentry/remix";
function App() {
// ....
}
export default withSentry(App, {
errorBoundaryOptions: {
fallback: ErrorBoundary,
},
});
// file: app/entry.server.tsx
import * as Sentry from "@sentry/remix";
// Init sentry
Sentry.init({
dsn: "xxxxx",
release: getPublicEnv("SENTRY_RELEASE"),
environment: process.env.NODE_ENV,
debug: false,
enabled: "development" !== process.env.NODE_ENV,
denyUrls: [
/\/build\//,
/\/favicons\//,
/\/img\//,
/\/fonts\//,
/\/favicon.ico/,
/\/site\.webmanifest/,
],
integrations: [new Sentry.Integrations.Http({ tracing: true })],
tracesSampleRate: 1,
});
// Remix stuff....
// function we export for Remix so it can handle errors
export function handleError(
error: unknown,
{ request }: DataFunctionArgs,
): void {
if (error instanceof Error) {
Sentry.captureRemixServerException(error, "remix.server", request);
} else {
Sentry.captureException(error);
}
}
Server side (lambda) configuration:
// in our lambda handler file
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.SENTRY_RELEASE,
environment: process.env.NODE_ENV,
enabled: true,
debug: false,
tracesSampleRate: 1,
});
// function we use to capture errors in try/catch
function captureError(
error: unknown,
event: APIGatewayProxyEventV2,
context: Context,
) {
console.error(error);
Sentry.configureScope((scope) => {
scope.setExtra("awsRequestId", event.requestContext.requestId);
scope.setExtra("awsRequestURL", createURLFromEvent(event));
});
Sentry.captureException(error);
}
// our try/catch block is like:
try {
// lambda/remix stuff...
} catch (error) {
captureError(error, event, context);
writeErrorToStream(streamResponse, 500, "Internal Server Error");
} finally {
await Sentry.flush(2000);
streamResponse.end();
}
Deploy flow
Scripts used in package.json:
"scripts": {
"build": "NODE_ENV=production remix build --sourcemap && tsx bin/build-lambda-server.ts",
"deploy": "tsx bin/deploy.ts",
}
- Build remix bundles:
npm run build
The script bin/build-lambda-server.ts creates a new build with esbuild (and sourcemap enabled) for our lambda function, something like:
// file: bin/build-lambda-server.ts
const result = await esbuild.build({
entryPoints: [`${paths.serverDir}/server.lambda.prod.ts`],
bundle: true,
minify: true,
sourcemap: true,
metafile: true,
platform: "node",
format: "esm",
target: "esnext",
outfile: `${paths.serverBundle}/index.mjs`,
outExtension: { ".js": ".mjs" },
banner: {
js: 'import { createRequire } from "module";const require = createRequire(import.meta.url);',
},
define: {
__SENTRY_DEBUG__: "false",
__RRWEB_EXCLUDE_IFRAME__: "true",
__RRWEB_EXCLUDE_SHADOW_DOM__: "true",
__SENTRY_EXCLUDE_REPLAY_WORKER__: "true",
"process.env.NODE_ENV": '"production"',
},
logLevel: "info",
});
// file: server/server.lambda.prod.ts
export const handler = createStreamRequestHandler({
build: build as any, // this is the remix server build
mode: process.env.NODE_ENV,
getLoadContext: async (request: Request) => {
// specific stuff
},
});
- Deploy both client bundle & lambda function bundle:
npm run deploy
// file bin/deploy.ts
const remixPublicPath = "/_static/"; // same as in remix.config.js publicPath property
const release = execSync("git rev-parse HEAD")
.toString()
.trim()
.substring(0, 12);
// 1. .... build an archive for the lambda
// 2. Send both client & server bundle sourcemaps to Sentry
// client bundle sourcemap is handled by sentry
execSync(
`npx sentry-upload-sourcemaps --org hello-travel --project remix --release ${release} --urlPrefix "~${remixPublicPath}"`,
{ cwd: paths.appDir },
);
// server bundle sourcemap is sent manually
execSync(
`npx sentry-cli sourcemaps upload --org=hello-travel --project=remix --release=${release} --url-prefix="~/var/task" ./server/build/index.mjs.map`,
{
cwd: paths.appDir,
},
);
// 3. other stuff not relevant to sentry (like updating lambda function code, cleanup, etc...)
Steps to Reproduce
Well, see the detailed code samples
Expected Result
Having traces with sourcemapping for both client and server errors.
Please note that I'm pretty sure I saw it working once for client errors. Didn't do any modification since then though....
Actual Result
Client error event example:
Server (lambda function) error event example:
For the client event you shared: hello-travel.sentry.io/issues/4949583427/events/236fed44801a4b4f8b4ad78bd27886b2, you did not upload the needed source map. "Sourcemap reference" in the artifact indicates what file this source map file would require. In general it is the //# sourceMapURL= comment at the end of the file. It is not in the uploaded artifacts.
As for the lambda event, would you mind also sharing a link? Thanks!
@lforst Yeah, that what we figured out after I posted the issue. I knew I got it working before!
It seems that there is something going on with some deploys not sending the sourcemaps for the client bundle. We're currently inspecting the issue on this...
As for the lambda handler, I'll get back at you on monday with more information (I'm on a break for the rest of the week). But if you take a look at the following screenshot, I suspect that we need to upload the /var/task/index.mjs source bundle file which references the sourcemap along with the sourcemap....
But how can we do that using the sentry-cli? I managed to upload the sourcemap, but don't know how to send the source code along with it in the same artifact bundle... Can you point me in the right direction about that?
Thanks!
@benjamindulau Correct. You always need to upload both, the generated file and its source map (so the corresponding index.mjs for the lambda for example). Generally you just need to point the CLI to where your built files are located.
You are currently only pointing it to one file:
npx sentry-cli sourcemaps upload --org=hello-travel --project=remix --release=${release} --url-prefix="~/var/task" ./server/build/index.mjs.map
instead you should point it at the entire folder:
npx sentry-cli sourcemaps upload --org=hello-travel --project=remix --release=${release} --url-prefix="~/var/task" ./server/build
@lforst Already tried that in the past. Tried again and this seems to upload only sourcemap files:
@szokeasaurusrex Would you mind checking whether we're picking up .mjs files with sentry-cli sourcemaps upload? If not we definitely should!