Skip to content
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7846b24
ci(v9): Ensure CI runs on v8 & v9 branches (#14604)
mydea Dec 6, 2024
d1ee444
feat(vue/nuxt)!: No longer create `"update"` spans for component trac…
Dec 10, 2024
26119bf
feat(nextjs)!: Remove `experimental_captureRequestError` (#14607)
Dec 10, 2024
d7e3626
feat(node)!: Collect request sessions via HTTP instrumentation
Dec 11, 2024
30e2329
Merge branch 'v9' into lforst-server-sessions
Dec 11, 2024
5430723
depr notice
Dec 11, 2024
6610144
ci(v9): Ensure CI runs on v8 & v9 branches (#14604)
mydea Dec 6, 2024
1648698
feat(vue/nuxt)!: No longer create `"update"` spans for component trac…
Dec 10, 2024
d9ee102
feat(nextjs)!: Remove `experimental_captureRequestError` (#14607)
Dec 10, 2024
32a1cff
meta(v9): Add v9 migration guide (#14296)
Dec 11, 2024
3add6cd
feat(node)!: Remove `processThreadBreadcrumbIntegration` (#14666)
Dec 12, 2024
8b81476
feat(node)!: Avoid http spans by default for custom OTEL setups (#14678)
mydea Dec 13, 2024
c0f9aaa
Merge remote-tracking branch 'origin/v9' into lforst-server-sessions
Dec 13, 2024
1c9544b
ref!: Don't polyfill optional chaining and nullish coalescing (#14603)
Dec 13, 2024
b2aed90
Merge branch 'v9' into lforst-server-sessions
Dec 13, 2024
ac0d55a
revert
Dec 13, 2024
74ac9c3
Hmm
Dec 13, 2024
5e65c87
ci(v9): Ensure CI runs on v8 & v9 branches (#14604)
mydea Dec 6, 2024
fac8d97
feat(vue/nuxt)!: No longer create `"update"` spans for component trac…
Dec 10, 2024
6772615
feat(nextjs)!: Remove `experimental_captureRequestError` (#14607)
Dec 10, 2024
6e88c55
meta(v9): Add v9 migration guide (#14296)
Dec 11, 2024
bfd865c
feat(node)!: Remove `processThreadBreadcrumbIntegration` (#14666)
Dec 12, 2024
550ceef
feat(node)!: Avoid http spans by default for custom OTEL setups (#14678)
mydea Dec 13, 2024
f792e64
ref!: Don't polyfill optional chaining and nullish coalescing (#14603)
Dec 13, 2024
5c7d64e
Merge branch 'v9' into lforst-server-sessions
Dec 13, 2024
a2ca9b0
crashed and errored
Dec 13, 2024
64ab7f6
rm unneeded tests
Dec 13, 2024
0d38856
More cleanup
Dec 13, 2024
974b37b
stuff
Dec 13, 2024
c3b7e24
formatting
Dec 13, 2024
7800715
Merge remote-tracking branch 'origin/develop' into lforst-server-sess…
Dec 16, 2024
cde5a08
Fix some tests
Dec 16, 2024
eb795ec
hm
Dec 16, 2024
31686ac
desperately try to improve bundle size
Dec 16, 2024
2d084e8
bundle?
Dec 16, 2024
a0fe31d
Merge branch 'develop' into lforst-server-sessions
Dec 16, 2024
084781a
fun
Dec 16, 2024
cae7fc8
Add the most beautiful tests I have ever produced
Dec 16, 2024
3cfd11b
Merge branch 'develop' into lforst-server-sessions
Dec 16, 2024
ad59b24
fix
Dec 16, 2024
d06262c
Merge branch 'develop' into lforst-server-sessions
Dec 16, 2024
0c7f97c
lint
Dec 16, 2024
5940711
Merge remote-tracking branch 'origin/develop' into lforst-server-sess…
Dec 16, 2024
845e454
fix
Dec 16, 2024
d93c352
facepalm
Dec 16, 2024
aabd1a9
Merge remote-tracking branch 'origin/develop' into lforst-server-sess…
Dec 19, 2024
9ab3627
Feedback
Dec 19, 2024
120c34e
messed up merge
Dec 19, 2024
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
2 changes: 1 addition & 1 deletion .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/index.js',
import: createImport('init'),
gzip: true,
limit: '24 KB',
limit: '24.1 KB',
modifyWebpackConfig: function (config) {
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
Expand Down
15 changes: 6 additions & 9 deletions dev-packages/node-integration-tests/suites/sessions/server.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import { loggingTransport } from '@sentry-internal/node-integration-tests';
import type { SessionFlusher } from '@sentry/core';
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
transport: loggingTransport,
integrations: [
Sentry.httpIntegration({
// Flush after 2 seconds (to avoid waiting for the default 60s)
sessionFlushingDelayMS: 2_000,
}),
],
});

import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';
import express from 'express';

const app = express();

// eslint-disable-next-line deprecation/deprecation
const flusher = (Sentry.getClient() as Sentry.NodeClient)['_sessionFlusher'] as SessionFlusher;

// Flush after 2 seconds (to avoid waiting for the default 60s)
setTimeout(() => {
flusher?.flush();
}, 2000);

app.get('/test/success', (_req, res) => {
res.send('Success!');
});
Expand Down
1 change: 1 addition & 0 deletions docs/migration/v8-to-v9.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ The following outlines deprecations that were introduced in version 8 of the SDK
- Deprecated `RequestSessionStatus` type. No replacements.
- Deprecated `SessionFlusherLike` type. No replacements.
- Deprecated `SessionFlusher`. No replacements.
- Deprecated `initSessionFlusher` on `ServerRuntimeClient`. No replacements. The `httpIntegration` will flush sessions by itself.

## `@sentry/nestjs`

Expand Down
31 changes: 24 additions & 7 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
} from './types-hoist';

import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
import { DEFAULT_ENVIRONMENT } from './constants';
import { getCurrentScope, getIsolationScope, getTraceContextFromScope } from './currentScopes';
import { DEBUG_BUILD } from './debug-build';
import { createEventEnvelope, createSessionEnvelope } from './envelope';
Expand All @@ -55,6 +56,7 @@ import { prepareEvent } from './utils/prepareEvent';
import { showSpanDropWarning } from './utils/spanUtils';

const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
const MISSING_RELEASE_FOR_SESSION_ERROR = 'Discarded session because of missing or non-string release';

/**
* Base implementation for all JavaScript SDK clients.
Expand Down Expand Up @@ -236,13 +238,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
* @inheritDoc
*/
public captureSession(session: Session): void {
if (!(typeof session.release === 'string')) {
DEBUG_BUILD && logger.warn('Discarded session because of missing or non-string release');
} else {
this.sendSession(session);
// After sending, we set init false to indicate it's not the first occurrence
updateSession(session, { init: false });
}
this.sendSession(session);
// After sending, we set init false to indicate it's not the first occurrence
updateSession(session, { init: false });
}

/**
Expand Down Expand Up @@ -371,6 +369,25 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
* @inheritDoc
*/
public sendSession(session: Session | SessionAggregates): void {
// Backfill release and environment on session
const { release: clientReleaseOption, environment: clientEnvironmentOption = DEFAULT_ENVIRONMENT } = this._options;
if ('aggregates' in session) {
const sessionAttrs = session.attrs || {};
if (!sessionAttrs.release && !clientReleaseOption) {
DEBUG_BUILD && logger.warn(MISSING_RELEASE_FOR_SESSION_ERROR);
return;
}
sessionAttrs.release = sessionAttrs.release || clientReleaseOption;
sessionAttrs.environment = sessionAttrs.environment || clientEnvironmentOption;
} else {
if (!session.release && !clientReleaseOption) {
DEBUG_BUILD && logger.warn(MISSING_RELEASE_FOR_SESSION_ERROR);
return;
}
session.release = session.release || clientReleaseOption;
session.environment = session.environment || clientEnvironmentOption;
}

const env = createSessionEnvelope(session, this._dsn, this._options._metadata, this._options.tunnel);

// sendEnvelope should not throw
Expand Down
6 changes: 0 additions & 6 deletions packages/core/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type {
User,
} from './types-hoist';

import { DEFAULT_ENVIRONMENT } from './constants';
import { getClient, getCurrentScope, getIsolationScope, withIsolationScope } from './currentScopes';
import { DEBUG_BUILD } from './debug-build';
import { closeSession, makeSession, updateSession } from './session';
Expand Down Expand Up @@ -265,18 +264,13 @@ export function addEventProcessor(callback: EventProcessor): void {
* @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()) || {};

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

const session = makeSession({
release,
environment,
user: currentScope.getUser() || isolationScope.getUser(),
...(userAgent && { userAgent }),
...context,
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ export {
export { setAsyncContextStrategy } from './asyncContext';
export { getGlobalSingleton, getMainCarrier } from './carrier';
export { makeSession, closeSession, updateSession } from './session';
// eslint-disable-next-line deprecation/deprecation
export { SessionFlusher } from './sessionflusher';
export { Scope } from './scope';
export { notifyEventProcessors } from './eventProcessors';
export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from './api';
Expand Down
37 changes: 4 additions & 33 deletions packages/core/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
Extras,
Primitive,
PropagationContext,
RequestSession,
Scope as ScopeInterface,
ScopeContext,
ScopeData,
Expand Down Expand Up @@ -93,10 +92,6 @@ class ScopeClass implements ScopeInterface {
/** Session */
protected _session?: Session;

/** Request Mode Session Status */
// eslint-disable-next-line deprecation/deprecation
protected _requestSession?: RequestSession;

/** The client on this scope */
protected _client?: Client;

Expand Down Expand Up @@ -145,7 +140,6 @@ class ScopeClass implements ScopeInterface {
newScope._transactionName = this._transactionName;
newScope._fingerprint = this._fingerprint;
newScope._eventProcessors = [...this._eventProcessors];
newScope._requestSession = this._requestSession;
newScope._attachments = [...this._attachments];
newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata };
newScope._propagationContext = { ...this._propagationContext };
Expand Down Expand Up @@ -228,23 +222,6 @@ class ScopeClass implements ScopeInterface {
return this._user;
}

/**
* @inheritDoc
*/
// eslint-disable-next-line deprecation/deprecation
public getRequestSession(): RequestSession | undefined {
return this._requestSession;
}

/**
* @inheritDoc
*/
// eslint-disable-next-line deprecation/deprecation
public setRequestSession(requestSession?: RequestSession): this {
this._requestSession = requestSession;
return this;
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -359,13 +336,12 @@ class ScopeClass implements ScopeInterface {

const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext;

const [scopeInstance, requestSession] =
const scopeInstance =
scopeToMerge instanceof Scope
? // eslint-disable-next-line deprecation/deprecation
[scopeToMerge.getScopeData(), scopeToMerge.getRequestSession()]
? scopeToMerge.getScopeData()
: isPlainObject(scopeToMerge)
? [captureContext as ScopeContext, (captureContext as ScopeContext).requestSession]
: [];
? (captureContext as ScopeContext)
: undefined;

const { tags, extra, user, contexts, level, fingerprint = [], propagationContext } = scopeInstance || {};

Expand All @@ -389,10 +365,6 @@ class ScopeClass implements ScopeInterface {
this._propagationContext = propagationContext;
}

if (requestSession) {
this._requestSession = requestSession;
}

return this;
}

Expand All @@ -409,7 +381,6 @@ class ScopeClass implements ScopeInterface {
this._level = undefined;
this._transactionName = undefined;
this._fingerprint = undefined;
this._requestSession = undefined;
this._session = undefined;
_setSpanForScope(this, undefined);
this._attachments = [];
Expand Down
112 changes: 25 additions & 87 deletions packages/core/src/server-runtime-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { createCheckInEnvelope } from './checkin';
import { getIsolationScope, getTraceContextFromScope } from './currentScopes';
import { DEBUG_BUILD } from './debug-build';
import type { Scope } from './scope';
import { SessionFlusher } from './sessionflusher';
import {
getDynamicSamplingContextFromScope,
getDynamicSamplingContextFromSpan,
Expand All @@ -42,9 +41,6 @@ export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportO
export class ServerRuntimeClient<
O extends ClientOptions & ServerRuntimeClientOptions = ServerRuntimeClientOptions,
> extends BaseClient<O> {
// eslint-disable-next-line deprecation/deprecation
protected _sessionFlusher: SessionFlusher | undefined;

/**
* Creates a new Edge SDK instance.
* @param options Configuration options for this SDK.
Expand Down Expand Up @@ -83,86 +79,23 @@ export class ServerRuntimeClient<
* @inheritDoc
*/
public captureException(exception: unknown, hint?: EventHint, scope?: Scope): string {
// Check if `_sessionFlusher` exists because it is initialized (defined) only when the `autoSessionTracking` is enabled.
// The expectation is that session aggregates are only sent when `autoSessionTracking` is enabled.
// TODO(v9): Our goal in the future is to not have the `autoSessionTracking` option and instead rely on integrations doing the creation and sending of sessions. We will not have a central kill-switch for sessions.
// TODO(v9): This should move into the httpIntegration.
// eslint-disable-next-line deprecation/deprecation
if (this._options.autoSessionTracking && this._sessionFlusher) {
// eslint-disable-next-line deprecation/deprecation
const requestSession = getIsolationScope().getRequestSession();

// Necessary checks to ensure this is code block is executed only within a request
// Should override the status only if `requestSession.status` is `Ok`, which is its initial stage
if (requestSession && requestSession.status === 'ok') {
requestSession.status = 'errored';
}
}

setCurrentRequestSessionErroredOrCrashed(hint);
return super.captureException(exception, hint, scope);
}

/**
* @inheritDoc
*/
public captureEvent(event: Event, hint?: EventHint, scope?: Scope): string {
// Check if `_sessionFlusher` exists because it is initialized only when the `autoSessionTracking` is enabled.
// The expectation is that session aggregates are only sent when `autoSessionTracking` is enabled.
// TODO(v9): Our goal in the future is to not have the `autoSessionTracking` option and instead rely on integrations doing the creation and sending of sessions. We will not have a central kill-switch for sessions.
// TODO(v9): This should move into the httpIntegration.
// eslint-disable-next-line deprecation/deprecation
if (this._options.autoSessionTracking && this._sessionFlusher) {
const eventType = event.type || 'exception';
const isException =
eventType === 'exception' && event.exception && event.exception.values && event.exception.values.length > 0;

// If the event is of type Exception, then a request session should be captured
if (isException) {
// eslint-disable-next-line deprecation/deprecation
const requestSession = getIsolationScope().getRequestSession();

// Ensure that this is happening within the bounds of a request, and make sure not to override
// Session Status if Errored / Crashed
if (requestSession && requestSession.status === 'ok') {
requestSession.status = 'errored';
}
}
// If the event is of type Exception, then a request session should be captured
const isException = !event.type && event.exception && event.exception.values && event.exception.values.length > 0;
if (isException) {
setCurrentRequestSessionErroredOrCrashed(hint);
}

return super.captureEvent(event, hint, scope);
}

/**
*
* @inheritdoc
*/
public close(timeout?: number): PromiseLike<boolean> {
if (this._sessionFlusher) {
this._sessionFlusher.close();
}
return super.close(timeout);
}

/**
* Initializes an instance of SessionFlusher on the client which will aggregate and periodically flush session data.
*
* NOTICE: This method will implicitly create an interval that is periodically called.
* To clean up this resources, call `.close()` when you no longer intend to use the client.
* Not doing so will result in a memory leak.
*/
public initSessionFlusher(): void {
const { release, environment } = this._options;
if (!release) {
DEBUG_BUILD && logger.warn('Cannot initialize an instance of SessionFlusher if no release is provided!');
} else {
// eslint-disable-next-line deprecation/deprecation
this._sessionFlusher = new SessionFlusher(this, {
release,
environment,
});
}
}

/**
* Create a cron monitor check in and send it to Sentry.
*
Expand All @@ -173,7 +106,7 @@ export class ServerRuntimeClient<
public captureCheckIn(checkIn: CheckIn, monitorConfig?: MonitorConfig, scope?: Scope): string {
const id = 'checkInId' in checkIn && checkIn.checkInId ? checkIn.checkInId : uuid4();
if (!this._isEnabled()) {
DEBUG_BUILD && logger.warn('SDK not enabled, will not capture checkin.');
DEBUG_BUILD && logger.warn('SDK not enabled, will not capture check-in.');
return id;
}

Expand Down Expand Up @@ -227,20 +160,6 @@ export class ServerRuntimeClient<
return id;
}

/**
* Method responsible for capturing/ending a request session by calling `incrementSessionStatusCount` to increment
* appropriate session aggregates bucket
*
* @deprecated This method should not be used or extended. It's functionality will move into the `httpIntegration` and not be part of any public API.
*/
protected _captureRequestSession(): void {
if (!this._sessionFlusher) {
DEBUG_BUILD && logger.warn('Discarded request mode session because autoSessionTracking option was disabled');
} else {
this._sessionFlusher.incrementSessionStatusCount();
}
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -285,3 +204,22 @@ export class ServerRuntimeClient<
return [dynamicSamplingContext, traceContext];
}
}

function setCurrentRequestSessionErroredOrCrashed(eventHint?: EventHint): void {
const requestSession = getIsolationScope().getScopeData().sdkProcessingMetadata.requestSession;
if (requestSession) {
// We mutate instead of doing `setSdkProcessingMetadata` because the http integration stores away a particular
// isolationScope. If that isolation scope is forked, setting the processing metadata here will not mutate the
// original isolation scope that the http integration stored away.
const isHandledException = eventHint?.mechanism?.handled ?? true;
if (isHandledException) {
// A request session can go from "errored" -> "crashed" but not "crashed" -> "errored".
// Crashed (unhandled exception) is worse than errored (handled exception).
if (requestSession.status !== 'crashed') {
requestSession.status = 'errored';
}
} else {
requestSession.status = 'crashed';
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (isHandledException) {
// A request session can go from "errored" -> "crashed" but not "crashed" -> "errored".
// Crashed (unhandled exception) is worse than errored (handled exception).
if (requestSession.status !== 'crashed') {
requestSession.status = 'errored';
}
} else {
requestSession.status = 'crashed';
}
// A request session can go from "errored" -> "crashed" but not "crashed" -> "errored".
// Crashed (unhandled exception) is worse than errored (handled exception).
if (isHandledException && requestSession.status !== 'crashed') {
requestSession.status = 'errored';
} else if (!isHandledException) {
requestSession.status = 'crashed';
}

}
}
Loading
Loading