Skip to content

Commit 189afce

Browse files
authored
feat(browser)!: Send session status unhandled instead of crashed for unhandled errors (#22475)
continuation of [#17849](<#17849>) Unhandled errors don't actually crash the browser, so browser sessions now<br>report the new `unhandled` status instead of `crashed`. Node/server sessions<br>are unchanged and continue to use `crashed`. closes #17842
1 parent 833a009 commit 189afce

6 files changed

Lines changed: 71 additions & 13 deletions

File tree

dev-packages/browser-integration-tests/suites/sessions/page-lifecycle/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ sentryTest('Updates the session when an error is thrown', async ({ getLocalTestU
9999
...initialSession,
100100
errors: 1,
101101
init: false,
102-
status: 'crashed',
102+
status: 'unhandled',
103103
timestamp: expect.any(String),
104104
});
105105
});

dev-packages/browser-integration-tests/suites/sessions/update-session/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ sentryTest('should update session when an error is thrown.', async ({ getLocalTe
1919

2020
expect(updatedSession.init).toBe(false);
2121
expect(updatedSession.errors).toBe(1);
22-
expect(updatedSession.status).toBe('crashed');
22+
expect(updatedSession.status).toBe('unhandled');
2323
expect(pageloadSession.sid).toBe(updatedSession.sid);
2424
});
2525

packages/browser/src/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ export class BrowserClient extends Client<BrowserClientOptions> {
8686

8787
super(opts);
8888

89+
// Unhandled errors don't actually crash the browser, so we report `unhandled` rather than `crashed`.
90+
this._unhandledSessionStatus = 'unhandled';
91+
8992
const { userInfo } = this.getDataCollectionOptions();
9093

9194
if (opts._metadata?.sdk) {

packages/browser/test/client.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* @vitest-environment jsdom
33
*/
44

5+
import { getCurrentScope, makeSession, setCurrentClient } from '@sentry/core/browser';
56
import { afterEach, describe, expect, it, vi } from 'vitest';
67
import { applyDefaultOptions, BrowserClient } from '../src/client';
78
import { WINDOW } from '../src/helpers';
@@ -49,6 +50,51 @@ describe('BrowserClient', () => {
4950
expect(flushOutcomesSpy).not.toHaveBeenCalled();
5051
expect(flushSpy).toHaveBeenCalledTimes(1);
5152
});
53+
54+
describe('session status on unhandled errors', () => {
55+
afterEach(() => {
56+
getCurrentScope().setSession(undefined);
57+
getCurrentScope().setClient(undefined);
58+
});
59+
60+
it('sets the session status to "unhandled" for an unhandled exception', () => {
61+
client = new BrowserClient(getDefaultBrowserClientOptions());
62+
setCurrentClient(client);
63+
64+
const session = makeSession();
65+
getCurrentScope().setSession(session);
66+
67+
client.captureException(new Error('test'), { mechanism: { handled: false } });
68+
69+
expect(session.status).toBe('unhandled');
70+
expect(session.errors).toBe(1);
71+
});
72+
73+
it('sets the session status to "unhandled" for a fatal event', () => {
74+
client = new BrowserClient(getDefaultBrowserClientOptions());
75+
setCurrentClient(client);
76+
77+
const session = makeSession();
78+
getCurrentScope().setSession(session);
79+
80+
client.captureEvent({ message: 'test', level: 'fatal' });
81+
82+
expect(session.status).toBe('unhandled');
83+
});
84+
85+
it('keeps the session status "ok" for a handled exception', () => {
86+
client = new BrowserClient(getDefaultBrowserClientOptions());
87+
setCurrentClient(client);
88+
89+
const session = makeSession();
90+
getCurrentScope().setSession(session);
91+
92+
client.captureException(new Error('test'));
93+
94+
expect(session.status).toBe('ok');
95+
expect(session.errors).toBe(1);
96+
});
97+
});
5298
});
5399

54100
describe('applyDefaultOptions', () => {

packages/core/src/client.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import type { ParameterizedString } from './types/parameterize';
3232
import type { ReplayEndEvent, ReplayStartEvent } from './types/replay';
3333
import type { RequestEventData } from './types/request';
3434
import type { SdkMetadata } from './types/sdkmetadata';
35-
import type { Session, SessionAggregates } from './types/session';
35+
import type { Session, SessionAggregates, SessionStatus } from './types/session';
3636
import type { SeverityLevel } from './types/severity';
3737
import type { Span, SpanAttributes, SpanContextData, SpanJSON, StreamedSpanJSON } from './types/span';
3838
import type { StartSpanOptions } from './types/startSpanOptions';
@@ -220,6 +220,14 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
220220

221221
protected readonly _dataCollection: ResolvedDataCollection;
222222

223+
/**
224+
* The session status to set when an unhandled error terminates a session.
225+
*
226+
* Defaults to `'crashed'`. Browser SDKs override this to `'unhandled'` because unhandled errors
227+
* don't actually crash the browser.
228+
*/
229+
protected _unhandledSessionStatus: SessionStatus;
230+
223231
/**
224232
* Initializes this client instance.
225233
*
@@ -234,6 +242,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
234242
this._eventProcessors = [];
235243
this._promiseBuffer = makePromiseBuffer(options.transportOptions?.bufferSize ?? DEFAULT_TRANSPORT_BUFFER_SIZE);
236244
this._dataCollection = resolveDataCollectionOptions(options);
245+
this._unhandledSessionStatus = 'crashed';
237246

238247
if (options.dsn) {
239248
this._dsn = makeDsn(options.dsn);
@@ -1254,34 +1263,34 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
12541263

12551264
/** Updates existing session based on the provided event */
12561265
protected _updateSessionFromEvent(session: Session, event: Event): void {
1257-
// initially, set `crashed` based on the event level and update from exceptions if there are any later on
1258-
let crashed = event.level === 'fatal';
1266+
// initially, set `unhandled` based on the event level and update from exceptions if there are any later on
1267+
let unhandled = event.level === 'fatal';
12591268
let errored = false;
12601269
const exceptions = event.exception?.values;
12611270

12621271
if (exceptions) {
12631272
errored = true;
1264-
// reset crashed to false if there are exceptions, to ensure `mechanism.handled` is respected.
1265-
crashed = false;
1273+
// reset `unhandled` to false if there are exceptions, to ensure `mechanism.handled` is respected.
1274+
unhandled = false;
12661275

12671276
for (const ex of exceptions) {
12681277
if (ex.mechanism?.handled === false) {
1269-
crashed = true;
1278+
unhandled = true;
12701279
break;
12711280
}
12721281
}
12731282
}
12741283

12751284
// A session is updated and that session update is sent in only one of the two following scenarios:
12761285
// 1. Session with non terminal status and 0 errors + an error occurred -> Will set error count to 1 and send update
1277-
// 2. Session with non terminal status and 1 error + a crash occurred -> Will set status crashed and send update
1286+
// 2. Session with non terminal status and 1 error + a crash occurred -> Will set status unhandled and send update
12781287
const sessionNonTerminal = session.status === 'ok';
1279-
const shouldUpdateAndSend = (sessionNonTerminal && session.errors === 0) || (sessionNonTerminal && crashed);
1288+
const shouldUpdateAndSend = (sessionNonTerminal && session.errors === 0) || (sessionNonTerminal && unhandled);
12801289

12811290
if (shouldUpdateAndSend) {
12821291
updateSession(session, {
1283-
...(crashed && { status: 'crashed' }),
1284-
errors: session.errors || Number(errored || crashed),
1292+
...(unhandled && { status: this._unhandledSessionStatus }),
1293+
errors: session.errors || Number(errored || unhandled),
12851294
});
12861295
this.captureSession(session);
12871296
}

packages/core/src/types/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface Session {
3030

3131
export type SessionContext = Partial<Session>;
3232

33-
export type SessionStatus = 'ok' | 'exited' | 'crashed' | 'abnormal';
33+
export type SessionStatus = 'ok' | 'exited' | 'crashed' | 'abnormal' | 'unhandled';
3434

3535
/** JSDoc */
3636
export interface SessionAggregates {

0 commit comments

Comments
 (0)