-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathinitOtel.ts
174 lines (151 loc) · 6.1 KB
/
initOtel.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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import moduleModule from 'module';
import { DiagLogLevel, context, diag, propagation, trace } from '@opentelemetry/api';
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
SEMRESATTRS_SERVICE_NAMESPACE,
} from '@opentelemetry/semantic-conventions';
import { GLOBAL_OBJ, SDK_VERSION, consoleSandbox, logger } from '@sentry/core';
import { SentryPropagator, SentrySampler, SentrySpanProcessor } from '@sentry/opentelemetry';
import { createAddHookMessageChannel } from 'import-in-the-middle';
import { DEBUG_BUILD } from '../debug-build';
import { getOpenTelemetryInstrumentationToPreload } from '../integrations/tracing';
import { SentryContextManager } from '../otel/contextManager';
import { isCjs } from '../utils/commonjs';
import type { NodeClient } from './client';
// About 277h - this must fit into new Array(len)!
const MAX_MAX_SPAN_WAIT_DURATION = 1_000_000;
interface AdditionalOpenTelemetryOptions {
/** Additional SpanProcessor instances that should be used. */
spanProcessors?: SpanProcessor[];
}
/**
* Initialize OpenTelemetry for Node.
*/
export function initOpenTelemetry(client: NodeClient, options: AdditionalOpenTelemetryOptions = {}): void {
if (client.getOptions().debug) {
setupOpenTelemetryLogger();
}
const provider = setupOtel(client, options);
client.traceProvider = provider;
}
/** Initialize the ESM loader. */
export function maybeInitializeEsmLoader(): void {
const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number);
// Register hook was added in v20.6.0 and v18.19.0
if (nodeMajor >= 22 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) {
if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered) {
try {
const { addHookMessagePort } = createAddHookMessageChannel();
// @ts-expect-error register is available in these versions
moduleModule.register('import-in-the-middle/hook.mjs', import.meta.url, {
data: { addHookMessagePort, include: [] },
transferList: [addHookMessagePort],
});
} catch (error) {
logger.warn('Failed to register ESM hook', error);
}
}
} else {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] You are using Node.js in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.',
);
});
}
}
interface NodePreloadOptions {
debug?: boolean;
integrations?: string[];
}
/**
* Preload OpenTelemetry for Node.
* This can be used to preload instrumentation early, but set up Sentry later.
* By preloading the OTEL instrumentation wrapping still happens early enough that everything works.
*/
export function preloadOpenTelemetry(options: NodePreloadOptions = {}): void {
const { debug } = options;
if (debug) {
logger.enable();
setupOpenTelemetryLogger();
}
if (!isCjs()) {
maybeInitializeEsmLoader();
}
// These are all integrations that we need to pre-load to ensure they are set up before any other code runs
getPreloadMethods(options.integrations).forEach(fn => {
fn();
if (debug) {
logger.log(`[Sentry] Preloaded ${fn.id} instrumentation`);
}
});
}
function getPreloadMethods(integrationNames?: string[]): ((() => void) & { id: string })[] {
const instruments = getOpenTelemetryInstrumentationToPreload();
if (!integrationNames) {
return instruments;
}
return instruments.filter(instrumentation => integrationNames.includes(instrumentation.id));
}
/** Just exported for tests. */
export function setupOtel(client: NodeClient, options: AdditionalOpenTelemetryOptions = {}): BasicTracerProvider {
// Create and configure NodeTracerProvider
const provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
resource: defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'node',
// eslint-disable-next-line deprecation/deprecation
[SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
[ATTR_SERVICE_VERSION]: SDK_VERSION,
}),
),
forceFlushTimeoutMillis: 500,
spanProcessors: [
new SentrySpanProcessor({
timeout: _clampSpanProcessorTimeout(client.getOptions().maxSpanWaitDuration),
}),
...(options.spanProcessors || []),
],
});
// Register as globals
trace.setGlobalTracerProvider(provider);
propagation.setGlobalPropagator(new SentryPropagator());
context.setGlobalContextManager(new SentryContextManager());
return provider;
}
/** Just exported for tests. */
export function _clampSpanProcessorTimeout(maxSpanWaitDuration: number | undefined): number | undefined {
if (maxSpanWaitDuration == null) {
return undefined;
}
// We guard for a max. value here, because we create an array with this length
// So if this value is too large, this would fail
if (maxSpanWaitDuration > MAX_MAX_SPAN_WAIT_DURATION) {
DEBUG_BUILD &&
logger.warn(`\`maxSpanWaitDuration\` is too high, using the maximum value of ${MAX_MAX_SPAN_WAIT_DURATION}`);
return MAX_MAX_SPAN_WAIT_DURATION;
} else if (maxSpanWaitDuration <= 0 || Number.isNaN(maxSpanWaitDuration)) {
DEBUG_BUILD && logger.warn('`maxSpanWaitDuration` must be a positive number, using default value instead.');
return undefined;
}
return maxSpanWaitDuration;
}
/**
* Setup the OTEL logger to use our own logger.
*/
function setupOpenTelemetryLogger(): void {
const otelLogger = new Proxy(logger as typeof logger & { verbose: (typeof logger)['debug'] }, {
get(target, prop, receiver) {
const actualProp = prop === 'verbose' ? 'debug' : prop;
return Reflect.get(target, actualProp, receiver);
},
});
// Disable diag, to ensure this works even if called multiple times
diag.disable();
diag.setLogger(otelLogger, DiagLogLevel.DEBUG);
}