Skip to content

Commit 05479b8

Browse files
authored
feat(nuxt): Add filter for not found source maps (devtools) (#14437)
Dev tools sometimes try to access source maps. This filter makes sure this error is not reported in Sentry as it adds noise to the data.
1 parent 6fbab43 commit 05479b8

File tree

2 files changed

+129
-22
lines changed

2 files changed

+129
-22
lines changed

packages/nuxt/src/server/sdk.ts

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,52 @@ export function init(options: SentryNuxtServerOptions): Client | undefined {
2626

2727
const client = initNode(sentryOptions);
2828

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

43-
return event;
44-
} else {
45-
return event;
46-
}
47-
}) satisfies EventProcessor,
48-
{ id: 'NuxtLowQualityTransactionsFilter' },
49-
),
32+
return client;
33+
}
34+
35+
/**
36+
* Filter out transactions for Nuxt build assets
37+
* This regex matches the default path to the nuxt-generated build assets (`_nuxt`).
38+
*
39+
* Only exported for testing
40+
*/
41+
export function lowQualityTransactionsFilter(options: SentryNuxtServerOptions): EventProcessor {
42+
return Object.assign(
43+
(event => {
44+
if (event.type === 'transaction' && event.transaction?.match(/^GET \/_nuxt\//)) {
45+
// todo: the buildAssetDir could be changed in the nuxt config - change this to a more generic solution
46+
options.debug &&
47+
DEBUG_BUILD &&
48+
logger.log('NuxtLowQualityTransactionsFilter filtered transaction: ', event.transaction);
49+
return null;
50+
} else {
51+
return event;
52+
}
53+
}) satisfies EventProcessor,
54+
{ id: 'NuxtLowQualityTransactionsFilter' },
5055
);
56+
}
5157

52-
return client;
58+
/**
59+
* 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).
60+
*
61+
* Only exported for testing
62+
*/
63+
export function clientSourceMapErrorFilter(options: SentryNuxtServerOptions): EventProcessor {
64+
return Object.assign(
65+
(event => {
66+
const errorMsg = event.exception?.values?.[0]?.value;
67+
if (errorMsg?.match(/^ENOENT: no such file or directory, open '.*\/_nuxt\/.*\.js\.map'/)) {
68+
options.debug && DEBUG_BUILD && logger.log('NuxtClientSourceMapErrorFilter filtered error: ', errorMsg);
69+
return null;
70+
}
71+
return event;
72+
}) satisfies EventProcessor,
73+
{ id: 'NuxtClientSourceMapErrorFilter' },
74+
);
5375
}
5476

5577
function getNuxtDefaultIntegrations(options: NodeOptions): Integration[] {

packages/nuxt/test/server/sdk.test.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import * as SentryNode from '@sentry/node';
22
import type { NodeClient } from '@sentry/node';
3+
import { Scope } from '@sentry/node';
4+
import { getGlobalScope } from '@sentry/node';
35
import { SDK_VERSION } from '@sentry/node';
6+
import type { EventProcessor } from '@sentry/types';
47
import { beforeEach, describe, expect, it, vi } from 'vitest';
58
import type { SentryNuxtServerOptions } from '../../src/common/types';
69
import { init } from '../../src/server';
7-
import { mergeRegisterEsmLoaderHooks } from '../../src/server/sdk';
10+
import { clientSourceMapErrorFilter, mergeRegisterEsmLoaderHooks } from '../../src/server/sdk';
811

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

@@ -83,6 +86,88 @@ describe('Nuxt Server SDK', () => {
8386
expect.any(Object),
8487
);
8588
});
89+
90+
it('registers an event processor', async () => {
91+
let passedEventProcessors: EventProcessor[] = [];
92+
const addEventProcessor = vi
93+
.spyOn(getGlobalScope(), 'addEventProcessor')
94+
.mockImplementation((eventProcessor: EventProcessor) => {
95+
passedEventProcessors = [...passedEventProcessors, eventProcessor];
96+
return new Scope();
97+
});
98+
99+
init({
100+
dsn: 'https://[email protected]/1337',
101+
});
102+
103+
expect(addEventProcessor).toHaveBeenCalledTimes(2);
104+
expect(passedEventProcessors[0]?.id).toEqual('NuxtLowQualityTransactionsFilter');
105+
expect(passedEventProcessors[1]?.id).toEqual('NuxtClientSourceMapErrorFilter');
106+
});
107+
});
108+
109+
describe('clientSourceMapErrorFilter', () => {
110+
const options = { debug: false };
111+
const filter = clientSourceMapErrorFilter(options);
112+
113+
describe('filters out errors', () => {
114+
it.each([
115+
[
116+
'source map errors with leading /',
117+
{
118+
exception: { values: [{ value: "ENOENT: no such file or directory, open '/path/to/_nuxt/file.js.map'" }] },
119+
},
120+
],
121+
[
122+
'source map errors without leading /',
123+
{ exception: { values: [{ value: "ENOENT: no such file or directory, open 'path/to/_nuxt/file.js.map'" }] } },
124+
],
125+
[
126+
'source map errors with long path',
127+
{
128+
exception: {
129+
values: [
130+
{
131+
value:
132+
"ENOENT: no such file or directory, open 'path/to/public/_nuxt/public/long/long/path/file.js.map'",
133+
},
134+
],
135+
},
136+
},
137+
],
138+
])('filters out %s', (_, event) => {
139+
// @ts-expect-error Event type is not correct in tests
140+
expect(filter(event)).toBeNull();
141+
});
142+
});
143+
144+
describe('does not filter out errors', () => {
145+
it.each([
146+
['other errors', { exception: { values: [{ value: 'Some other error' }] } }],
147+
['events with no exceptions', {}],
148+
[
149+
'events without _nuxt in path',
150+
{
151+
exception: { values: [{ value: "ENOENT: no such file or directory, open '/path/to/other/file.js.map'" }] },
152+
},
153+
],
154+
[
155+
'source map errors with different casing',
156+
{
157+
exception: { values: [{ value: "ENOENT: No Such file or directory, open '/path/to/_nuxt/file.js.map'" }] },
158+
},
159+
],
160+
[
161+
'non-source-map file',
162+
{ exception: { values: [{ value: "ENOENT: no such file or directory, open '/path/to/_nuxt/file.js'" }] } },
163+
],
164+
['events with no exception values', { exception: { values: [] } }],
165+
['events with null exception value', { exception: { values: [null] } }],
166+
])('does not filter out %s', (_, event) => {
167+
// @ts-expect-error Event type is not correct in tests
168+
expect(filter(event)).toEqual(event);
169+
});
170+
});
86171
});
87172

88173
describe('mergeRegisterEsmLoaderHooks', () => {

0 commit comments

Comments
 (0)