Skip to content
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

fix(tracing): Pass tracePropagationTargets to instrumentOutgoingRequests #6259

Merged
merged 6 commits into from
Nov 22, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
integrations: [new Integrations.BrowserTracing({ tracePropagationTargets: ['http://example.com'] })],
tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fetch('http://example.com/0').then(fetch('http://example.com/1').then(fetch('http://example.com/2')));
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, Request } from '@playwright/test';

import { sentryTest } from '../../../../../utils/fixtures';

sentryTest(
'should attach `sentry-trace` and `baggage` header to request matching tracePropagationTargets',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const requests = (
await Promise.all([
page.goto(url),
Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://example.com/${idx}`))),
])
)[1];

expect(requests).toHaveLength(3);

requests?.forEach(async (request: Request) => {
const requestHeaders = await request.allHeaders();
expect(requestHeaders).toMatchObject({
'sentry-trace': expect.any(String),
baggage: expect.any(String),
});
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
integrations: [
new Integrations.BrowserTracing({ tracePropagationTargets: [], tracingOrigins: ['http://example.com'] }),
],
tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fetch('http://example.com/0').then(fetch('http://example.com/1').then(fetch('http://example.com/2')));
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, Request } from '@playwright/test';

import { sentryTest } from '../../../../../utils/fixtures';

sentryTest(
'[pre-v8] should prefer custom tracePropagationTargets over tracingOrigins',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const requests = (
await Promise.all([
page.goto(url),
Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://example.com/${idx}`))),
])
)[1];

expect(requests).toHaveLength(3);

requests?.forEach(async (request: Request) => {
const requestHeaders = await request.allHeaders();
expect(requestHeaders).not.toMatchObject({
'sentry-trace': expect.any(String),
baggage: expect.any(String),
});
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
integrations: [new Integrations.BrowserTracing({ tracingOrigins: ['http://example.com'] })],
tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fetch('http://example.com/0').then(fetch('http://example.com/1').then(fetch('http://example.com/2')));
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, Request } from '@playwright/test';

import { sentryTest } from '../../../../../utils/fixtures';

sentryTest(
'[pre-v8] should attach `sentry-trace` and `baggage` header to request matching tracingOrigins',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const requests = (
await Promise.all([
page.goto(url),
Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://example.com/${idx}`))),
])
)[1];

expect(requests).toHaveLength(3);

requests?.forEach(async (request: Request) => {
const requestHeaders = await request.allHeaders();
expect(requestHeaders).toMatchObject({
'sentry-trace': expect.any(String),
baggage: expect.any(String),
});
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fetch('http://localhost:4200/0').then(fetch('http://localhost:4200/1').then(fetch('http://localhost:4200/2')));
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, Request } from '@playwright/test';

import { sentryTest } from '../../../../../utils/fixtures';

sentryTest(
'should attach `sentry-trace` and `baggage` header to request matching default tracePropagationTargets',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const requests = (
await Promise.all([
page.goto(url),
Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://localhost:4200/${idx}`))),
])
)[1];

expect(requests).toHaveLength(3);

requests?.forEach(async (request: Request) => {
const requestHeaders = await request.allHeaders();
expect(requestHeaders).toMatchObject({
'sentry-trace': expect.any(String),
baggage: expect.any(String),
});
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fetch('http://example.com/0').then(fetch('http://example.com/1').then(fetch('http://example.com/2')));
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, Request } from '@playwright/test';

import { sentryTest } from '../../../../../utils/fixtures';

sentryTest(
'should not attach `sentry-trace` and `baggage` header to request not matching default tracePropagationTargets',
async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const requests = (
await Promise.all([
page.goto(url),
Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://example.com/${idx}`))),
])
)[1];

expect(requests).toHaveLength(3);

requests?.forEach(async (request: Request) => {
const requestHeaders = await request.allHeaders();
expect(requestHeaders).not.toMatchObject({
'sentry-trace': expect.any(String),
baggage: expect.any(String),
});
});
},
);
21 changes: 17 additions & 4 deletions packages/tracing/src/browser/browsertracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export interface BrowserTracingOptions extends RequestInstrumentationOptions {
): void;
}

const DEFAULT_BROWSER_TRACING_OPTIONS = {
const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
idleTimeout: DEFAULT_IDLE_TIMEOUT,
finalTimeout: DEFAULT_FINAL_TIMEOUT,
heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL,
Expand Down Expand Up @@ -153,6 +153,15 @@ export class BrowserTracing implements Integration {
..._options,
};

// TODO (v8): remove this block after tracingOrigins is removed
// Set tracePropagationTargets to tracingOrigins if specified by the user
// In case both are specified, tracePropagationTargets takes precedence
// eslint-disable-next-line deprecation/deprecation
if (_options && !_options.tracePropagationTargets && _options.tracingOrigins) {
// eslint-disable-next-line deprecation/deprecation
this.options.tracePropagationTargets = _options.tracingOrigins;
}

const { _metricOptions } = this.options;
startTrackingWebVitals(_metricOptions && _metricOptions._reportAllChanges);
if (this.options._experiments?.enableLongTask) {
Expand All @@ -174,8 +183,7 @@ export class BrowserTracing implements Integration {
markBackgroundTransactions,
traceFetch,
traceXHR,
// eslint-disable-next-line deprecation/deprecation
tracingOrigins,
tracePropagationTargets,
shouldCreateSpanForRequest,
} = this.options;

Expand All @@ -189,7 +197,12 @@ export class BrowserTracing implements Integration {
registerBackgroundTabDetection();
}

instrumentOutgoingRequests({ traceFetch, traceXHR, tracingOrigins, shouldCreateSpanForRequest });
instrumentOutgoingRequests({
traceFetch,
traceXHR,
tracePropagationTargets,
shouldCreateSpanForRequest,
});
}

/** Create routing idle transaction. */
Expand Down
22 changes: 7 additions & 15 deletions packages/tracing/src/browser/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const defaultRequestInstrumentationOptions: RequestInstrumentationOptions
/** Registers span creators for xhr and fetch requests */
export function instrumentOutgoingRequests(_options?: Partial<RequestInstrumentationOptions>): void {
// eslint-disable-next-line deprecation/deprecation
const { traceFetch, traceXHR, tracingOrigins, tracePropagationTargets, shouldCreateSpanForRequest } = {
const { traceFetch, traceXHR, tracePropagationTargets, tracingOrigins, shouldCreateSpanForRequest } = {
traceFetch: defaultRequestInstrumentationOptions.traceFetch,
traceXHR: defaultRequestInstrumentationOptions.traceXHR,
..._options,
Expand All @@ -122,8 +122,11 @@ export function instrumentOutgoingRequests(_options?: Partial<RequestInstrumenta
const shouldCreateSpan =
typeof shouldCreateSpanForRequest === 'function' ? shouldCreateSpanForRequest : (_: string) => true;

// TODO(v8) Remove tracingOrigins here
// The only reason we're passing it in here is because this instrumentOutgoingRequests function is publicly exported
// and we don't want to break the API. We can remove it in v8.
const shouldAttachHeadersWithTargets = (url: string): boolean =>
shouldAttachHeaders(url, tracingOrigins, tracePropagationTargets);
shouldAttachHeaders(url, tracePropagationTargets || tracingOrigins);

const spans: Record<string, Span> = {};

Expand All @@ -144,20 +147,9 @@ export function instrumentOutgoingRequests(_options?: Partial<RequestInstrumenta
* A function that determines whether to attach tracing headers to a request.
* This was extracted from `instrumentOutgoingRequests` to make it easier to test shouldAttachHeaders.
* We only export this fuction for testing purposes.
*
* TODO (v8): Remove `tracingOrigins` which should drastically simplify this function.
*/
export function shouldAttachHeaders(
url: string,
tracePropagationTargets: (string | RegExp)[] | undefined,
tracingOrigins: (string | RegExp)[] | undefined,
): boolean {
// TODO (v8): Replace the entire code below with this one-liner:
// return stringMatchesSomePattern(url, tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS);
if (tracePropagationTargets || tracingOrigins) {
return stringMatchesSomePattern(url, tracePropagationTargets || tracingOrigins);
}
return stringMatchesSomePattern(url, DEFAULT_TRACE_PROPAGATION_TARGETS);
export function shouldAttachHeaders(url: string, tracePropagationTargets: (string | RegExp)[] | undefined): boolean {
return stringMatchesSomePattern(url, tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS);
}

/**
Expand Down
47 changes: 47 additions & 0 deletions packages/tracing/test/browser/browsertracing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ jest.mock('@sentry/utils', () => {

jest.mock('../../src/browser/metrics');

const instrumentOutgoingRequestsMock = jest.fn();
jest.mock('./../../src/browser/request', () => {
const actual = jest.requireActual('./../../src/browser/request');
return {
...actual,
instrumentOutgoingRequests: (options: Partial<BrowserTracingOptions>) => instrumentOutgoingRequestsMock(options),
};
});

beforeAll(() => {
const dom = new JSDOM();
// @ts-ignore need to override global document
Expand Down Expand Up @@ -128,6 +137,7 @@ describe('BrowserTracing', () => {
expect(transaction.endTimestamp).toBe(span.endTimestamp);
});

// TODO (v8): remove these tests
describe('tracingOrigins', () => {
it('sets tracing origins if provided and does not warn', () => {
const sampleTracingOrigins = ['something'];
Expand All @@ -152,6 +162,43 @@ describe('BrowserTracing', () => {
});
});

describe('tracePropagationTargets', () => {
it('sets tracePropagationTargets if provided', () => {
const sampleTracePropagationTargets = ['something'];
const inst = createBrowserTracing(true, {
routingInstrumentation: customInstrumentRouting,
tracePropagationTargets: sampleTracePropagationTargets,
});

expect(inst.options.tracePropagationTargets).toEqual(sampleTracePropagationTargets);
});

it('sets tracePropagationTargets to an empty array and does not warn', () => {
const sampleTracePropagationTargets: string[] = [];
const inst = createBrowserTracing(true, {
routingInstrumentation: customInstrumentRouting,
tracePropagationTargets: sampleTracePropagationTargets,
});

expect(inst.options.tracePropagationTargets).toEqual(sampleTracePropagationTargets);
});

it('correctly passes tracePropagationTargets to `instrumentOutgoingRequests` in `setupOnce`', () => {
jest.clearAllMocks();
const sampleTracePropagationTargets = ['something'];
createBrowserTracing(true, {
routingInstrumentation: customInstrumentRouting,
tracePropagationTargets: sampleTracePropagationTargets,
});

expect(instrumentOutgoingRequestsMock).toHaveBeenCalledWith({
traceFetch: true,
traceXHR: true,
tracePropagationTargets: ['something'],
});
});
});

describe('beforeNavigate', () => {
it('is called on transaction creation', () => {
const mockBeforeNavigation = jest.fn().mockReturnValue({ name: 'here/is/my/path' });
Expand Down
Loading