Skip to content
Open
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
8 changes: 4 additions & 4 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init'),
gzip: true,
limit: '24.1 KB',
limit: '24.5 KB',
modifyWebpackConfig: function (config) {
const webpack = require('webpack');

Expand Down Expand Up @@ -190,7 +190,7 @@ module.exports = [
name: 'CDN Bundle (incl. Logs, Metrics)',
path: createCDNPath('bundle.logs.metrics.min.js'),
gzip: true,
limit: '29 KB',
limit: '30 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Logs, Metrics)',
Expand All @@ -214,7 +214,7 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics)',
path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'),
gzip: true,
limit: '81 KB',
limit: '82 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Feedback)',
Expand All @@ -241,7 +241,7 @@ module.exports = [
path: createCDNPath('bundle.tracing.min.js'),
gzip: false,
brotli: false,
limit: '128 KB',
limit: '129 KB',
},
{
name: 'CDN Bundle (incl. Logs, Metrics) - uncompressed',
Expand Down
18 changes: 15 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

### Important Changes

- **feat(tanstackstart-react): Add global sentry exception middlewares ([#19330](https://github.com/getsentry/sentry-javascript/pull/19330))**

The `sentryGlobalRequestMiddleware` and `sentryGlobalFunctionMiddleware` global middlewares capture unhandled exceptions thrown in TanStack Start API routes and server functions. Add them as the first entries in the `requestMiddleware` and `functionMiddleware` arrays of `createStart()`:
Expand All @@ -18,11 +20,21 @@
});
```

### Important Changes
- **fix(node-core): Reduce bundle size by removing apm-js-collab and requiring pino >= 9.10 ([#18631](https://github.com/getsentry/sentry-javascript/pull/18631))**

In order to keep receiving pino logs, you need to update your pino version to >= 9.10, the reason for the support bump is to reduce the bundle size of the node-core SDK in frameworks that cannot tree-shake the apm-js-collab dependency.

- **fix(browser): Ensure user id is consistently added to sessions ([#19341](https://github.com/getsentry/sentry-javascript/pull/19341))**

- fix(node-core): Reduce bundle size by removing apm-js-collab and requiring pino >= 9.10 ([#18631](https://github.com/getsentry/sentry-javascript/pull/18631))
Previously, the SDK inconsistently set the user id on sessions, meaning sessions were often lacking proper coupling to the user set for example via `Sentry.setUser()`.
Additionally, the SDK incorrectly skipped starting a new session for the first soft navigation after the pageload.
This patch fixes these issues. As a result, metrics around sessions, like "Crash Free Sessions" or "Crash Free Users" might change.
This could also trigger alerts, depending on your set thresholds and conditions.
We apologize for any inconvenience caused!

In order to keep receiving pino logs, you need to update your pino version to >= 9.10, the reason for the support bump is to reduce the bundle size of the node-core SDK in frameworks that cannot tree-shake the apm-js-collab dependency.
While we're at it, if you're using Sentry in a Single Page App or meta framework, you might want to give the new `'page'` session lifecycle a try!
This new mode no longer creates a session per soft navigation but continues the initial session until the next hard page refresh.
Check out the [docs](https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/integrations/browsersession/) to learn more!

## 10.39.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<meta charset="utf-8" />
</head>
<body>
<a id="navigate" href="foo">Navigate</a>
<a id="navigate" href="#foo">Navigate</a>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
import type { Route } from '@playwright/test';
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
import { waitForSession } from '../../../utils/helpers';

sentryTest('should start a new session on pageload.', async ({ getLocalTestUrl, page }) => {
sentryTest('starts a new session on pageload.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const session = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const sessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');

await page.goto(url);
const session = await sessionPromise;

expect(session).toBeDefined();
expect(session.init).toBe(true);
expect(session.errors).toBe(0);
expect(session.status).toBe('ok');
expect(session.did).toBe('1337');
expect(session).toEqual({
attrs: {
environment: 'production',
release: '0.1',
user_agent: expect.any(String),
},
did: '1337',
errors: 0,
init: true,
sid: expect.any(String),
started: expect.any(String),
status: 'ok',
timestamp: expect.any(String),
});
});

sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => {
sentryTest('starts a new session with navigation.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const initSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');

// Route must be set up before any navigation to avoid race conditions
await page.route('**/foo', (route: Route) => route.continue({ url }));

const initSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
await page.goto(url);
const initSession = await initSessionPromise;

const newSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.locator('#navigate').click();

const newSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const newSession = await newSessionPromise;

expect(newSession).toBeDefined();
expect(newSession.init).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ document.getElementById('manual-session').addEventListener('click', () => {
Sentry.startSession();
Sentry.captureException('Test error from manual session');
});

document.getElementById('error').addEventListener('click', () => {
throw new Error('Test error from error button');
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
<body>
<button id="navigate">Navigate via pushState</button>
<button id="manual-session">Manual session</button>
<button id="error">Throw error</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,45 +1,53 @@
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import type { SerializedSession } from '@sentry/core/src';
import { sentryTest } from '../../../utils/fixtures';
import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
waitForErrorRequest,
waitForSession,
} from '../../../utils/helpers';

sentryTest('should start a session on pageload with page lifecycle.', async ({ getLocalTestUrl, page }) => {
sentryTest('starts a session on pageload with page lifecycle.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const sessions = await getMultipleSentryEnvelopeRequests<SessionContext>(page, 1, {
url,
envelopeType: 'session',
timeout: 2000,
});
const sessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const session = await sessionPromise;

expect(sessions.length).toBeGreaterThanOrEqual(1);
const session = sessions[0];
expect(session).toBeDefined();
expect(session.init).toBe(true);
expect(session.errors).toBe(0);
expect(session.status).toBe('ok');
expect(session).toEqual({
attrs: {
environment: 'production',
release: '0.1',
user_agent: expect.any(String),
},
errors: 0,
init: true,
sid: expect.any(String),
started: expect.any(String),
status: 'ok',
timestamp: expect.any(String),
});
});

sentryTest(
'should NOT start a new session on pushState navigation with page lifecycle.',
"doesn't start a new session on pushState navigation with page lifecycle.",
async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const sessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
const sessionsPromise = getMultipleSentryEnvelopeRequests<SerializedSession>(page, 10, {
url,
envelopeType: 'session',
timeout: 4000,
});

const manualSessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
const manualSessionsPromise = getMultipleSentryEnvelopeRequests<SerializedSession>(page, 10, {
envelopeType: 'session',
timeout: 4000,
});

const eventsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
envelopeType: 'event',
timeout: 4000,
});
const eventsPromise = waitForErrorRequest(page, e => e.message === 'Test error from manual session');

await page.waitForSelector('#navigate');

Expand All @@ -56,17 +64,42 @@ sentryTest(
await page.locator('#manual-session').click();

const newSessions = (await manualSessionsPromise).filter(session => session.init);
const events = await eventsPromise;
const event = envelopeRequestParser(await eventsPromise);

expect(newSessions.length).toBe(2);
expect(newSessions[0].init).toBe(true);
expect(newSessions[1].init).toBe(true);
expect(newSessions[1].sid).not.toBe(newSessions[0].sid);
expect(events).toEqual([
expect(event).toEqual(
expect.objectContaining({
level: 'error',
message: 'Test error from manual session',
}),
]);
);
},
);

sentryTest('Updates the session when an error is thrown', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const initialSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const initialSession = await initialSessionPromise;

// for good measure, throw in a few navigations
await page.locator('#navigate').click();
await page.locator('#navigate').click();
await page.locator('#navigate').click();

const updatedSessionPromise = waitForSession(page, s => !s.init && s.status !== 'ok');
await page.locator('#error').click();
const updatedSession = await updatedSessionPromise;

expect(updatedSession).toEqual({
...initialSession,
errors: 1,
init: false,
status: 'crashed',
timestamp: expect.any(String),
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import type { SerializedSession } from '@sentry/core/src';
import { sentryTest } from '../../../utils/fixtures';
import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers';

Expand All @@ -8,7 +8,7 @@ sentryTest(
async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const sessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
const sessionsPromise = getMultipleSentryEnvelopeRequests<SerializedSession>(page, 10, {
url,
envelopeType: 'session',
timeout: 4000,
Expand All @@ -20,8 +20,8 @@ sentryTest(
await page.locator('#navigate').click();
await page.locator('#navigate').click();

const sessions = (await sessionsPromise).filter(session => session.init);
const startedSessions = (await sessionsPromise).filter(session => session.init);

expect(sessions.length).toBe(3);
expect(startedSessions.length).toBe(4);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<meta charset="utf-8" />
</head>
<body>
<a id="navigate" href="foo">Navigate</a>
<a id="navigate" href="#foo">Navigate</a>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Route } from '@playwright/test';
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
import { waitForSession } from '../../../utils/helpers';

sentryTest('should start a new session on pageload.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const session = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const sessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const session = await sessionPromise;

expect(session).toBeDefined();
expect(session.init).toBe(true);
Expand All @@ -16,18 +16,20 @@ sentryTest('should start a new session on pageload.', async ({ getLocalTestUrl,

sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
await page.route('**/foo', (route: Route) => route.continue({ url }));

const initSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const initSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const initSession = await initSessionPromise;

const newSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.locator('#navigate').click();

const newSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const newSession = await newSessionPromise;

expect(newSession).toBeDefined();
expect(newSession.init).toBe(true);
expect(newSession.errors).toBe(0);
expect(newSession.status).toBe('ok');
expect(newSession.sid).toBeDefined();

expect(initSession.sid).not.toBe(newSession.sid);
});
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest, waitForSession } from '../../../utils/helpers';
import { waitForSession } from '../../../utils/helpers';

sentryTest('should update session when an error is thrown.', async ({ getLocalTestUrl, page }) => {
const pageloadSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
const url = await getLocalTestUrl({ testDir: __dirname });

const pageloadSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
await page.goto(url);
const pageloadSession = await pageloadSessionPromise;

const updatedSessionPromise = waitForSession(page);
const updatedSessionPromise = waitForSession(page, s => !s.init);
await page.locator('#throw-error').click();
const updatedSession = await updatedSessionPromise;

expect(pageloadSession).toBeDefined();
expect(pageloadSession.init).toBe(true);
expect(pageloadSession.errors).toBe(0);
expect(updatedSession).toBeDefined();

expect(updatedSession.init).toBe(false);
expect(updatedSession.errors).toBe(1);
expect(updatedSession.status).toBe('crashed');
Expand All @@ -25,7 +26,9 @@ sentryTest('should update session when an error is thrown.', async ({ getLocalTe
sentryTest('should update session when an exception is captured.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const pageloadSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const pageloadSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const pageloadSession = await pageloadSessionPromise;

const updatedSessionPromise = waitForSession(page);
await page.locator('#capture-exception').click();
Expand All @@ -34,6 +37,7 @@ sentryTest('should update session when an exception is captured.', async ({ getL
expect(pageloadSession).toBeDefined();
expect(pageloadSession.init).toBe(true);
expect(pageloadSession.errors).toBe(0);

expect(updatedSession).toBeDefined();
expect(updatedSession.init).toBe(false);
expect(updatedSession.errors).toBe(1);
Expand Down
Loading
Loading