Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sourcemaps not working (Metro bundler) #3244

Closed
mikevercoelen opened this issue Aug 14, 2023 · 20 comments
Closed

Sourcemaps not working (Metro bundler) #3244

mikevercoelen opened this issue Aug 14, 2023 · 20 comments
Labels
Expo Issues related to Sentry React Native Expo support Platform: React-Native source-maps

Comments

@mikevercoelen
Copy link

mikevercoelen commented Aug 14, 2023

We're at the 3rd attempt right now to get Sentry sourcemaps working for using expo export --platform web, which is running Metro bundler for web.

The screenshot below is a result from running sentry-cli sourcemaps explain <eventId>

Screenshot_2023-08-14_at_3 41 20_PM

This is our setup:

package.json > scripts

        "exportWeb": "expo export --platform web --dump-sourcemap",
        "uploadWebSourceMaps": "export EXPO_PUBLIC_SENTRY_RELEASE=web@$(git rev-parse HEAD); node ./scripts/uploadWebSourceMaps.js",

Our CI/CD triggers npm run exportWeb && npm run uploadWebSourceMaps

This is uploadWebSourceMaps.js

// Inspired by @see https://github.com/getsentry/action-release/

const path = require('path');

const dotenv = require('dotenv');
dotenv.config();

const { getJsonFileContent, checkEnvVars } = require('./utils/cli');
const { DIST_PATH } = require('./utils/paths');
const { createReleaseAndUploadSourcemaps } = require('./utils/sentry/cli');

const METADATA_PATH = path.resolve(DIST_PATH, 'metadata.json');

async function main() {
    checkEnvVars([
        'EXPO_PUBLIC_SENTRY_ENVIRONMENT',
        'EXPO_PUBLIC_SENTRY_ORG',
        'EXPO_PUBLIC_SENTRY_PROJECT',
        'SENTRY_AUTH_TOKEN',
    ]);

    process.env['SENTRY_ORG'] = process.env.EXPO_PUBLIC_SENTRY_ORG;
    process.env['SENTRY_PROJECT'] = process.env.EXPO_PUBLIC_SENTRY_PROJECT;
    process.env['SENTRY_ENVIRONMENT'] = process.env.EXPO_PUBLIC_SENTRY_ENVIRONMENT;
    process.env['SENTRY_RELEASE'] = process.env.EXPO_PUBLIC_SENTRY_RELEASE;

    const metadata = await getJsonFileContent(METADATA_PATH);
    const bundleFileName = metadata.fileMetadata.web.bundle;
    const bundlePath = path.resolve(DIST_PATH, bundleFileName);

    await createReleaseAndUploadSourcemaps({
        bundlePath,
    });
}

void main();

This is utils/sentry/cli.js

const SentryCli = require('@sentry/cli');

const { checkEnvVars } = require('../cli');

let _cli;

function getCli() {
    process.env['SENTRY_PIPELINE'] = 'rocko';

    if (!_cli) {
        _cli = new SentryCli().releases;
    }

    return _cli;
}

async function createReleaseAndUploadSourcemaps({ bundlePath }) {
    checkEnvVars(['SENTRY_ORG', 'SENTRY_PROJECT', 'SENTRY_ENVIRONMENT', 'SENTRY_RELEASE', 'SENTRY_AUTH_TOKEN']);

    const cli = getCli();
    const version = process.env['SENTRY_RELEASE'];
    await cli.new(version, {
        projects: [process.env.EXPO_PUBLIC_SENTRY_PROJECT],
    });

    await cli.setCommits(version, {
        auto: true,
    });

    await cli.uploadSourceMaps(version, {
        include: [bundlePath],
    });

    await cli.newDeploy(version, {
        env: process.env['SENTRY_ENVIRONMENT'],
    });

    await cli.finalize(version);
}

module.exports = {
    getCli,
    createReleaseAndUploadSourcemaps,
};

Any help / advise would be great.

@kahest
Copy link
Member

kahest commented Aug 16, 2023

Hey @mikevercoelen, thanks for the detailled report, we'll look into this and follow up here. Please be aware that due to vacations and our hackweek next week, we will likely be a bit slower to respond to and resolve issues.

@josh-
Copy link

josh- commented Aug 21, 2023

Just flagging that this could also potentially be related to an issue raised here expo/sentry-expo#364 in which source maps are unable to be uploaded to Sentry since Expo v49.

I've added some excepts from our Expo Application Service build to that issue which includes this output from the sentry-cli.

@Kudo
Copy link

Kudo commented Aug 22, 2023

