Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ee9da95
wip
Lms24 Sep 26, 2025
c2df464
types, serialization, integration WIP
Lms24 Oct 1, 2025
5ffbbd2
create span v2 envelope
Lms24 Oct 1, 2025
3b7c13f
exports
Lms24 Oct 2, 2025
20a47c0
apply ignorespans, improve beforesendspan, handle segment span being …
Lms24 Oct 2, 2025
244025e
apply common attributes
Lms24 Oct 3, 2025
5a4b9d7
linter really doesn't like me and I can't blame him
Lms24 Oct 3, 2025
9ef8ccf
apply scope contexts, extras, request data attributes
Lms24 Oct 3, 2025
e7eea4d
cleanup
Lms24 Oct 14, 2025
abf898d
changelog entry
Lms24 Oct 14, 2025
1b28d9f
size-limit bumps
Lms24 Oct 14, 2025
5346b4c
fix lint, circular deps, size limit
Lms24 Oct 14, 2025
bce731a
bump preview version
Lms24 Oct 15, 2025
69862a1
s/makeV2Callback/withStreamSpan
Lms24 Oct 15, 2025
20f8245
add todos for event processors and integration hooks
Lms24 Oct 15, 2025
07324c0
changelog
Lms24 Oct 16, 2025
0882966
export withStreamSpan from browser
Lms24 Oct 16, 2025
522ce4a
changelog
Lms24 Oct 16, 2025
136845f
fix some attribute mishaps
Lms24 Oct 31, 2025
4cc3bbc
remove is_remote, add is_segment
Lms24 Nov 10, 2025
54b4499
add `sentry.segment.id` common span attribute
Lms24 Nov 10, 2025
b74b276
rip span kind
Lms24 Nov 12, 2025
a2c33e8
restart ci
Lms24 Nov 12, 2025
ea489e7
does this fix size limit?
Lms24 Nov 21, 2025
e6becfe
kk limits fixed but raise limits :(
Lms24 Nov 21, 2025
6818976
size limit once more
Lms24 Nov 21, 2025
fdee9c7
s/user.username/user.name
Lms24 Nov 25, 2025
01c0fab
one more limit bump
Lms24 Nov 25, 2025
f42d562
rewrite to `captureSpan`
Lms24 Nov 28, 2025
6bd700e
capturespan
Lms24 Nov 28, 2025
dae8d5b
add integration test for pageload span
Lms24 Dec 1, 2025
a32d8e5
more integration tests
Lms24 Dec 1, 2025
9c1fe8c
span links test
Lms24 Dec 1, 2025
587e3e8
set web vitals as attributes in span-first
Lms24 Dec 2, 2025
776e302
rewrite httpContext integration to use processSpan client hook
Lms24 Dec 2, 2025
d9cd2f3
minor lint stuff
Lms24 Dec 4, 2025
571a961
initial StreamingSpanExporter implementation for otel
Lms24 Dec 4, 2025
7473fb2
rewrite pipeline to just always use spanJSonV2 because thanks OTel
Lms24 Dec 5, 2025
45408a4
wip
Lms24 Dec 10, 2025
8602881
add exporter
Lms24 Dec 10, 2025
07633a3
more contexts
Lms24 Dec 10, 2025
282eed0
add unit tests for captureSpan pipeline and utils
Lms24 Dec 11, 2025
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
27 changes: 17 additions & 10 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,19 @@ module.exports = [
gzip: true,
limit: '48 KB',
},
// {
// name: '@sentry/browser (incl. Tracing Span-First)',
// path: 'packages/browser/build/npm/esm/index.js',
// import: createImport('init', 'browserTracingIntegration', 'spanStreamingIntegration'),
// gzip: true,
// limit: '44 KB',
// },
{
name: '@sentry/browser (incl. Tracing, Replay)',
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
limit: '80 KB',
limit: '82 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
Expand Down Expand Up @@ -82,14 +89,14 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'),
gzip: true,
limit: '85 KB',
limit: '86 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay, Feedback)',
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'),
gzip: true,
limit: '97 KB',
limit: '98 KB',
},
{
name: '@sentry/browser (incl. Feedback)',
Expand All @@ -103,7 +110,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'sendFeedback'),
gzip: true,
limit: '30 KB',
limit: '31 KB',
},
{
name: '@sentry/browser (incl. FeedbackAsync)',
Expand All @@ -127,15 +134,15 @@ module.exports = [
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44 KB',
limit: '45 KB',
},
// Vue SDK (ESM)
{
name: '@sentry/vue',
path: 'packages/vue/build/esm/index.js',
import: createImport('init'),
gzip: true,
limit: '30 KB',
limit: '31 KB',
},
{
name: '@sentry/vue (incl. Tracing)',
Expand Down Expand Up @@ -163,7 +170,7 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing)',
path: createCDNPath('bundle.tracing.min.js'),
gzip: true,
limit: '42.5 KB',
limit: '43 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay)',
Expand Down Expand Up @@ -213,7 +220,7 @@ module.exports = [
import: createImport('init'),
ignore: ['next/router', 'next/constants'],
gzip: true,
limit: '46 KB',
limit: '47 KB',
},
// SvelteKit SDK (ESM)
{
Expand All @@ -222,7 +229,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '42 KB',
limit: '43 KB',
},
// Node-Core SDK (ESM)
{
Expand All @@ -240,7 +247,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '160 KB',
limit: '161 KB',
},
{
name: '@sentry/node - without tracing',
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"angular.enable-strict-mode-prompt": false
}
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,16 @@ Work in this release was contributed by @hanseo0507. Thank you for your contribu

Work in this release was contributed by @0xbad0c0d3. Thank you for your contribution!

## 10.21.0-alpha.1

This release is a preview release for sending spans in browser via spanV2 instead of transaction event envelopes. All of this is experimental and subject to change. Use at your own risk. [More Details.](https://github.com/getsentry/sentry-javascript/pull/17852)

- export withStreamSpan from `@sentry/browser`

## 10.21.0-alpha.0

This release is a preview release for sending spans in browser via spanV2 instead of transaction event envelopes. All of this is experimental and subject to change. Use at your own risk. [More Details.](https://github.com/getsentry/sentry-javascript/pull/17852)

## 10.20.0

### Important Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
traceLifecycle: 'stream',
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
tracePropagationTargets: ['http://sentry-test-site.example'],
tracesSampleRate: 1,
sendDefaultPii: true,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
document.getElementById('go-background').addEventListener('click', () => {
setTimeout(() => {
Object.defineProperty(document, 'hidden', { value: true, writable: true });
const ev = document.createEvent('Event');
ev.initEvent('visibilitychange');
document.dispatchEvent(ev);
}, 250);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="go-background">New Tab</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../utils/fixtures';
import { shouldSkipTracingTest } from '../../../utils/helpers';
import { getSpanOp, waitForV2Spans } from '../../../utils/spanFirstUtils';

sentryTest('ends pageload span when the page goes to background', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}
const url = await getLocalTestUrl({ testDir: __dirname });

const spanPromise = waitForV2Spans(page, spans => !!spans.find(span => getSpanOp(span) === 'pageload'));

await page.goto(url);
await page.locator('#go-background').click();

const pageloadSpan = (await spanPromise).find(span => getSpanOp(span) === 'pageload');

expect(pageloadSpan?.status).toBe('error'); // a cancelled span previously mapped to status error with message cancelled.
expect(pageloadSpan?.attributes?.['sentry.op']?.value).toBe('pageload');
expect(pageloadSpan?.attributes?.['sentry.cancellation_reason']?.value).toBe('document.hidden');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
traceLifecycle: 'stream',
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
tracePropagationTargets: ['http://sentry-test-site.example'],
tracesSampleRate: 1,
sendDefaultPii: true,
debug: true,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from '@playwright/test';
import type { Event } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import {
envelopeRequestParser,
runScriptInSandbox,
shouldSkipTracingTest,
waitForErrorRequest,
} from '../../../utils/helpers';
import { getSpanOp, waitForV2Spans } from '../../../utils/spanFirstUtils';

sentryTest(
'puts the pageload span name onto an error event caught during pageload',
async ({ getLocalTestUrl, page, browserName }) => {
if (browserName === 'webkit') {
// This test fails on Webkit as errors thrown from `runScriptInSandbox` are Script Errors and skipped by Sentry
sentryTest.skip();
}

if (shouldSkipTracingTest()) {
sentryTest.skip();
}

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

const errorEventPromise = waitForErrorRequest(page);
const spanPromise = waitForV2Spans(page, spans => !!spans.find(span => getSpanOp(span) === 'pageload'));

await page.goto(url);

await runScriptInSandbox(page, {
content: `
throw new Error('Error during pageload');
`,
});

const errorEvent = envelopeRequestParser<Event>(await errorEventPromise);
const pageloadSpan = (await spanPromise).find(span => getSpanOp(span) === 'pageload');

expect(pageloadSpan?.attributes?.['sentry.op']?.value).toEqual('pageload');
expect(errorEvent.exception?.values?.[0]).toBeDefined();

expect(pageloadSpan?.name).toEqual('/index.html');

expect(pageloadSpan?.status).toBe('error');
expect(pageloadSpan?.attributes?.['sentry.idle_span_finish_reason']?.value).toBe('idleTimeout');

expect(errorEvent.transaction).toEqual(pageloadSpan?.name);
},
);
12 changes: 12 additions & 0 deletions dev-packages/browser-integration-tests/suites/span-first/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
traceLifecycle: 'stream',
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
tracePropagationTargets: ['http://sentry-test-site.example'],
tracesSampleRate: 1,
sendDefaultPii: true,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import { SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { shouldSkipTracingTest } from '../../../utils/helpers';
import { getSpanOp, waitForV2Spans } from '../../../utils/spanFirstUtils';

sentryTest("navigation spans link back to previous trace's root span", async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

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

const pageloadSpan = await sentryTest.step('Initial pageload', async () => {
const pageloadSpanPromise = waitForV2Spans(page, spans => !!spans.find(span => getSpanOp(span) === 'pageload'));
await page.goto(url);
return (await pageloadSpanPromise).find(span => getSpanOp(span) === 'pageload');
});

const navigation1Span = await sentryTest.step('First navigation', async () => {
const navigation1SpanPromise = waitForV2Spans(
page,
spans => !!spans.find(span => getSpanOp(span) === 'navigation'),
);
await page.goto(`${url}#foo`);
return (await navigation1SpanPromise).find(span => getSpanOp(span) === 'navigation');
});

const navigation2Span = await sentryTest.step('Second navigation', async () => {
const navigation2SpanPromise = waitForV2Spans(
page,
spans => !!spans.find(span => getSpanOp(span) === 'navigation'),
);
await page.goto(`${url}#bar`);
return (await navigation2SpanPromise).find(span => getSpanOp(span) === 'navigation');
});

const pageloadTraceId = pageloadSpan?.trace_id;
const navigation1TraceId = navigation1Span?.trace_id;
const navigation2TraceId = navigation2Span?.trace_id;

expect(pageloadSpan?.links).toBeUndefined();

expect(navigation1Span?.links).toEqual([
{
trace_id: pageloadTraceId,
span_id: pageloadSpan?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: { value: 'previous_trace', type: 'string' },
},
},
]);

expect(navigation1Span?.attributes).toMatchObject({
'sentry.previous_trace': { type: 'string', value: `${pageloadTraceId}-${pageloadSpan?.span_id}-1` },
});

expect(navigation2Span?.links).toEqual([
{
trace_id: navigation1TraceId,
span_id: navigation1Span?.span_id,
sampled: true,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: { value: 'previous_trace', type: 'string' },
},
},
]);

expect(navigation2Span?.attributes).toMatchObject({
'sentry.previous_trace': { type: 'string', value: `${navigation1TraceId}-${navigation1Span?.span_id}-1` },
});

expect(pageloadTraceId).not.toEqual(navigation1TraceId);
expect(navigation1TraceId).not.toEqual(navigation2TraceId);
expect(pageloadTraceId).not.toEqual(navigation2TraceId);
});

sentryTest("doesn't link between hard page reloads by default", async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

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

await sentryTest.step('First pageload', async () => {
const pageloadRequestPromise = waitForV2Spans(page, spans => !!spans.find(span => getSpanOp(span) === 'pageload'));
await page.goto(url);
return (await pageloadRequestPromise).find(span => getSpanOp(span) === 'pageload');
});

await sentryTest.step('Second pageload', async () => {
const pageload2RequestPromise = waitForV2Spans(page, spans => !!spans.find(span => getSpanOp(span) === 'pageload'));
await page.reload();
const pageload2Span = (await pageload2RequestPromise).find(span => getSpanOp(span) === 'pageload');

expect(pageload2Span?.trace_id).toBeDefined();
expect(pageload2Span?.links).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
traceLifecycle: 'stream',
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
tracePropagationTargets: ['http://sentry-test-site.example'],
tracesSampleRate: 1,
sendDefaultPii: true,
});
Loading
Loading