getsentry / sentry-javascript

Official Sentry SDKs for JavaScript
https://sentry.io
MIT License
7.99k stars 1.57k forks source link

[Remix] Can't get source mapping to work consistently #10548

Open benjamindulau opened 9 months ago

benjamindulau commented 9 months ago

Is there an existing issue for this?

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",
}
  1. 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
  },
});
  1. 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:

![Capture d’écran 2024-02-07 à 11 02 12](https://github.com/getsentry/sentry-javascript/assets/430689/82c0c245-8add-4483-81f5-dbc37207adc6)

## Server (lambda function) error event example:

![Capture d’écran 2024-01-24 à 15 32 47](https://github.com/getsentry/sentry-javascript/assets/430689/e26ce900-ff7c-4ba4-afd8-355d401f8918)
lforst commented 9 months ago

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!

benjamindulau commented 9 months ago

@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!

Capture d’écran 2024-02-07 à 16 52 38

lforst commented 9 months ago

@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
benjamindulau commented 9 months ago

@lforst Already tried that in the past. Tried again and this seems to upload only sourcemap files:

Capture d’écran 2024-02-07 à 18 09 02 Capture d’écran 2024-02-07 à 18 10 30
lforst commented 9 months ago

@szokeasaurusrex Would you mind checking whether we're picking up .mjs files with sentry-cli sourcemaps upload? If not we definitely should!