Skip to content

ref(core): Restructure hub exports #10639

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 2 commits into from
Feb 13, 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
101 changes: 101 additions & 0 deletions packages/core/src/asyncContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Hub, Integration } from '@sentry/types';
import { GLOBAL_OBJ } from '@sentry/utils';

export interface RunWithAsyncContextOptions {
/** Whether to reuse an existing async context if one exists. Defaults to false. */
reuseExisting?: boolean;
}

/**
* @private Private API with no semver guarantees!
*
* Strategy used to track async context.
*/
export interface AsyncContextStrategy {
/**
* Gets the current async context. Returns undefined if there is no current async context.
*/
getCurrentHub: () => Hub | undefined;

/**
* Runs the supplied callback in its own async context.
*/
runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions): T;
}

/**
* An object that contains a hub and maintains a scope stack.
* @hidden
*/
export interface Carrier {
__SENTRY__?: SentryCarrier;
}

interface SentryCarrier {
hub?: Hub;
acs?: AsyncContextStrategy;
/**
* Extra Hub properties injected by various SDKs
*/
integrations?: Integration[];
extensions?: {
/** Extension methods for the hub, which are bound to the current Hub instance */
// eslint-disable-next-line @typescript-eslint/ban-types
[key: string]: Function;
};
}

/**
* Returns the global shim registry.
*
* FIXME: This function is problematic, because despite always returning a valid Carrier,
* it has an optional `__SENTRY__` property, which then in turn requires us to always perform an unnecessary check
* at the call-site. We always access the carrier through this function, so we can guarantee that `__SENTRY__` is there.
**/
export function getMainCarrier(): Carrier {
// This ensures a Sentry carrier exists
getSentryCarrier(GLOBAL_OBJ);
return GLOBAL_OBJ;
}

/**
* @private Private API with no semver guarantees!
*
* Sets the global async context strategy
*/
export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefined): void {
// Get main carrier (global for every environment)
const registry = getMainCarrier();
const sentry = getSentryCarrier(registry);
sentry.acs = strategy;
}

/**
* Runs the supplied callback in its own async context. Async Context strategies are defined per SDK.
*
* @param callback The callback to run in its own async context
* @param options Options to pass to the async context strategy
* @returns The result of the callback
*/
export function runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions = {}): T {
const registry = getMainCarrier();
const sentry = getSentryCarrier(registry);

if (sentry.acs) {
return sentry.acs.runWithAsyncContext(callback, options);
}

// if there was no strategy, fallback to just calling the callback
return callback();
}

/** Will either get the existing sentry carrier, or create a new one. */
export function getSentryCarrier(carrier: Carrier): SentryCarrier {
if (!carrier.__SENTRY__) {
carrier.__SENTRY__ = {
extensions: {},
hub: undefined,
};
}
return carrier.__SENTRY__;
}
2 changes: 1 addition & 1 deletion packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ import {
} from '@sentry/utils';

import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
import { getIsolationScope } from './currentScopes';
import { DEBUG_BUILD } from './debug-build';
import { createEventEnvelope, createSessionEnvelope } from './envelope';
import { getIsolationScope } from './hub';
import type { IntegrationIndex } from './integration';
import { afterSetupIntegrations } from './integration';
import { setupIntegration, setupIntegrations } from './integration';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/breadcrumbs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Breadcrumb, BreadcrumbHint } from '@sentry/types';
import { consoleSandbox, dateTimestampInSeconds } from '@sentry/utils';
import { getIsolationScope } from './currentScopes';
import { getClient } from './exports';
import { getIsolationScope } from './hub';

/**
* Default maximum number of breadcrumbs added to an event. Can be overwritten
Expand Down
47 changes: 47 additions & 0 deletions packages/core/src/currentScopes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Scope } from '@sentry/types';
import { getCurrentHub } from './hub';
import { Scope as ScopeClass } from './scope';

/**
* The global scope is kept in this module.
* When accessing it, we'll make sure to set one if none is currently present.
*/
let globalScope: Scope | undefined;

