Skip to content

feat(node): Allow to configure skipOpenTelemetrySetup #10907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions packages/node-experimental/src/sdk/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
requestDataIntegration,
startSession,
} from '@sentry/core';
import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry';
import { openTelemetrySetupCheck, setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry';
import type { Client, Integration, Options } from '@sentry/types';
import {
consoleSandbox,
Expand Down Expand Up @@ -117,8 +117,36 @@ export function init(options: NodeOptions | undefined = {}): void {
);
}

// Always init Otel, even if tracing is disabled, because we need it for trace propagation & the HTTP integration
initOtel();
// If users opt-out of this, they _have_ to set up OpenTelemetry themselves
// There is no way to use this SDK without OpenTelemetry!
if (!options.skipOpenTelemetrySetup) {
initOtel();
}

validateOpenTelemetrySetup();
}

function validateOpenTelemetrySetup(): void {
if (!DEBUG_BUILD) {
return;
}

const setup = openTelemetrySetupCheck();

const required = ['SentrySpanProcessor', 'SentryContextManager', 'SentryPropagator'] as const;
for (const k of required) {
if (!setup.includes(k)) {
logger.error(
`You have to set up the ${k}. Without this, the OpenTelemetry & Sentry integration will not work properly.`,
);
}
}

if (!setup.includes('SentrySampler')) {
logger.warn(
'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.',
);
}
}

function getClientOptions(options: NodeOptions): NodeClientOptions {
Expand Down
10 changes: 10 additions & 0 deletions packages/node-experimental/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ export interface BaseNodeOptions {
*/
spotlight?: boolean | string;

/**
* If this is set to true, the SDK will not set up OpenTelemetry automatically.
* In this case, you _have_ to ensure to set it up correctly yourself, including:
* * The `SentrySpanProcessor`
* * The `SentryPropagator`
* * The `SentryContextManager`
* * The `SentrySampler`
*/
skipOpenTelemetrySetup?: boolean;

/** Callback that is executed when a fatal global error occurs. */
onFatalError?(this: void, error: Error): void;
}
Expand Down
17 changes: 17 additions & 0 deletions packages/node-experimental/test/sdk/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Integration } from '@sentry/types';

import * as auto from '../../src/integrations/tracing';
import { getClient } from '../../src/sdk/api';
import type { NodeClient } from '../../src/sdk/client';
import { init } from '../../src/sdk/init';
import { cleanupOtel } from '../helpers/mockSdkInit';

Expand Down Expand Up @@ -119,4 +120,20 @@ describe('init()', () => {
}),
);
});

it('sets up OpenTelemetry by default', () => {
init({ dsn: PUBLIC_DSN });

const client = getClient<NodeClient>();

expect(client.traceProvider).toBeDefined();
});

it('allows to opt-out of OpenTelemetry setup', () => {
init({ dsn: PUBLIC_DSN, skipOpenTelemetrySetup: true });

const client = getClient<NodeClient>();

expect(client.traceProvider).not.toBeDefined();
});
});
5 changes: 5 additions & 0 deletions packages/opentelemetry/src/contextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from './constants';
import { getCurrentHub } from './custom/getCurrentHub';
import { getScopesFromContext, setContextOnScope, setHubOnContext, setScopesOnContext } from './utils/contextData';
import { setIsSetup } from './utils/setupCheck';

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

