Skip to content
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
2 changes: 2 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@
"oxfmt",
"oxlint",
"padangle",
"pagead",
"parasharrajat",
"passcodes",
"passplus",
Expand Down Expand Up @@ -955,6 +956,7 @@
"RNFS",
"RNLinksdk",
"rnmapbox",
"rmkt",
"rock",
"rowcount",
"rowindex",
Expand Down
15 changes: 14 additions & 1 deletion src/libs/telemetry/integrations/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@ import * as SentryReact from '@sentry/react';
import * as Sentry from '@sentry/react-native';

const shouldCreateSpanForRequest = (url: string): boolean => {
const filteredPhrases = ['/api/Log', 'firebaselogging-pa.googleapis.com', 'analytics.google.com', 'rs.fullstory.com', 'api.github.com', 'group-ib.com', 'fp-api.expensify.com'];
const filteredPhrases = [
'/api/Log',
'firebaselogging-pa.googleapis.com',
'analytics.google.com',
'rs.fullstory.com',
'api.github.com',
'group-ib.com',
'fp-api.expensify.com',
'/api/Ping',
'ccm/collect',
'rmkt/collect',
'pagead/form-data',
'ccm/form-data',
];
return !filteredPhrases.some((phrase) => url.includes(phrase));
};

Expand Down
4 changes: 4 additions & 0 deletions src/libs/telemetry/integrations/index.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ function isApplicationKeyStamped(): boolean {
/**
* Browser tracing integration is enabled on Web to support web health measurements
* such as INP, LCP, FCP, CLS.
*
* `ignoreResourceSpans` stops the SDK from creating a span per stylesheet/font (`resource.link`) and per script
* (`resource.script`) on every pageload and navigation
*/
const tracingIntegration = SentryReact.browserTracingIntegration({
shouldCreateSpanForRequest,
ignoreResourceSpans: ['resource.link', 'resource.script'],
});

/**
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/shouldCreateSpanForRequestTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {shouldCreateSpanForRequest} from '@libs/telemetry/integrations/common';

// The module builds integrations at import time, so the SDK factories are stubbed out.
jest.mock('@sentry/react-native', () => ({
reactNavigationIntegration: jest.fn(),
breadcrumbsIntegration: jest.fn(),
consoleLoggingIntegration: jest.fn(),
}));

jest.mock('@sentry/react', () => ({
browserProfilingIntegration: jest.fn(),
}));

describe('shouldCreateSpanForRequest', () => {
it('creates a span for the API commands we debug with', () => {
expect(shouldCreateSpanForRequest('https://www.expensify.com/api/OpenReport?')).toBe(true);
});

it('drops the NetInfo reachability heartbeat, which every client issues on a timer', () => {
expect(shouldCreateSpanForRequest('https://www.expensify.com/api/Ping?accountID=123')).toBe(false);
});

it('drops the logging command, so writing logs cannot generate more telemetry', () => {
expect(shouldCreateSpanForRequest('https://www.expensify.com/api/Log?')).toBe(false);
});

it.each([
['ccm/collect', 'https://www.google.com/ccm/collect?tid=G-12345&en=page_view'],
['rmkt/collect', 'https://www.google.com/rmkt/collect?tid=12345'],
['pagead/form-data', 'https://googleads.g.doubleclick.net/pagead/form-data?id=12345'],
['ccm/form-data', 'https://www.google.com/ccm/form-data?id=12345'],
])('drops the third-party Google Ads/Analytics request to %s', (_endpoint, url) => {
expect(shouldCreateSpanForRequest(url)).toBe(false);
});
});
27 changes: 27 additions & 0 deletions tests/unit/webTracingIntegrationTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Held outside the factory so the assertion can read the calls without importing `@sentry/react`,
// which is a transitive dependency that knip flags when imported by name.
const mockBrowserTracingIntegration = jest.fn(() => ({name: 'BrowserTracing'}));

// The module builds integrations at import time, so the SDK factories are stubbed out.
jest.mock('@sentry/react', () => ({
browserTracingIntegration: mockBrowserTracingIntegration,
reportingObserverIntegration: jest.fn(() => ({name: 'ReportingObserver'})),
thirdPartyErrorFilterIntegration: jest.fn(() => ({name: 'ThirdPartyErrorFilter'})),
browserProfilingIntegration: jest.fn(() => ({name: 'BrowserProfiling'})),
}));

jest.mock('@sentry/react-native', () => ({
reactNavigationIntegration: jest.fn(),
breadcrumbsIntegration: jest.fn(),
consoleLoggingIntegration: jest.fn(),
}));

describe('web tracing integration', () => {
it('tells the browser SDK not to create resource spans for stylesheets/fonts and scripts', async () => {
// Given the web integrations module, which configures browserTracingIntegration at import time
await import('@libs/telemetry/integrations/index.web');

// Then the two highest-volume resource span ops are never created
expect(mockBrowserTracingIntegration).toHaveBeenCalledWith(expect.objectContaining({ignoreResourceSpans: ['resource.link', 'resource.script']}));
});
});
Loading