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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(browser): Add option to sample linked traces consistently by Lms24 · Pull Request #16037 · getsentry/sentry-javascript · GitHub
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(browser): Add option to sample linked traces consistently by Lms24 · Pull Request #16037 · getsentry/sentry-javascript · GitHub
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(browser): Add option to sample linked traces consistently by Lms24 · Pull Request #16037 · getsentry/sentry-javascript · GitHub
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(browser): Add option to sample linked traces consistently by Lms24 · Pull Request #16037 · getsentry/sentry-javascript · GitHub
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(browser): Add option to sample linked traces consistently by Lms24 · Pull Request #16037 · getsentry/sentry-javascript · GitHub
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(browser): Add option to sample linked traces consistently by Lms24 · Pull Request #16037 · getsentry/sentry-javascript · GitHub
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat(browser): Add option to sample linked traces consistently by Lms24 · Pull Request #16037 · getsentry/sentry-javascript · GitHub
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,7 +47,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '76 KB',
limit: '77 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand DownExpand Up@@ -219,7 +219,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '38.5 KB',
limit: '39 KB',
},
// Node SDK (ESM)
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ctx => {
if (ctx.attributes && ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
return 1;
}
return ctx.inheritOrSampleWith(0);
},
debug: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<button id="btn1">
</button>
<button id="btn2">
</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
import { expect } from '@playwright/test';
import {
extractTraceparentData,
parseBaggageHeader,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE,
} from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
eventAndTraceHeaderRequestParser,
shouldSkipTracingTest,
waitForTracingHeadersOnUrl,
waitForTransactionRequest,
} from '../../../../../../utils/helpers';

sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => {
return evt.contexts?.trace?.op === 'pageload';
});
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);
const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);
expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand };
});

const customTraceContext = await sentryTest.step('Custom trace', async () => {
const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
await page.locator('#btn1').click();
const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser(
await customTrace1RequestPromise,
);

const customTraceContext = customTrace1Event.contexts?.trace;

expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
// although we "continue the trace" from pageload, this is actually a root span,
// so there must not be a parent span id
expect(customTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand));

return customTraceContext;
});

await sentryTest.step('Navigation', async () => {
const navigation1RequestPromise = waitForTransactionRequest(
page,
evt => evt.contexts?.trace?.op === 'navigation',
);
await page.goto(`${url}#foo`);
const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser(
await navigation1RequestPromise,
);
const navTraceContext = navigationEvent.contexts?.trace;

expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id);
expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(navTraceContext?.links).toEqual([
{
trace_id: customTraceContext?.trace_id,
span_id: customTraceContext?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: 'previous_trace',
},
},
]);
expect(navTraceContext?.parent_span_id).toBeUndefined();

expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand));
});
});

sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => {
const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload');
await page.goto(url);

const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise);
const pageloadSampleRand = Number(res[1]?.sample_rand);

expect(pageloadSampleRand).toBeGreaterThanOrEqual(0);

const pageloadTraceContext = res[0].contexts?.trace;

expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1);

return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand };
});

await sentryTest.step('Make fetch request', async () => {
const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom');
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();

const { baggage, sentryTrace } = await tracingHeadersPromise;

const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise);

const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand);
const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace;
const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client');

expect(fetchTraceSampleRand).toEqual(pageloadSampleRand);

expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual(
pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE],
);
expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: fetchTraceTraceContext?.trace_id,
parentSpanId: httpClientSpan?.span_id,
parentSampled: true,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${pageloadSampleRand}`,
'sentry-sample_rate': '1',
'sentry-sampled': 'true',
'sentry-trace_id': fetchTraceTraceContext?.trace_id,
'sentry-transaction': 'custom root span 2',
});
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'in-memory',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampleRate: 1,
debug: true,
sendClientReports: true,
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
const btn1 = document.getElementById('btn1');

const btn2 = document.getElementById('btn2');

btn1.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 1', op: 'custom' }, () => {});
});
});

btn2.addEventListener('click', () => {
Sentry.startNewTrace(() => {
Sentry.startSpan({ name: 'custom root span 2', op: 'custom' }, async () => {
await fetch('https://someUrl.com');
});
});
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="sentry-trace" content="12345678901234567890123456789012-1234567890123456-0" />
<meta
name="baggage"
content="sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=0.2,sentry-sampled=false,sentry-transaction=my-transaction,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=prod,sentry-sample_rand=0.9"
/>
</head>
<button id="btn1">Custom Trace</button>
<button id="btn2">fetch request</button>
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import type { ClientReport } from '@sentry/core';
import { extractTraceparentData, parseBaggageHeader } from '@sentry/core';
import { sentryTest } from '../../../../../../utils/fixtures';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
hidePage,
shouldSkipTracingTest,
waitForClientReportRequest,
waitForTracingHeadersOnUrl,
} from '../../../../../../utils/helpers';

const metaTagSampleRand = 0.9;
const metaTagSampleRate = 0.2;
const metaTagTraceId = '12345678901234567890123456789012';

sentryTest.describe('When `consistentTraceSampling` is `true` and page contains <meta> tags', () => {
sentryTest(
'Continues negative sampling decision from meta tag across all traces and downstream propagations',
async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

let txnsReceived = 0;
// @ts-expect-error - no need to return something valid here
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'transaction' }, () => {
++txnsReceived;
return {};
});

const clientReportPromise = waitForClientReportRequest(page);

await sentryTest.step('Initial pageload', async () => {
await page.goto(url);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Custom instrumented button click', async () => {
await page.locator('#btn1').click();
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Navigation', async () => {
await page.goto(`${url}#foo`);
expect(txnsReceived).toEqual(0);
});

await sentryTest.step('Make fetch request', async () => {
const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'https://someUrl.com');

await page.locator('#btn2').click();
const { baggage, sentryTrace } = await tracingHeadersPromise;

expect(sentryTrace).toBeDefined();
expect(baggage).toBeDefined();

expect(extractTraceparentData(sentryTrace)).toEqual({
traceId: expect.not.stringContaining(metaTagTraceId),
parentSpanId: expect.stringMatching(/^[0-9a-f]{16}$/),
parentSampled: false,
});

expect(parseBaggageHeader(baggage)).toEqual({
'sentry-environment': 'production',
'sentry-public_key': 'public',
'sentry-sample_rand': `${metaTagSampleRand}`,
'sentry-sample_rate': `${metaTagSampleRate}`,
'sentry-sampled': 'false',
'sentry-trace_id': expect.not.stringContaining(metaTagTraceId),
'sentry-transaction': 'custom root span 2',
});
});

await sentryTest.step('Client report', async () => {
await hidePage(page);
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
expect(clientReport).toEqual({
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
reason: 'sample_rate',
},
],
});
});

await sentryTest.step('Wait for transactions to be discarded', async () => {
// give it a little longer just in case a txn is pending to be sent
await page.waitForTimeout(1000);
expect(txnsReceived).toEqual(0);
});
},
);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [
Sentry.browserTracingIntegration({
linkPreviousTrace: 'session-storage',
consistentTraceSampling: true,
}),
],
tracePropagationTargets: ['someurl.com'],
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0);
},
debug: true,
sendClientReports: true,
});
Loading