Skip to content

feat(nuxt): Add filter for not found source maps (devtools) #14437

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

Merged
merged 4 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
64 changes: 43 additions & 21 deletions packages/nuxt/src/server/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,52 @@ export function init(options: SentryNuxtServerOptions): Client | undefined {

const client = initNode(sentryOptions);

getGlobalScope().addEventProcessor(
Object.assign(
(event => {
if (event.type === 'transaction') {
// Filter out transactions for Nuxt build assets
// This regex matches the default path to the nuxt-generated build assets (`_nuxt`).
// todo: the buildAssetDir could be changed in the nuxt config - change this to a more generic solution
if (event.transaction?.match(/^GET \/_nuxt\//)) {
options.debug &&
DEBUG_BUILD &&
logger.log('NuxtLowQualityTransactionsFilter filtered transaction: ', event.transaction);
return null;
}
getGlobalScope().addEventProcessor(lowQualityTransactionsFilter(options));
getGlobalScope().addEventProcessor(clientSourceMapErrorFilter(options));

return event;
} else {
return event;
}
}) satisfies EventProcessor,
{ id: 'NuxtLowQualityTransactionsFilter' },
),
return client;
}

/**
* Filter out transactions for Nuxt build assets
* This regex matches the default path to the nuxt-generated build assets (`_nuxt`).
*
* Only exported for testing
*/
export function lowQualityTransactionsFilter(options: SentryNuxtServerOptions): EventProcessor {
return Object.assign(
(event => {
if (event.type === 'transaction' && event.transaction?.match(/^GET \/_nuxt\//)) {
// todo: the buildAssetDir could be changed in the nuxt config - change this to a more generic solution
options.debug &&
DEBUG_BUILD &&
logger.log('NuxtLowQualityTransactionsFilter filtered transaction: ', event.transaction);
return null;
} else {
return event;
}
}) satisfies EventProcessor,
{ id: 'NuxtLowQualityTransactionsFilter' },
);
}

return client;
/**
* The browser devtools try to get the source maps, but as client source maps may not be available there is going to be an error (no problem for the application though).
*
* Only exported for testing
*/
export function clientSourceMapErrorFilter(options: SentryNuxtServerOptions): EventProcessor {
return Object.assign(
(event => {
const errorMsg = event.exception?.values?.[0]?.value;
if (errorMsg?.match(/^ENOENT: no such file or directory, open '.*\/_nuxt\/.*\.js\.map'/)) {
options.debug && DEBUG_BUILD && logger.log('NuxtClientSourceMapErrorFilter filtered error: ', errorMsg);
return null;
}
return event;
}) satisfies EventProcessor,
{ id: 'NuxtClientSourceMapErrorFilter' },
);
}

function getNuxtDefaultIntegrations(options: NodeOptions): Integration[] {
Expand Down
87 changes: 86 additions & 1 deletion packages/nuxt/test/server/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as SentryNode from '@sentry/node';
import type { NodeClient } from '@sentry/node';
import { Scope } from '@sentry/node';
import { getGlobalScope } from '@sentry/node';
import { SDK_VERSION } from '@sentry/node';
import type { EventProcessor } from '@sentry/types';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { SentryNuxtServerOptions } from '../../src/common/types';
import { init } from '../../src/server';
import { mergeRegisterEsmLoaderHooks } from '../../src/server/sdk';
import { clientSourceMapErrorFilter, mergeRegisterEsmLoaderHooks } from '../../src/server/sdk';

const nodeInit = vi.spyOn(SentryNode, 'init');

Expand Down Expand Up @@ -83,6 +86,88 @@ describe('Nuxt Server SDK', () => {
expect.any(Object),
);
});

it('registers an event processor', async () => {
let passedEventProcessors: EventProcessor[] = [];
const addEventProcessor = vi
.spyOn(getGlobalScope(), 'addEventProcessor')
.mockImplementation((eventProcessor: EventProcessor) => {
passedEventProcessors = [...passedEventProcessors, eventProcessor];
return new Scope();
});

init({
dsn: 'https://[email protected]/1337',
});

expect(addEventProcessor).toHaveBeenCalledTimes(2);
expect(passedEventProcessors[0]?.id).toEqual('NuxtLowQualityTransactionsFilter');
expect(passedEventProcessors[1]?.id).toEqual('NuxtClientSourceMapErrorFilter');
});
});

describe('clientSourceMapErrorFilter', () => {
const options = { debug: false };
const filter = clientSourceMapErrorFilter(options);

describe('filters out errors', () => {
it.each([
[
'source map errors with leading /',
{
exception: { values: [{ value: "ENOENT: no such file or directory, open '/path/to/_nuxt/file.js.map'" }] },
},
],
[
'source map errors without leading /',
{ exception: { values: [{ value: "ENOENT: no such file or directory, open 'path/to/_nuxt/file.js.map'" }] } },
],
[
'source map errors with long path',
{
exception: {
values: [
{
value:
"ENOENT: no such file or directory, open 'path/to/public/_nuxt/public/long/long/path/file.js.map'",
},
],
},
},
],
])('filters out %s', (_, event) => {
// @ts-expect-error Event type is not correct in tests
expect(filter(event)).toBeNull();
});
});

describe('does not filter out errors', () => {
it.each([
['other errors', { exception: { values: [{ value: 'Some other error' }] } }],
['events with no exceptions', {}],
[
'events without _nuxt in path',
{
exception: { values: [{ value: "ENOENT: no such file or directory, open '/path/to/other/file.js.map'" }] },
},
],
[
'source map errors with different casing',
{
exception: { values: [{ value: "ENOENT: No Such file or directory, open '/path/to/_nuxt/file.js.map'" }] },
},
],
[
'non-source-map file',
{ exception: { values: [{ value: "ENOENT: no such file or directory, open '/path/to/_nuxt/file.js'" }] } },
],
['events with no exception values', { exception: { values: [] } }],
['events with null exception value', { exception: { values: [null] } }],
])('does not filter out %s', (_, event) => {
// @ts-expect-error Event type is not correct in tests
expect(filter(event)).toEqual(event);
});
});
});

describe('mergeRegisterEsmLoaderHooks', () => {
Expand Down
Loading