/**
* Get the currently active scope.
*/
export function getCurrentScope(): Scope {
// eslint-disable-next-line deprecation/deprecation
return getCurrentHub().getScope();
}

/**
* Get the currently active isolation scope.
* The isolation scope is active for the current exection context.
*/
export function getIsolationScope(): Scope {
// eslint-disable-next-line deprecation/deprecation
return getCurrentHub().getIsolationScope();
}

/**
* Get the global scope.
* This scope is applied to _all_ events.
*/
export function getGlobalScope(): Scope {
if (!globalScope) {
globalScope = new ScopeClass();
}

return globalScope;
}

/**
* This is mainly needed for tests.
* DO NOT USE this, as this is an internal API and subject to change.
* @hidden
*/
export function setGlobalScope(scope: Scope | undefined): void {
globalScope = scope;
}
27 changes: 11 additions & 16 deletions packages/core/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import type {
User,
} from '@sentry/types';
import { GLOBAL_OBJ, isThenable, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
import { runWithAsyncContext } from './asyncContext';

import { DEFAULT_ENVIRONMENT } from './constants';
import { getCurrentScope, getIsolationScope } from './currentScopes';
import { DEBUG_BUILD } from './debug-build';
import type { Hub } from './hub';
import { runWithAsyncContext } from './hub';
import { getCurrentHub, getIsolationScope } from './hub';
import { getCurrentHub } from './hub';
import type { Scope } from './scope';
import { closeSession, makeSession, updateSession } from './session';
import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent';
Expand Down Expand Up @@ -148,19 +149,21 @@ export function setUser(user: User | null): ReturnType<Hub['setUser']> {
* callback();
* popScope();
*/
export function withScope<T>(callback: (scope: Scope) => T): T;
export function withScope<T>(callback: (scope: ScopeInterface) => T): T;
/**
* Set the given scope as the active scope in the callback.
*/
export function withScope<T>(scope: ScopeInterface | undefined, callback: (scope: Scope) => T): T;
export function withScope<T>(scope: ScopeInterface | undefined, callback: (scope: ScopeInterface) => T): T;
/**
* Either creates a new active scope, or sets the given scope as active scope in the given callback.
*/
export function withScope<T>(
...rest: [callback: (scope: Scope) => T] | [scope: ScopeInterface | undefined, callback: (scope: Scope) => T]
...rest:
| [callback: (scope: ScopeInterface) => T]
| [scope: ScopeInterface | undefined, callback: (scope: ScopeInterface) => T]
): T {
// eslint-disable-next-line deprecation/deprecation
const hub = getCurrentHub();
const hub = getCurrentHub() as Hub;

// If a scope is defined, we want to make this the active scope instead of the default one
if (rest.length === 2) {
Expand Down Expand Up @@ -196,7 +199,7 @@ export function withScope<T>(
* context strategy, the currently active isolation scope may change within execution of the callback.)
* @returns The same value that `callback` returns.
*/
export function withIsolationScope<T>(callback: (isolationScope: Scope) => T): T {
export function withIsolationScope<T>(callback: (isolationScope: ScopeInterface) => T): T {
return runWithAsyncContext(() => {
return callback(getIsolationScope());
});
Expand All @@ -209,7 +212,7 @@ export function withIsolationScope<T>(callback: (isolationScope: Scope) => T): T
* @param callback Execution context in which the provided span will be active. Is passed the newly forked scope.
* @returns the value returned from the provided callback function.
*/
export function withActiveSpan<T>(span: Span, callback: (scope: Scope) => T): T {
export function withActiveSpan<T>(span: Span, callback: (scope: ScopeInterface) => T): T {
return withScope(scope => {
// eslint-disable-next-line deprecation/deprecation
scope.setSpan(span);
Expand Down Expand Up @@ -359,14 +362,6 @@ export function isInitialized(): boolean {
return !!getClient();
}

/**
* Get the currently active scope.
*/
export function getCurrentScope(): Scope {
// eslint-disable-next-line deprecation/deprecation
return getCurrentHub().getScope();
}

/**
* Add an event processor.
* This will be added to the current isolation scope, ensuring any event that is processed in the current execution
Expand Down
Loading