-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
contextManager.ts
78 lines (68 loc) · 3.36 KB
/
contextManager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import type { Context, ContextManager } from '@opentelemetry/api';
import { getCurrentScope, getIsolationScope } from '@sentry/core';
import type { Scope } from '@sentry/types';
import {
SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY,
SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY,
SENTRY_FORK_SET_SCOPE_CONTEXT_KEY,
} from './constants';
import { getCurrentHub } from './custom/getCurrentHub';
import { getScopesFromContext, setHubOnContext, setScopesOnContext } from './utils/contextData';
/**
* Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Hub.
*
* Usage:
* import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
* const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager);
* const contextManager = new SentryContextManager();
*/
export function wrapContextManagerClass<ContextManagerInstance extends ContextManager>(
ContextManagerClass: new (...args: unknown[]) => ContextManagerInstance,
): typeof ContextManagerClass {
/**
* 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.
*/
// @ts-expect-error TS does not like this, but we know this is fine
class SentryContextManager extends ContextManagerClass {
/**
* 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 currentScopes = getScopesFromContext(context);
const currentScope = currentScopes?.scope || getCurrentScope();
const currentIsolationScope = currentScopes?.isolationScope || getIsolationScope();
const shouldForkIsolationScope = context.getValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) === true;
const scope = context.getValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) as Scope | undefined;
const isolationScope = context.getValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY) as Scope | undefined;
const newCurrentScope = scope || currentScope.clone();
const newIsolationScope =
isolationScope || (shouldForkIsolationScope ? currentIsolationScope.clone() : currentIsolationScope);
const scopes = { scope: newCurrentScope, isolationScope: newIsolationScope };
const mockHub = {
// eslint-disable-next-line deprecation/deprecation
...getCurrentHub(),
getScope: () => newCurrentScope,
getIsolationScope: () => newIsolationScope,
};
const ctx1 = setHubOnContext(context, mockHub);
const ctx2 = setScopesOnContext(ctx1, scopes);
// Remove the unneeded values again
const ctx3 = ctx2
.deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY)
.deleteValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY)
.deleteValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY);
return super.with(ctx3, fn, thisArg, ...args);
}
}
return SentryContextManager as unknown as typeof ContextManagerClass;
}