hi there!

since expo sdk 49, we changed the bundle command from react-native/cli.js bundle to @expo/cli/build/bin/cli export:embed. (#21396 #21397). however, the sentry-cli has the fixed bundle check.

does it have a way to customize or add export:embed to the checks in sentry-cli?

@sperrys
Copy link

sperrys commented Aug 27, 2023

@kahest , any ability to prioritize this would be appreciated. I imagine several other teams are also heavily impacted currently.

@krystofwoldrich
Copy link
Member

@mikevercoelen Thank you for the detailed message and the code samples. It looks like you are only uploading the bundle file but not its source map.

    const bundlePath = path.resolve(DIST_PATH, bundleFileName);

You can add the map file or just pass the bundles dir path.

    await cli.uploadSourceMaps(version, {
        include: [bundlePath, bundleMapPath],
    });

@krystofwoldrich
Copy link
Member

@Kudo Thank you for the link to the changes we will update @sentry/cli to check for the new @expo/cli command.

@krystofwoldrich
Copy link
Member

The original issue is about manual source maps upload, solution here.

The original issue is not related to the expo/sentry-expo#364.

@Kudo
Copy link

Kudo commented Aug 29, 2023

oh no sorry for posting to wrong issue and thanks @krystofwoldrich for helping for both two issues 👏

@mikevercoelen
Copy link
Author

@krystofwoldrich Applied the fix, verified it's uploading the source maps and bundle, still no working source maps, all events are still referring to scrambled source code.

@krystofwoldrich
Copy link
Member

krystofwoldrich commented Oct 2, 2023

@mikevercoelen Thank you for the message,
could you share a bit more details, specifically the output of the upload?

Have you checked that the events in Sentry have the same release and dist and the uploaded artifacts?

If your events have dist include the same value during the source map upload.

    await cli.uploadSourceMaps(version, {
        include: [bundlePath],
        dist: 'dist_value',
    });

@sem4phor
Copy link

is it planned to add docs about web sourcemaps and maybe a guide how to streamline web deployments? 🤔

@krystofwoldrich
Copy link
Member

@sem4phor Yes, adding web docs is part of our support for RN Web -> #2083

@krystofwoldrich
Copy link
Member

Since there are no new reactions to #3244 (comment) I'm closing this issue.

@felix-lambert
Copy link

felix-lambert commented Oct 20, 2023

Hello the sourcemap is still not being uploaded. Can we have instruction on how to upload the sourcemap with sentry? On older version of expo I didn't have this problem. It has now been a few months i'm using expo without sourcemaps

@krystofwoldrich
Copy link
Member

@felix-lambert Could you please open a new issue with details of our setup (Expo version, RN version, Sentry SDK version...)?

@felix-lambert
Copy link

felix-lambert commented Nov 1, 2023

@krystofwoldrich Hello I'm using sentry-expo. Do I still need to open an issue here? Also like to mention that the sourcemap seems to upload well on the web app version but not on android or ios

@krystofwoldrich
Copy link
Member

@felix-lambert We are working on #3262, so feel free to open the issue here.

@jer-sen
Copy link

jer-sen commented Dec 12, 2023

Here is a tested solution to fix sourcemaps on Web for expo@49.

Import this file (name it sentryInjection.web.js and create another empty file sentryInjection.js in the same folder) as the first import of your index.js. Its content (process.env.EXPO_PUBLIC_...) will be changed by metro (babel) during bundle so that it will set correctly global variables at runtime enabling Sentry to find the right uploaded sourcemap file.

/*
	We inject bundle id and release id as would Sentry bunder plugin do
	cf https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/packages/bundler-plugin-core/src/index.ts
	and https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/packages/bundler-plugin-core/src/utils.ts

  Code may be interpreted by Sentry to get bundle id and release id so we must keep original code as much as possible.
  Only exceptions are replacing the followings with pure literals:
  - process.env.EXPO_PUBLIC_SENTRY_DEBUG_ID
  - process.env.EXPO_PUBLIC_SENTRY_DEBUG_ID_IDENTIFIER
  - process.env.EXPO_PUBLIC_SENTRY_REALEASE_ID
*/

!(function () {
  try {
    var e =
        'undefined' != typeof window
          ? window
            'undefined' != typeof global
            ? global
              'undefined' != typeof self
              ? self
              : {},
      t = new Error().stack;
    t &&
      ((e._sentryDebugIds = e._sentryDebugIds || {}),
      (e._sentryDebugIds[t] = process.env.EXPO_PUBLIC_SENTRY_DEBUG_ID),
      (e._sentryDebugIdIdentifier = process.env.EXPO_PUBLIC_SENTRY_DEBUG_ID_IDENTIFIER));
  } catch (e) {}
})();
var _global =
  'undefined' !== typeof window
    ? window
    : 'undefined' !== typeof global
      ? global
      : 'undefined' !== typeof self
        ? self
        : {};
_global.SENTRY_RELEASE = {
  id: process.env.EXPO_PUBLIC_SENTRY_REALEASE_ID,
};

Then build your app with a script like this (ESM so name it build.mjs if you don't have "type": "module" in yourpackage.json). Feel free to use what you want for releaseId, here I'm using build date.

import { spawn } from 'node:child_process';
import * as crypto from 'node:crypto';
import * as fs from 'node:fs';
import * as path from 'node:path';
import SentryCli from '@sentry/cli';
import chalk from 'chalk';
import { z } from 'zod';

const DIST_PATH = './dist';

const releaseId = new Date().toISOString();
const debugId = crypto.randomUUID();

const log = (s: string) => {
  console.log('\n', chalk.yellowBright(s));
};

log('Setting Sentry env vars...');
process.env.EXPO_PUBLIC_SENTRY_DEBUG_ID = debugId;
process.env.EXPO_PUBLIC_SENTRY_DEBUG_ID_IDENTIFIER = `sentry-dbid-${debugId}`;
process.env.EXPO_PUBLIC_SENTRY_REALEASE_ID = releaseId;

log('Exporting for Web...');
const shell = spawn(
  process.platform === 'win32' ? 'yarn.cmd' : 'yarn',
  'expo export -p web --dump-sourcemap'.split(' '),
  {
    stdio: 'inherit',
  },
);
await new Promise((resolve: (code: number, signal: NodeJS.Signals) => void) => {
  shell.on('close', resolve);
});

log('Removing useless files...');
const manifestZod = z
  .object({
    version: z.number(),
    bundler: z.literal('metro'),
    fileMetadata: z
      .object({
        web: z
          .object({
            bundle: z.string().endsWith('.js'),
            assets: z.array(
              z
                .object({
                  path: z.string().startsWith('assets'),
                  ext: z.enum(['png', 'jpg', 'ttf']),
                })
                .strict(),
            ),
          })
          .strict(),
      })
      .strict(),
  })
  .strict();
const manifest = manifestZod.parse(
  JSON.parse(fs.readFileSync(path.join(DIST_PATH, 'metadata.json'), 'utf-8')),
);
fs.unlinkSync(path.join(DIST_PATH, 'metadata.json'));
fs.unlinkSync(path.join(DIST_PATH, 'debug.html'));
manifest.fileMetadata.web.assets.forEach((asset) => {
  fs.unlinkSync(path.join(DIST_PATH, asset.path));
});

log('Uploading source maps...');
const cli = new SentryCli(null, { org: 'your_sentry_org', project: 'your_sentry_project' }).releases;
await cli.new(releaseId);
await cli.uploadSourceMaps(releaseId, { include: [DIST_PATH] });
await cli.finalize(releaseId);

log('Removing source maps...');
fs.unlinkSync(path.join(DIST_PATH, manifest.fileMetadata.web.bundle.replace(/\.js$/u, '.map')));

log(`Version ${releaseId} of app Web ready to deploy!`);

@mikevercoelen
Copy link
Author

mikevercoelen commented Dec 12, 2023

@jer-sen How do you guys initialize Sentry? By using Sentry.init from sentry-expo ?

If so what are the release and dist params and the rest of the config? Are you guys using RewriteFrames etc?

Also running expo export -p web does not export source maps, it needs --dump-sourcemap so I'm a bit confused how this script is working for you guys

@jer-sen
Copy link

jer-sen commented Dec 12, 2023

@mikevercoelen on Web we use Sentry.init from import * as Sentry from '@sentry/react';.
We don't set release nor dist, Sentry will automatically use injected window.SENTRY_RELEASE.
We don't use RewriteFrames.

You are right for --dump-sourcemap, I replaced a call to an nx executor to help the readers but I forgot to add this flag. Comment edited!

Note also that it's only working for expo@49 (cf https://docs.expo.dev/guides/environment-variables/)

@kahest kahest added the Expo Issues related to Sentry React Native Expo support label Jan 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Expo Issues related to Sentry React Native Expo support Platform: React-Native source-maps
Projects
Archived in project
Archived in project
Development

No branches or pull requests

9 participants