sentry-javascript icon indicating copy to clipboard operation
sentry-javascript copied to clipboard

TypeScript and source maps not working with webpack in Nx monorepo

Open lytaitruong opened this issue 3 years ago • 1 comments

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 package are you using?

@sentry/node

SDK Version

7.7.0

Framework Version

7.7.0

Link to Sentry event

https://sentry.io/organizations/testestetest/issues/3451337163/?project=6596330#exception

Steps to Reproduce

Hi, I'm try to using Sentry like as our React our website using to tracking bugs issue on production. Now we have integration with our BE site

Our BE site using Nx monorepo + NestJS platform and integration with Sentry via @ntegral/nestjs-sentry library

Platform

  • Node 16.x
  • @nestjs/core 8.0.0
  • fastify 4.3.0
  • @ntegral/nestjs-sentry: 3.0.7

We have follow to instruction of docs

  • https://docs.sentry.io/platforms/node/sourcemaps/
  • https://docs.sentry.io/platforms/node/typescript/
  • https://issuemode.com/issues/getsentry/sentry-javascript/90410055
  • https://github.com/getsentry/sentry-javascript/issues/2929
  • https://github.com/getsentry/sentry-javascript/issues/5196

But still don't know why it still happen in this case https://github.com/getsentry/sentry-javascript/issues/2929#issuecomment-698271942

Example Artifacts file upload via sentry-cli

Screen Shot 2022-07-25 at 00 25 51

Our webpack config

{
   entry: {
    main: [
      '/Users/lytaitruong/Projects/PRIVATE/nx-mono/apps/backend/src/main.ts'
    ]
  },
  devtool: 'source-map',
  mode: 'development',
  output: {
    path: '/Users/lytaitruong/Projects/PRIVATE/nx-mono/dist/apps/backend',
    filename: 'main.js',
    hashFunction: 'xxhash64',
    pathinfo: false,
    libraryTarget: 'commonjs'
  },
  module: { unsafeCache: true, rules: [ [Object] ] },
  resolve: {
    extensions: [ '.ts', '.tsx', '.mjs', '.js', '.jsx' ],
    alias: {},
    plugins: [ [TsconfigPathsPlugin] ],
    mainFields: [ 'es2015', 'module', 'main' ]
  },
  performance: { hints: false },
  plugins: [
    ForkTsCheckerWebpackPlugin { options: [Object] },
    CopyPlugin { patterns: [Array], options: {} },
    SentryCliPlugin {
      options: [Object],
      cli: [SentryCli],
      release: [Promise]
    }
  ],
  watch: true,
  watchOptions: { aggregateTimeout: 200, poll: undefined },
  stats: {
    hash: true,
    timings: false,
    cached: false,
    cachedAssets: false,
    modules: false,
    warnings: true,
    errors: true,
    colors: true,
    chunks: true,
    assets: false,
    chunkOrigins: false,
    chunkModules: false,
    children: false,
    reasons: false,
    version: false,
    errorDetails: false,
    moduleTrace: false,
    usedExports: false
  },
  experiments: { cacheUnaffected: true },
  target: 'node',
  node: false,
  externals: [ [Function (anonymous)] ]
}

Screen Shot 2022-07-25 at 00 41 55

I have create example repo in my github for you reproduce again on it. github repo

Expected Result

It should be generate file js or can open file via sourcemap

Actual Result

Screen Shot 2022-07-25 at 00 27 29 Screen Shot 2022-07-25 at 00 31 59

lytaitruong avatar Jul 24 '22 17:07 lytaitruong

Hi @lytaitruong and thanks for writing in!

Have you gone through our sourcemaps troubleshooting guide? If you haven't already, I recommend trying sentry-cli sourcemaps explain - maybe this will help you finding what's wrong. A lot of sourcemaps problems come up because of unmatching prefixes or filenames. Sometimes it's necessary to use the RewriteFrames Integration; sometimes it's enough to specify prefix or suffix options in Sentry CLI (or the webpack plugin).

Let me know if this helps!

