Skip to content

feat(core): Deprecate session APIs on hub and add global replacements #10054

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 5, 2024
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
7 changes: 6 additions & 1 deletion MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ Sentry.init({

In v8, the Hub class will be removed. The following methods are therefore deprecated:

- `hub.shouldSendDefaultPii()`: Access Sentry client option via `Sentry.getClient().getOptions().sendDefaultPii` instead
* `hub.startTransaction()`: See [Deprecation of `startTransaction`](#deprecate-starttransaction)
* `hub.lastEventId()`: See [Deprecation of `lastEventId`](#deprecate-sentrylasteventid-and-hublasteventid)
* `hub.startSession()`: Use top-level `Sentry.startSession()` instead
* `hub.endSession()`: Use top-level `Sentry.endSession()` instead
* `hub.captureSession()`: Use top-level `Sentry.captureSession()` instead
* `hub.shouldSendDefaultPii()`: Access Sentry client option via `Sentry.getClient().getOptions().sendDefaultPii` instead

## Deprecated fields on `Span` and `Transaction`

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

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '0.1',
// intentionally disabling this, we want to leverage the deprecated hub API
autoSessionTracking: false,
});

// simulate old startSessionTracking behavior
Sentry.getCurrentHub().startSession({ ignoreDuration: true });
Sentry.getCurrentHub().captureSession();
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<a id='navigate' href="foo">Navigate</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Route } from '@playwright/test';
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/types';

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

sentryTest('should start a new session on pageload.', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });
const session = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);

expect(session).toBeDefined();
expect(session.init).toBe(true);
expect(session.errors).toBe(0);
expect(session.status).toBe('ok');
});

