Skip to content

Commit 605fd50

Browse files
mydeaLuca Forstner
and
Luca Forstner
authored
feat(node-experimental): Update to new Scope APIs (#9799)
This PR introduces the new scope APIs to node-experimental. * `getCurrentHub()` is still around, but just a mock hub that uses other methods under the hood. * Instead, there are the following new APIs: * `getCurrentScope()` * `getIsolationScope()` * `getGlobalScope()` * `withIsolationScope()` Mostly existing tests should cover this OK. The main change here is that for spans, since we use the isolation scope any tags etc. added while the span is running are _also_ added to the resulting event. For POTEL, we automatically set an isolation scope whenever a http.server span is generated. Replaces #9419 --------- Co-authored-by: Luca Forstner <[email protected]>
1 parent 84299d0 commit 605fd50

28 files changed

+2387
-135
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export { makeSession, closeSession, updateSession } from './session';
4444
export { SessionFlusher } from './sessionflusher';
4545
export { Scope } from './scope';
4646
export {
47+
notifyEventProcessors,
4748
// eslint-disable-next-line deprecation/deprecation
4849
addGlobalEventProcessor,
4950
} from './eventProcessors';

packages/core/src/integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function filterDuplicates(integrations: Integration[]): Integration[] {
4646
}
4747

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

packages/node-experimental/src/index.ts

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,34 @@ export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanc
1313
export * as Handlers from './sdk/handlers';
1414
export type { Span } from './types';
1515

16-
export { startSpan, startInactiveSpan, getCurrentHub, getClient, getActiveSpan } from '@sentry/opentelemetry';
16+
export { startSpan, startInactiveSpan, getActiveSpan } from '@sentry/opentelemetry';
17+
export {
18+
getClient,
19+
addBreadcrumb,
20+
captureException,
21+
captureEvent,
22+
captureMessage,
23+
addGlobalEventProcessor,
24+
addEventProcessor,
25+
lastEventId,
26+
setContext,
27+
setExtra,
28+
setExtras,
29+
setTag,
30+
setTags,
31+
setUser,
32+
withScope,
33+
withIsolationScope,
34+
// eslint-disable-next-line deprecation/deprecation
35+
configureScope,
36+
getCurrentScope,
37+
getGlobalScope,
38+
getIsolationScope,
39+
setIsolationScope,
40+
setCurrentScope,
41+
} from './sdk/api';
42+
export { getCurrentHub, makeMain } from './sdk/hub';
43+
export { Scope } from './sdk/scope';
1744

1845
export {
1946
makeNodeTransport,
@@ -24,36 +51,16 @@ export {
2451
extractRequestData,
2552
deepReadDirSync,
2653
getModuleFromFilename,
27-
// eslint-disable-next-line deprecation/deprecation
28-
addGlobalEventProcessor,
29-
addEventProcessor,
30-
addBreadcrumb,
31-
captureException,
32-
captureEvent,
33-
captureMessage,
3454
close,
35-
// eslint-disable-next-line deprecation/deprecation
36-
configureScope,
3755
createTransport,
3856
// eslint-disable-next-line deprecation/deprecation
3957
extractTraceparentData,
4058
flush,
41-
getActiveTransaction,
4259
Hub,
43-
lastEventId,
44-
makeMain,
4560
runWithAsyncContext,
46-
Scope,
4761
SDK_VERSION,
48-
setContext,
49-
setExtra,
50-
setExtras,
51-
setTag,
52-
setTags,
53-
setUser,
5462
spanStatusfromHttpCode,
5563
trace,
56-
withScope,
5764
captureCheckIn,
5865
withMonitor,
5966
hapiErrorPlugin,

packages/node-experimental/src/integrations/http.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { _INTERNAL, getClient, getCurrentHub, getSpanKind, setSpanMetadata } fro
88
import type { EventProcessor, Hub, Integration } from '@sentry/types';
99
import { stringMatchesSomePattern } from '@sentry/utils';
1010

11+
import { getIsolationScope, setIsolationScope } from '../sdk/api';
12+
import { Scope } from '../sdk/scope';
1113
import type { NodeExperimentalClient } from '../types';
1214
import { addOriginToSpan } from '../utils/addOriginToSpan';
1315
import { getRequestUrl } from '../utils/getRequestUrl';
@@ -127,6 +129,11 @@ export class Http implements Integration {
127129
requireParentforIncomingSpans: false,
128130
requestHook: (span, req) => {
129131
this._updateSpan(span, req);
132+
133+
// Update the isolation scope, isolate this request
134+
if (getSpanKind(span) === SpanKind.SERVER) {
135+
setIsolationScope(getIsolationScope().clone());
136+
}
130137
},
131138
responseHook: (span, res) => {
132139
this._addRequestBreadcrumb(span, res);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as api from '@opentelemetry/api';
2+
3+
import { setAsyncContextStrategy } from './../sdk/globals';
4+
import { getCurrentHub } from './../sdk/hub';
5+
import type { CurrentScopes } from './../sdk/types';
6+
import { getScopesFromContext } from './../utils/contextData';
7+
8+
/**
9+
* Sets the async context strategy to use follow the OTEL context under the hood.
10+
* We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts)
11+
*/
12+
export function setOpenTelemetryContextAsyncContextStrategy(): void {
13+
function getScopes(): CurrentScopes | undefined {
14+
const ctx = api.context.active();
15+
return getScopesFromContext(ctx);
16+
}
17+
18+
/* This is more or less a NOOP - we rely on the OTEL context manager for this */
19+
function runWithAsyncContext<T>(callback: () => T): T {
20+
const ctx = api.context.active();
21+
22+
// We depend on the otelContextManager to handle the context/hub
23+
return api.context.with(ctx, () => {
24+
return callback();
25+
});
26+
}
27+
28+
setAsyncContextStrategy({ getScopes, getCurrentHub, runWithAsyncContext });
29+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { Context } from '@opentelemetry/api';
2+
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
3+
import { setHubOnContext } from '@sentry/opentelemetry';
4+
import { getCurrentHub } from '../sdk/hub';
5+
6+
import { getCurrentScope, getIsolationScope } from './../sdk/api';
7+
import { Scope } from './../sdk/scope';
8+
import type { CurrentScopes } from './../sdk/types';
9+
import { getScopesFromContext, setScopesOnContext } from './../utils/contextData';
10+
11+
/**
12+
* This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager.
13+
* It ensures that we create a new hub per context, so that the OTEL Context & the Sentry Hub are always in sync.
14+
*
15+
* Note that we currently only support AsyncHooks with this,
16+
* but since this should work for Node 14+ anyhow that should be good enough.
17+
*/
18+
export class SentryContextManager extends AsyncLocalStorageContextManager {
19+
/**
20+
* Overwrite with() of the original AsyncLocalStorageContextManager
21+
* to ensure we also create a new hub per context.
22+
*/
23+
public with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
24+
context: Context,
25+
fn: F,
26+
thisArg?: ThisParameterType<F>,
27+
...args: A
28+
): ReturnType<F> {
29+
const previousScopes = getScopesFromContext(context);
30+
31+
const currentScope = previousScopes ? previousScopes.scope : getCurrentScope();
32+
const isolationScope = previousScopes ? previousScopes.isolationScope : getIsolationScope();
33+
34+
const newCurrentScope = currentScope.clone();
35+
const scopes: CurrentScopes = { scope: newCurrentScope, isolationScope };
36+
37+
// We also need to "mock" the hub on the context, as the original @sentry/opentelemetry uses that...
38+
const mockHub = { ...getCurrentHub(), getScope: () => scopes.scope };
39+
40+
const ctx1 = setHubOnContext(context, mockHub);
41+
const ctx2 = setScopesOnContext(ctx1, scopes);
42+
43+
return super.with(ctx2, fn, thisArg, ...args);
44+
}
45+
}

0 commit comments

Comments
 (0)