Lms24 avatar Jul 25 '22 11:07 Lms24

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you label it Status: Backlog or Status: In Progress, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

github-actions[bot] avatar Aug 16 '22 00:08 github-actions[bot]

Hopefully @lytaitruong solved his troubles, still i want to share a solution and some explanation that should work in a similar context (Nx monorepo, Node.JS application, Webpack bundler, TSC compiler, workspace using apps and libs folders), as i spent quite some time figuring it out.

First thing to ensure this solution would work : read the stack trace from your error. If the in_app frame contains the path to the Typescript file and the called function, then you can forget about uploading your source maps, as the source maps are already used to resolve the source file path. Forcing Sentry to use source maps would only resolve to an invalid source file. Second thing, have a closer look at the path resolved in the stack trace, in my case the error generated from :

  • libraries errors are nested under the application (maybe because i am using non buildable libraries ?). example : at RateLimiterGuard.canActivate ($HOME/<NxWorkspaceName>/dist/apps/<projectName>/webpack:/<projectName>/libs/microservices/shared/guards/src/rate-limiter.guard.ts:126:11)
  • application errors are missing the apps prefix. example: at CertificatesController.getSupportedSchemas ($HOME/<NxWorkspaceName>/dist/apps/<projectName>/webpack:/<projectName>/src/app/certificates/certificates.controller.ts:186:11)

As you might notice the paths are not correctly resolved, and we will need to rewrite those paths so that Sentry can correctly resolve our source files.

I suggest the following approach, which as the main disadvantage to require knowledge of the project name which is running, in this example i am setting the name in a global metadata and reuse in the customStackParser function. This function is then assigned to the Sentry config, stackParser property (stackParser: [[10, customStackParser]])


function getFrameDirectory(frame: StackFrame, projectName?: string): string {
  /**
   * handle cases where the stack trace is located inside the dist folder
   * We start by removing the path inside the dist folder
   * and then we try to resolve the correct TS files path either for apps or libs
   **/
  if (frame.filename.includes('webpack:')) {
    const projectPath = () => {
      const preProjectPath = frame.filename.split('/webpack:/').pop();
      /**
       * for some reason, the stack trace path is located under the application project root for libs
       * '$HOME/<nx-workspace>//dist/apps/<app>/webpack:/<app>/libs/*.ts
       **/
      if (preProjectPath.includes('/libs/')) {
        return projectName
          ? preProjectPath?.split(`${projectName}/`).pop()
          : preProjectPath;
      }
      /**
       * for apps, the stack trace path is located under the project root and miss the apps prefix
       * '$HOME/<nx-workspace>/dist/apps/<app>/webpack:/<app>/*.ts
       **/
      return projectName
        ? path.join(
            'apps',
            projectName,
            preProjectPath?.split(`${projectName}/`).pop()
          )
        : path.join('apps', preProjectPath);
    };
    return path.dirname(path.resolve(projectPath()));
  }
  return path.dirname(frame.filename);
}

function renameFrameFilename(
  frame: StackFrame,
  root = process.cwd(),
  projectName?: string
): string {
  if (!frame.filename) {
    return '';
  }
  const filename = path.basename(frame.filename);
  const fileDir = getFrameDirectory(frame, projectName);
  const relativePath = path.relative(root, fileDir);
  return relativePath ? `${relativePath}/${filename}` : filename;
}

/**
 * This is a custom stack parser that ia used to rewrite the stack frames
 * so that Sentry can resolve the source file correctly.
 * It does NOT require to upload sourcemaps to Sentry.
 */
export function customStackParser(
  line: string,
  projectName: string // i suggest to set a default value using Reflect.getMetadata
): StackFrame | undefined {
  const [, parser] = nodeStackLineParser(getModuleFromFilename);
  const frame = parser(line);
  const filename = renameFrameFilename(frame, process.cwd(), projectName);
  frame.filename = filename;
  frame.abs_path = filename;
  return frame;
}

Hoping this will help someone.

getlarge avatar Aug 17 '23 07:08 getlarge