Skip to content

feat(node-experimental): Update to new Scope APIs #9799

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 15 commits into from
Dec 19, 2023
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export { makeSession, closeSession, updateSession } from './session';
export { SessionFlusher } from './sessionflusher';
export { Scope } from './scope';
export {
notifyEventProcessors,
// eslint-disable-next-line deprecation/deprecation
addGlobalEventProcessor,
} from './eventProcessors';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function filterDuplicates(integrations: Integration[]): Integration[] {
}

/** Gets integrations to install */
export function getIntegrationsToSetup(options: Options): Integration[] {
export function getIntegrationsToSetup(options: Pick<Options, 'defaultIntegrations' | 'integrations'>): Integration[] {
const defaultIntegrations = options.defaultIntegrations || [];
const userIntegrations = options.integrations;

Expand Down
49 changes: 28 additions & 21 deletions packages/node-experimental/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,34 @@ export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanc
export * as Handlers from './sdk/handlers';
export type { Span } from './types';

export { startSpan, startInactiveSpan, getCurrentHub, getClient, getActiveSpan } from '@sentry/opentelemetry';
export { startSpan, startInactiveSpan, getActiveSpan } from '@sentry/opentelemetry';
export {
getClient,
addBreadcrumb,
captureException,
captureEvent,
captureMessage,
addGlobalEventProcessor,
addEventProcessor,
lastEventId,
setContext,
setExtra,
setExtras,
setTag,
setTags,
setUser,
withScope,
withIsolationScope,
// eslint-disable-next-line deprecation/deprecation
configureScope,
getCurrentScope,
getGlobalScope,
getIsolationScope,
setIsolationScope,
setCurrentScope,
} from './sdk/api';
export { getCurrentHub, makeMain } from './sdk/hub';
export { Scope } from './sdk/scope';

export {
makeNodeTransport,
Expand All @@ -24,36 +51,16 @@ export {
extractRequestData,
deepReadDirSync,
getModuleFromFilename,
// eslint-disable-next-line deprecation/deprecation
addGlobalEventProcessor,
addEventProcessor,
addBreadcrumb,
captureException,
captureEvent,
captureMessage,
close,
// eslint-disable-next-line deprecation/deprecation
configureScope,
createTransport,
// eslint-disable-next-line deprecation/deprecation
extractTraceparentData,
flush,
getActiveTransaction,
Hub,
lastEventId,
makeMain,
runWithAsyncContext,
Scope,
SDK_VERSION,
setContext,
setExtra,
setExtras,
setTag,
setTags,
setUser,
spanStatusfromHttpCode,
trace,
withScope,
captureCheckIn,
withMonitor,
hapiErrorPlugin,
Expand Down
7 changes: 7 additions & 0 deletions packages/node-experimental/src/integrations/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { _INTERNAL, getClient, getCurrentHub, getSpanKind, setSpanMetadata } fro
import type { EventProcessor, Hub, Integration } from '@sentry/types';
import { stringMatchesSomePattern } from '@sentry/utils';

import { getIsolationScope, setIsolationScope } from '../sdk/api';
import { Scope } from '../sdk/scope';
import type { NodeExperimentalClient } from '../types';
import { addOriginToSpan } from '../utils/addOriginToSpan';
import { getRequestUrl } from '../utils/getRequestUrl';
Expand Down Expand Up @@ -127,6 +129,11 @@ export class Http implements Integration {
requireParentforIncomingSpans: false,
requestHook: (span, req) => {
this._updateSpan(span, req);

// Update the isolation scope, isolate this request
if (getSpanKind(span) === SpanKind.SERVER) {
setIsolationScope(getIsolationScope().clone());
}
},
responseHook: (span, res) => {
this._addRequestBreadcrumb(span, res);
Expand Down
29 changes: 29 additions & 0 deletions packages/node-experimental/src/otel/asyncContextStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as api from '@opentelemetry/api';

import { setAsyncContextStrategy } from './../sdk/globals';
import { getCurrentHub } from './../sdk/hub';
import type { CurrentScopes } from './../sdk/types';
import { getScopesFromContext } from './../utils/contextData';

/**
* Sets the async context strategy to use follow the OTEL context under the hood.
* We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts)
*/
export function setOpenTelemetryContextAsyncContextStrategy(): void {
function getScopes(): CurrentScopes | undefined {
const ctx = api.context.active();
return getScopesFromContext(ctx);
}

/* This is more or less a NOOP - we rely on the OTEL context manager for this */
function runWithAsyncContext<T>(callback: () => T): T {
const ctx = api.context.active();

// We depend on the otelContextManager to handle the context/hub
return api.context.with(ctx, () => {
return callback();
});
}

setAsyncContextStrategy({ getScopes, getCurrentHub, runWithAsyncContext });
}
45 changes: 45 additions & 0 deletions packages/node-experimental/src/otel/contextManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Context } from '@opentelemetry/api';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import { setHubOnContext } from '@sentry/opentelemetry';
import { getCurrentHub } from '../sdk/hub';

import { getCurrentScope, getIsolationScope } from './../sdk/api';
import { Scope } from './../sdk/scope';
import type { CurrentScopes } from './../sdk/types';
import { getScopesFromContext, setScopesOnContext } from './../utils/contextData';

/**
* This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager.
* It ensures that we create a new hub per context, so that the OTEL Context & the Sentry Hub are always in sync.
*
* Note that we currently only support AsyncHooks with this,
* but since this should work for Node 14+ anyhow that should be good enough.
*/
export class SentryContextManager extends AsyncLocalStorageContextManager {
/**
* Overwrite with() of the original AsyncLocalStorageContextManager
* to ensure we also create a new hub per context.
*/
public with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
const previousScopes = getScopesFromContext(context);

const currentScope = previousScopes ? previousScopes.scope : getCurrentScope();
const isolationScope = previousScopes ? previousScopes.isolationScope : getIsolationScope();

const newCurrentScope = currentScope.clone();
const scopes: CurrentScopes = { scope: newCurrentScope, isolationScope };

// We also need to "mock" the hub on the context, as the original @sentry/opentelemetry uses that...
const mockHub = { ...getCurrentHub(), getScope: () => scopes.scope };

const ctx1 = setHubOnContext(context, mockHub);
const ctx2 = setScopesOnContext(ctx1, scopes);

return super.with(ctx2, fn, thisArg, ...args);
}
}
Loading