// @ts-expect-error TS does not like this, but we know this is fine
class SentryContextManager extends ContextManagerClass {
public constructor() {
super();
setIsSetup('SentryContextManager');
}
/**
* Overwrite with() of the original AsyncLocalStorageContextManager
* to ensure we also create a new hub per context.
Expand Down
2 changes: 2 additions & 0 deletions packages/opentelemetry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export { SentryPropagator } from './propagator';
export { SentrySpanProcessor } from './spanProcessor';
export { SentrySampler } from './sampler';

export { openTelemetrySetupCheck } from './utils/setupCheck';

// Legacy
export { getClient } from '@sentry/core';

Expand Down
6 changes: 6 additions & 0 deletions packages/opentelemetry/src/propagator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
SENTRY_TRACE_STATE_PARENT_SPAN_ID,
} from './constants';
import { getScopesFromContext, setScopesOnContext } from './utils/contextData';
import { setIsSetup } from './utils/setupCheck';

/** Get the Sentry propagation context from a span context. */
export function getPropagationContextFromSpanContext(spanContext: SpanContext): PropagationContext {
Expand All @@ -41,6 +42,11 @@ export function getPropagationContextFromSpanContext(spanContext: SpanContext):
* Injects and extracts `sentry-trace` and `baggage` headers from carriers.
*/
export class SentryPropagator extends W3CBaggagePropagator {
public constructor() {
super();
setIsSetup('SentryPropagator');
}

/**
* @inheritDoc
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/opentelemetry/src/sampler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isNaN, logger } from '@sentry/utils';
import { DEBUG_BUILD } from './debug-build';
import { getPropagationContextFromSpanContext } from './propagator';
import { InternalSentrySemanticAttributes } from './semanticAttributes';
import { setIsSetup } from './utils/setupCheck';

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

public constructor(client: Client) {
this._client = client;
setIsSetup('SentrySampler');
}

/** @inheritDoc */
Expand Down
3 changes: 3 additions & 0 deletions packages/opentelemetry/src/spanProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DEBUG_BUILD } from './debug-build';
import { SentrySpanExporter } from './spanExporter';
import { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent';
import { getHubFromContext } from './utils/contextData';
import { setIsSetup } from './utils/setupCheck';
import { getSpanHub, setSpanHub, setSpanParent, setSpanScopes } from './utils/spanData';

function onSpanStart(span: Span, parentContext: Context): void {
Expand Down Expand Up @@ -56,6 +57,8 @@ function onSpanEnd(span: Span): void {
export class SentrySpanProcessor extends BatchSpanProcessor implements SpanProcessorInterface {
public constructor() {
super(new SentrySpanExporter());

setIsSetup('SentrySpanProcessor');
}

/**
Expand Down
18 changes: 18 additions & 0 deletions packages/opentelemetry/src/utils/setupCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type OpenTelemetryElement = 'SentrySpanProcessor' | 'SentryContextManager' | 'SentryPropagator' | 'SentrySampler';

const setupElements = new Set<OpenTelemetryElement>();

/** Get all the OpenTelemetry elements that have been set up. */
export function openTelemetrySetupCheck(): OpenTelemetryElement[] {
return Array.from(setupElements);
}

/** Mark an OpenTelemetry element as setup. */
export function setIsSetup(element: OpenTelemetryElement): void {
setupElements.add(element);
}

/** Only exported for tests. */
export function clearOpenTelemetrySetupCheck(): void {
setupElements.clear();
}
2 changes: 2 additions & 0 deletions packages/opentelemetry/test/helpers/mockSdkInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ClientOptions, Options } from '@sentry/types';

import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core';
import { setOpenTelemetryContextAsyncContextStrategy } from '../../src/asyncContextStrategy';
import { clearOpenTelemetrySetupCheck } from '../../src/utils/setupCheck';
import { init as initTestClient } from './TestClient';
import { initOtel } from './initOtel';

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

export function cleanupOtel(_provider?: BasicTracerProvider): void {
clearOpenTelemetrySetupCheck();
const provider = getProvider(_provider);

if (!provider) {
Expand Down
44 changes: 44 additions & 0 deletions packages/opentelemetry/test/utils/setupCheck.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';

import { SentrySampler } from '../../src/sampler';
import { SentrySpanProcessor } from '../../src/spanProcessor';
import { openTelemetrySetupCheck } from '../../src/utils/setupCheck';
import { TestClient, getDefaultTestClientOptions } from '../helpers/TestClient';
import { setupOtel } from '../helpers/initOtel';
import { cleanupOtel } from '../helpers/mockSdkInit';

describe('openTelemetrySetupCheck', () => {
let provider: BasicTracerProvider | undefined;

beforeEach(() => {
cleanupOtel(provider);
});

afterEach(() => {
cleanupOtel(provider);
});

it('returns empty array by default', () => {
const setup = openTelemetrySetupCheck();
expect(setup).toEqual([]);
});

it('returns all setup parts', () => {
const client = new TestClient(getDefaultTestClientOptions());
provider = setupOtel(client);

const setup = openTelemetrySetupCheck();
expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor', 'SentryPropagator', 'SentryContextManager']);
});

it('returns partial setup parts', () => {
const client = new TestClient(getDefaultTestClientOptions());
provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
});
provider.addSpanProcessor(new SentrySpanProcessor());

const setup = openTelemetrySetupCheck();
expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor']);
});
});