Skip to content

Commit 2fa83c5

Browse files
committed
feat(node): Allow to configure skipOpenTelemetrySetup
Also warn if there is an incomplete setup, to ensure users notice if stuff is breaking.
1 parent f655931 commit 2fa83c5

File tree

11 files changed

+140
-3
lines changed

11 files changed

+140
-3
lines changed

packages/node-experimental/src/sdk/init.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
requestDataIntegration,
1212
startSession,
1313
} from '@sentry/core';
14-
import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry';
14+
import { openTelemetrySetupCheck, setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry';
1515
import type { Client, Integration, Options } from '@sentry/types';
1616
import {
1717
consoleSandbox,
@@ -117,8 +117,36 @@ export function init(options: NodeOptions | undefined = {}): void {
117117
);
118118
}
119119

120-
// Always init Otel, even if tracing is disabled, because we need it for trace propagation & the HTTP integration
121-
initOtel();
120+
// If users opt-out of this, they _have_ to set up OpenTelemetry themselves
121+
// There is no way to use this SDK without OpenTelemetry!
122+
if (!options.skipOpenTelemetrySetup) {
123+
initOtel();
124+
}
125+
126+
validateOpenTelemetrySetup();
127+
}
128+
129+
function validateOpenTelemetrySetup(): void {
130+
if (!DEBUG_BUILD) {
131+
return;
132+
}
133+
134+
const setup = openTelemetrySetupCheck();
135+
136+
const required = ['SentrySpanProcessor', 'SentryContextManager', 'SentryPropagator'] as const;
137+
for (const k of required) {
138+
if (!setup.includes(k)) {
139+
logger.error(
140+
`You have to set up the ${k}. Without this, the OpenTelemetry & Sentry integration will not work properly.`,
141+
);
142+
}
143+
}
144+
145+
if (!setup.includes('SentrySampler')) {
146+
logger.warn(
147+
'You have to set up the SentrySampler. Without this, the OpenTelemetry & Sentry integration may still work, but sample rates set for the Sentry SDK will not be respected.',
148+
);
149+
}
122150
}
123151

124152
function getClientOptions(options: NodeOptions): NodeClientOptions {

packages/node-experimental/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ export interface BaseNodeOptions {
6464
*/
6565
spotlight?: boolean | string;
6666

67+
/**
68+
* If this is set to true, the SDK will not set up OpenTelemetry automatically.
69+
* In this case, you _have_ to ensure to set it up correctly yourself, including:
70+
* * The `SentrySpanProcessor`
71+
* * The `SentryPropagator`
72+
* * The `SentryContextManager`
73+
* * The `SentrySampler`
74+
*/
75+
skipOpenTelemetrySetup?: boolean;
76+
6777
/** Callback that is executed when a fatal global error occurs. */
6878
onFatalError?(this: void, error: Error): void;
6979
}

packages/node-experimental/test/sdk/init.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Integration } from '@sentry/types';
22

33
import * as auto from '../../src/integrations/tracing';
44
import { getClient } from '../../src/sdk/api';
5+
import type { NodeClient } from '../../src/sdk/client';
56
import { init } from '../../src/sdk/init';
67
import { cleanupOtel } from '../helpers/mockSdkInit';
78

@@ -119,4 +120,20 @@ describe('init()', () => {
119120
}),
120121
);
121122
});
123+
124+
it('sets up OpenTelemetry by default', () => {
125+
init({ dsn: PUBLIC_DSN });
126+
127+
const client = getClient<NodeClient>();
128+
129+
expect(client.traceProvider).toBeDefined();
130+
});
131+
132+
it('allows to opt-out of OpenTelemetry setup', () => {
133+
init({ dsn: PUBLIC_DSN, skipOpenTelemetrySetup: true });
134+
135+
const client = getClient<NodeClient>();
136+
137+
expect(client.traceProvider).not.toBeDefined();
138+
});
122139
});

packages/opentelemetry/src/contextManager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from './constants';
1010
import { getCurrentHub } from './custom/getCurrentHub';
1111
import { getScopesFromContext, setContextOnScope, setHubOnContext, setScopesOnContext } from './utils/contextData';
12+
import { setIsSetup } from './utils/setupCheck';
1213

