Skip to content

Commit 558d3ff

Browse files
authored
Merge pull request #16219 from getsentry/create-lowQualityTransactionsFilter-for-react-router
feat(react-router): Create low quality transactions filter for react router
2 parents 8f7599a + 497cfc1 commit 558d3ff

File tree

4 files changed

+143
-2
lines changed

4 files changed

+143
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { type Client, type Event, type EventHint, defineIntegration, logger } from '@sentry/core';
2+
import type { NodeOptions } from '@sentry/node';
3+
4+
/**
5+
* Integration that filters out noisy http transactions such as requests to node_modules, favicon.ico, @id/
6+
*
7+
*/
8+
9+
function _lowQualityTransactionsFilterIntegration(options: NodeOptions): {
10+
name: string;
11+
processEvent: (event: Event, hint: EventHint, client: Client) => Event | null;
12+
} {
13+
const matchedRegexes = [/GET \/node_modules\//, /GET \/favicon\.ico/, /GET \/@id\//];
14+
15+
return {
16+
name: 'LowQualityTransactionsFilter',
17+
18+
processEvent(event: Event, _hint: EventHint, _client: Client): Event | null {
19+
if (event.type !== 'transaction' || !event.transaction) {
20+
return event;
21+
}
22+
23+
const transaction = event.transaction;
24+
25+
if (matchedRegexes.some(regex => transaction.match(regex))) {
26+
options.debug && logger.log('[ReactRouter] Filtered node_modules transaction:', event.transaction);
27+
return null;
28+
}
29+
30+
return event;
31+
},
32+
};
33+
}
34+
35+
export const lowQualityTransactionsFilterIntegration = defineIntegration((options: NodeOptions) =>
36+
_lowQualityTransactionsFilterIntegration(options),
37+
);

packages/react-router/src/server/sdk.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1+
import type { Integration } from '@sentry/core';
12
import { applySdkMetadata, logger, setTag } from '@sentry/core';
23
import type { NodeClient, NodeOptions } from '@sentry/node';
3-
import { init as initNodeSdk } from '@sentry/node';
4+
import { getDefaultIntegrations as getNodeDefaultIntegrations, init as initNodeSdk } from '@sentry/node';
45
import { DEBUG_BUILD } from '../common/debug-build';
6+
import { lowQualityTransactionsFilterIntegration } from './lowQualityTransactionsFilterIntegration';
7+
8+
function getDefaultIntegrations(options: NodeOptions): Integration[] {
9+
return [...getNodeDefaultIntegrations(options), lowQualityTransactionsFilterIntegration(options)];
10+
}
511

612
/**
713
* Initializes the server side of the React Router SDK
814
*/
915
export function init(options: NodeOptions): NodeClient | undefined {
1016
const opts = {
1117
...options,
18+
defaultIntegrations: getDefaultIntegrations(options),
1219
};
1320

1421
DEBUG_BUILD && logger.log('Initializing SDK...');
@@ -20,5 +27,6 @@ export function init(options: NodeOptions): NodeClient | undefined {
2027
setTag('runtime', 'node');
2128

2229
DEBUG_BUILD && logger.log('SDK successfully initialized');
30+
2331
return client;
2432
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { Event, EventType, Integration } from '@sentry/core';
2+
import * as SentryCore from '@sentry/core';
3+
import * as SentryNode from '@sentry/node';
4+
import { afterEach, describe, expect, it, vi } from 'vitest';
5+
import { lowQualityTransactionsFilterIntegration } from '../../src/server/lowQualityTransactionsFilterIntegration';
6+
7+
const loggerLog = vi.spyOn(SentryCore.logger, 'log').mockImplementation(() => {});
8+
9+
describe('Low Quality Transactions Filter Integration', () => {
10+
afterEach(() => {
11+
vi.clearAllMocks();
12+
SentryNode.getGlobalScope().clear();
13+
});
14+
15+
describe('integration functionality', () => {
16+
describe('filters out low quality transactions', () => {
17+
it.each([
18+
['node_modules requests', 'GET /node_modules/some-package/index.js'],
19+
['favicon.ico requests', 'GET /favicon.ico'],
20+
['@id/ requests', 'GET /@id/some-id'],
21+
])('%s', (description, transaction) => {
22+
const integration = lowQualityTransactionsFilterIntegration({ debug: true }) as Integration;
23+
const event = {
24+
type: 'transaction' as EventType,
25+
transaction,
26+
} as Event;
27+
28+
const result = integration.processEvent!(event, {}, {} as SentryCore.Client);
29+
30+
expect(result).toBeNull();
31+
32+
expect(loggerLog).toHaveBeenCalledWith('[ReactRouter] Filtered node_modules transaction:', transaction);
33+
});
34+
});
35+
36+
describe('allows high quality transactions', () => {
37+
it.each([
38+
['normal page requests', 'GET /api/users'],
39+
['API endpoints', 'POST /data'],
40+
['app routes', 'GET /projects/123'],
41+
])('%s', (description, transaction) => {
42+
const integration = lowQualityTransactionsFilterIntegration({}) as Integration;
43+
const event = {
44+
type: 'transaction' as EventType,
45+
transaction,
46+
} as Event;
47+
48+
const result = integration.processEvent!(event, {}, {} as SentryCore.Client);
49+
50+
expect(result).toEqual(event);
51+
});
52+
});
53+
54+
it('does not affect non-transaction events', () => {
55+
const integration = lowQualityTransactionsFilterIntegration({}) as Integration;
56+
const event = {
57+
type: 'error' as EventType,
58+
transaction: 'GET /node_modules/some-package/index.js',
59+
} as Event;
60+
61+
const result = integration.processEvent!(event, {}, {} as SentryCore.Client);
62+
63+
expect(result).toEqual(event);
64+
});
65+
});
66+
});

packages/react-router/test/server/sdk.test.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import type { Integration } from '@sentry/core';
2+
import type { NodeClient } from '@sentry/node';
13
import * as SentryNode from '@sentry/node';
24
import { SDK_VERSION } from '@sentry/node';
35
import { afterEach, describe, expect, it, vi } from 'vitest';
6+
import * as LowQualityModule from '../../src/server/lowQualityTransactionsFilterIntegration';
47
import { init as reactRouterInit } from '../../src/server/sdk';
58

69
const nodeInit = vi.spyOn(SentryNode, 'init');
@@ -39,7 +42,34 @@ describe('React Router server SDK', () => {
3942
});
4043

4144
it('returns client from init', () => {
42-
expect(reactRouterInit({})).not.toBeUndefined();
45+
const client = reactRouterInit({
46+
dsn: 'https://[email protected]/1337',
47+
}) as NodeClient;
48+
expect(client).not.toBeUndefined();
49+
});
50+
51+
it('adds the low quality transactions filter integration by default', () => {
52+
const filterSpy = vi.spyOn(LowQualityModule, 'lowQualityTransactionsFilterIntegration');
53+
54+
reactRouterInit({
55+
dsn: 'https://[email protected]/1337',
56+
});
57+
58+
expect(filterSpy).toHaveBeenCalled();
59+
60+
expect(nodeInit).toHaveBeenCalledTimes(1);
61+
const initOptions = nodeInit.mock.calls[0]?.[0];
62+
63+
expect(initOptions).toBeDefined();
64+
65+
const defaultIntegrations = initOptions?.defaultIntegrations as Integration[];
66+
expect(Array.isArray(defaultIntegrations)).toBe(true);
67+
68+
const filterIntegration = defaultIntegrations.find(
69+
integration => integration.name === 'LowQualityTransactionsFilter',
70+
);
71+
72+
expect(filterIntegration).toBeDefined();
4373
});
4474
});
4575
});

0 commit comments

Comments
 (0)