Skip to content
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