1314
/**
1415
* Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Hub.
@@ -31,6 +32,10 @@ export function wrapContextManagerClass<ContextManagerInstance extends ContextMa
3132

3233
// @ts-expect-error TS does not like this, but we know this is fine
3334
class SentryContextManager extends ContextManagerClass {
35+
public constructor() {
36+
super();
37+
setIsSetup('SentryContextManager');
38+
}
3439
/**
3540
* Overwrite with() of the original AsyncLocalStorageContextManager
3641
* to ensure we also create a new hub per context.

packages/opentelemetry/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export { SentryPropagator } from './propagator';
4343
export { SentrySpanProcessor } from './spanProcessor';
4444
export { SentrySampler } from './sampler';
4545

46+
export { openTelemetrySetupCheck } from './utils/setupCheck';
47+
4648
// Legacy
4749
export { getClient } from '@sentry/core';
4850

packages/opentelemetry/src/propagator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
SENTRY_TRACE_STATE_PARENT_SPAN_ID,
1919
} from './constants';
2020
import { getScopesFromContext, setScopesOnContext } from './utils/contextData';
21+
import { setIsSetup } from './utils/setupCheck';
2122

2223
/** Get the Sentry propagation context from a span context. */
2324
export function getPropagationContextFromSpanContext(spanContext: SpanContext): PropagationContext {
@@ -41,6 +42,11 @@ export function getPropagationContextFromSpanContext(spanContext: SpanContext):
4142
* Injects and extracts `sentry-trace` and `baggage` headers from carriers.
4243
*/
4344
export class SentryPropagator extends W3CBaggagePropagator {
45+
public constructor() {
46+
super();
47+
setIsSetup('SentryPropagator');
48+
}
49+
4450
/**
4551
* @inheritDoc
4652
*/

packages/opentelemetry/src/sampler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { isNaN, logger } from '@sentry/utils';
1010
import { DEBUG_BUILD } from './debug-build';
1111
import { getPropagationContextFromSpanContext } from './propagator';
1212
import { InternalSentrySemanticAttributes } from './semanticAttributes';
13+
import { setIsSetup } from './utils/setupCheck';
1314

1415
/**
1516
* A custom OTEL sampler that uses Sentry sampling rates to make it's decision
@@ -19,6 +20,7 @@ export class SentrySampler implements Sampler {
1920

2021
public constructor(client: Client) {
2122
this._client = client;
23+
setIsSetup('SentrySampler');
2224
}
2325

2426
/** @inheritDoc */

packages/opentelemetry/src/spanProcessor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { DEBUG_BUILD } from './debug-build';
99
import { SentrySpanExporter } from './spanExporter';
1010
import { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent';
1111
import { getHubFromContext } from './utils/contextData';
12+
import { setIsSetup } from './utils/setupCheck';
1213
import { getSpanHub, setSpanHub, setSpanParent, setSpanScopes } from './utils/spanData';
1314

1415
function onSpanStart(span: Span, parentContext: Context): void {
@@ -56,6 +57,8 @@ function onSpanEnd(span: Span): void {
5657
export class SentrySpanProcessor extends BatchSpanProcessor implements SpanProcessorInterface {
5758
public constructor() {
5859
super(new SentrySpanExporter());
60+
61+
setIsSetup('SentrySpanProcessor');
5962
}
6063

6164
/**
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
type OpenTelemetryElement = 'SentrySpanProcessor' | 'SentryContextManager' | 'SentryPropagator' | 'SentrySampler';
2+
3+
const setupElements = new Set<OpenTelemetryElement>();
4+
5+
/** Get all the OpenTelemetry elements that have been set up. */
6+
export function openTelemetrySetupCheck(): OpenTelemetryElement[] {
7+
return Array.from(setupElements);
8+
}
9+
10+
/** Mark an OpenTelemetry element as setup. */
11+
export function setIsSetup(element: OpenTelemetryElement): void {
12+
setupElements.add(element);
13+
}
14+
15+
/** Only exported for tests. */
16+
export function clearOpenTelemetrySetupCheck(): void {
17+
setupElements.clear();
18+
}

packages/opentelemetry/test/helpers/mockSdkInit.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ClientOptions, Options } from '@sentry/types';
44

55
import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core';
66
import { setOpenTelemetryContextAsyncContextStrategy } from '../../src/asyncContextStrategy';
7+
import { clearOpenTelemetrySetupCheck } from '../../src/utils/setupCheck';
78
import { init as initTestClient } from './TestClient';
89
import { initOtel } from './initOtel';
910

@@ -32,6 +33,7 @@ export function mockSdkInit(options?: Partial<ClientOptions>) {
3233
}
3334

3435
export function cleanupOtel(_provider?: BasicTracerProvider): void {
36+
clearOpenTelemetrySetupCheck();
3537
const provider = getProvider(_provider);
3638

3739
if (!provider) {

0 commit comments

Comments
 (0)