sentryTest('should start a new session with navigation.', async ({ getLocalTestPath, page, browserName }) => {
// Navigations get CORS error on Firefox and WebKit as we're using `file://` protocol.
if (browserName !== 'chromium') {
sentryTest.skip();
}

const url = await getLocalTestPath({ testDir: __dirname });
await page.route('**/foo', (route: Route) => route.fulfill({ path: `${__dirname}/dist/index.html` }));

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

await page.click('#navigate');

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

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);
});
25 changes: 6 additions & 19 deletions packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { Hub } from '@sentry/core';
import {
Integrations as CoreIntegrations,
captureSession,
getClient,
getCurrentHub,
getIntegrationsToSetup,
getReportDialogEndpoint,
initAndBind,
startSession,
} from '@sentry/core';
import type { UserFeedback } from '@sentry/types';
import {
Expand Down Expand Up @@ -250,11 +252,6 @@ export function wrap(fn: (...args: any) => any): any {
return internalWrap(fn)();
}

function startSessionOnHub(hub: Hub): void {
hub.startSession({ ignoreDuration: true });
hub.captureSession();
}

/**
* Enable automatic Session Tracking for the initial page load.
*/
Expand All @@ -264,29 +261,19 @@ function startSessionTracking(): void {
return;
}

const hub = getCurrentHub();

// The only way for this to be false is for there to be a version mismatch between @sentry/browser (>= 6.0.0) and
// @sentry/hub (< 5.27.0). In the simple case, there won't ever be such a mismatch, because the two packages are
// pinned at the same version in package.json, but there are edge cases where it's possible. See
// https://github.com/getsentry/sentry-javascript/issues/3207 and
// https://github.com/getsentry/sentry-javascript/issues/3234 and
// https://github.com/getsentry/sentry-javascript/issues/3278.
if (!hub.captureSession) {
return;
}

// The session duration for browser sessions does not track a meaningful
// concept that can be used as a metric.
// Automatically captured sessions are akin to page views, and thus we
// discard their duration.
startSessionOnHub(hub);
startSession({ ignoreDuration: true });
captureSession();

// We want to create a session for every navigation as well
addHistoryInstrumentationHandler(({ from, to }) => {
// Don't create an additional session for the initial route or if the location did not change
if (from !== undefined && from !== to) {
startSessionOnHub(getCurrentHub());
startSession({ ignoreDuration: true });
captureSession();
}
});
}
Expand Down
104 changes: 102 additions & 2 deletions packages/core/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@ import type {
FinishedCheckIn,
MonitorConfig,
Primitive,
Session,
SessionContext,
Severity,
SeverityLevel,
TransactionContext,
User,
} from '@sentry/types';
import { isThenable, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
import { GLOBAL_OBJ, isThenable, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from './constants';
import { DEBUG_BUILD } from './debug-build';
import type { Hub } from './hub';
import { getCurrentHub } from './hub';
import { getCurrentHub, getIsolationScope } from './hub';
import type { Scope } from './scope';
import { closeSession, makeSession, updateSession } from './session';
import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent';
import { parseEventHintOrCaptureContext } from './utils/prepareEvent';

Expand Down Expand Up @@ -322,3 +326,99 @@ export function getClient<C extends Client>(): C | undefined {
export function getCurrentScope(): Scope {
return getCurrentHub().getScope();
}

/**
* Start a session on the current isolation scope.
*
* @param context (optional) additional properties to be applied to the returned session object
*
* @returns the new active session
*/
export function startSession(context?: SessionContext): Session {
const client = getClient();
const isolationScope = getIsolationScope();
const currentScope = getCurrentScope();

const { release, environment = DEFAULT_ENVIRONMENT } = (client && client.getOptions()) || {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: should we maybe just bail out if no client is found? Doesn't matter probably, but for clarity...

Copy link
Member Author

@Lms24 Lms24 Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm thinking about this, if we do so, we have to return undefined which I'm not sure we want to do. Also this means the signature of the replacement would slightly differ from the deprecated hub method. Since we're only taking env and release from the client I think it's okay to continue. Obviously, no
session will be sent but to me this aligns more with the concept of no-op spans for example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, sounds good to me!


// Will fetch userAgent if called from browser sdk
const { userAgent } = GLOBAL_OBJ.navigator || {};

const session = makeSession({
release,
environment,
user: isolationScope.getUser(),
...(userAgent && { userAgent }),
...context,
});

// End existing session if there's one
const currentSession = isolationScope.getSession();
if (currentSession && currentSession.status === 'ok') {
updateSession(currentSession, { status: 'exited' });
}

endSession();

// Afterwards we set the new session on the scope
isolationScope.setSession(session);

// TODO (v8): Remove this and only use the isolation scope(?).
// For v7 though, we can't "soft-break" people using getCurrentHub().getScope().setSession()
currentScope.setSession(session);

return session;
}

/**
* End the session on the current isolation scope.
*/
export function endSession(): void {
const isolationScope = getIsolationScope();
const currentScope = getCurrentScope();

const session = isolationScope.getSession();
if (session) {
closeSession(session);
}
_sendSessionUpdate();

// the session is over; take it off of the scope
isolationScope.setSession();

// TODO (v8): Remove this and only use the isolation scope(?).
// For v7 though, we can't "soft-break" people using getCurrentHub().getScope().setSession()
currentScope.setSession();
}

/**
* Sends the current Session on the scope
*/
function _sendSessionUpdate(): void {
const isolationScope = getIsolationScope();
const currentScope = getCurrentScope();
const client = getClient();
// TODO (v8): Remove currentScope and only use the isolation scope(?).
// For v7 though, we can't "soft-break" people using getCurrentHub().getScope().setSession()
const session = currentScope.getSession() || isolationScope.getSession();
if (session && client && client.captureSession) {
client.captureSession(session);
}
}

/**
* Sends the current session on the scope to Sentry
*
* @param end If set the session will be marked as exited and removed from the scope.
* Defaults to `false`.
*/
export function captureSession(end: boolean = false): void {
// both send the update and pull the session from the scope
if (end) {
endSession();
return;
}

// only send the update
_sendSessionUpdate();
}
6 changes: 6 additions & 0 deletions packages/core/src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,10 +487,13 @@ Sentry.init({...});

/**
* @inheritDoc
*
* @deprecated Use top level `captureSession` instead.
*/
public captureSession(endSession: boolean = false): void {
// both send the update and pull the session from the scope
if (endSession) {
// eslint-disable-next-line deprecation/deprecation
return this.endSession();
}

Expand All @@ -500,6 +503,7 @@ Sentry.init({...});

/**
* @inheritDoc
* @deprecated Use top level `endSession` instead.
*/
public endSession(): void {
const layer = this.getStackTop();
Expand All @@ -516,6 +520,7 @@ Sentry.init({...});

/**
* @inheritDoc
* @deprecated Use top level `startSession` instead.
*/
public startSession(context?: SessionContext): Session {
const { scope, client } = this.getStackTop();
Expand All @@ -537,6 +542,7 @@ Sentry.init({...});
if (currentSession && currentSession.status === 'ok') {
updateSession(currentSession, { status: 'exited' });
}
// eslint-disable-next-line deprecation/deprecation
this.endSession();

// Afterwards we set the new session on the scope
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export {
withScope,
getClient,
getCurrentScope,
startSession,
endSession,
captureSession,
} from './exports';
export {
getCurrentHub,
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/session.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { SerializedSession, Session, SessionContext, SessionStatus } from '@sentry/types';
import { dropUndefinedKeys, timestampInSeconds, uuid4 } from '@sentry/utils';

/**
* Creates a new `Session` object by setting certain default parameters. If optional @param context
* is passed, the passed properties are applied to the session object.
Expand Down
Loading