Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0fa085a

Browse files
committedMay 21, 2025·
feat(node): Move default option handling from init to NodeClient
Possibly relevant changes: * `client.startClientReportTracking()` is now called in the NodeClient constructor - so unless `sendClientReports: false` is configured, this will always be setup now, even for manual clients. * Env. vars will automatically be picked up and put on the current scope in the NodeClient constructor * `Failed to register ESM hook` warning is now a `console.warn`, not using the logger - this means we do not need to manually enable the logger before we call `initAndBind` anymore. * spotlight is now properly handled like a default integration
1 parent 593d5b3 commit 0fa085a

File tree

9 files changed

+142
-124
lines changed

9 files changed

+142
-124
lines changed
 

‎packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export {
160160
isVueViewModel,
161161
} from './utils-hoist/is';
162162
export { isBrowser } from './utils-hoist/isBrowser';
163-
export { CONSOLE_LEVELS, consoleSandbox, logger, originalConsoleMethods } from './utils-hoist/logger';
163+
export { CONSOLE_LEVELS, consoleSandbox, logger, originalConsoleMethods, enableLogger } from './utils-hoist/logger';
164164
export type { Logger } from './utils-hoist/logger';
165165
export {
166166
addContextToFrame,

‎packages/core/src/sdk.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { Client } from './client';
22
import { getCurrentScope } from './currentScopes';
3-
import { DEBUG_BUILD } from './debug-build';
43
import type { ClientOptions } from './types-hoist/options';
5-
import { consoleSandbox, logger } from './utils-hoist/logger';
4+
import { enableLogger } from './utils-hoist/logger';
65

76
/** A class object that can instantiate Client objects. */
87
export type ClientClass<F extends Client, O extends ClientOptions> = new (options: O) => F;
@@ -14,25 +13,14 @@ export type ClientClass<F extends Client, O extends ClientOptions> = new (option
1413
* @param clientClass The client class to instantiate.
1514
* @param options Options to pass to the client.
1615
*/
17-
export function initAndBind<F extends Client, O extends ClientOptions>(
18-
clientClass: ClientClass<F, O>,
19-
options: O,
20-
): Client {
21-
if (options.debug === true) {
22-
if (DEBUG_BUILD) {
23-
logger.enable();
24-
} else {
25-
// use `console.warn` rather than `logger.warn` since by non-debug bundles have all `logger.x` statements stripped
26-
consoleSandbox(() => {
27-
// eslint-disable-next-line no-console
28-
console.warn('[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.');
29-
});
30-
}
16+
export function initAndBind<F extends Client, O extends ClientOptions>(ClientClass: ClientClass<F, O>, options: O): F {
17+
if (options.debug) {
18+
enableLogger();
3119
}
3220
const scope = getCurrentScope();
3321
scope.update(options.initialScope);
3422

35-
const client = new clientClass(options);
23+
const client = new ClientClass(options);
3624
setCurrentClient(client);
3725
client.init();
3826
return client;

‎packages/core/src/utils-hoist/logger.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,16 @@ function makeLogger(): Logger {
100100
* The logger is a singleton on the carrier, to ensure that a consistent logger is used throughout the SDK.
101101
*/
102102
export const logger = getGlobalSingleton('logger', makeLogger);
103+
104+
/** Enables the logger, or log a warning if DEBUG_BUILD is false. */
105+
export function enableLogger(): void {
106+
if (DEBUG_BUILD) {
107+
logger.enable();
108+
} else {
109+
// use `console.warn` rather than `logger.warn` since by non-debug bundles have all `logger.x` statements stripped
110+
consoleSandbox(() => {
111+
// eslint-disable-next-line no-console
112+
console.warn('[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.');
113+
});
114+
}
115+
}

‎packages/node/src/sdk/client.ts

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ import type { Tracer } from '@opentelemetry/api';
33
import { trace } from '@opentelemetry/api';
44
import { registerInstrumentations } from '@opentelemetry/instrumentation';
55
import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
6-
import type { DynamicSamplingContext, Scope, ServerRuntimeClientOptions, TraceContext } from '@sentry/core';
6+
import type { DynamicSamplingContext, Scope, TraceContext } from '@sentry/core';
77
import { _INTERNAL_flushLogsBuffer, applySdkMetadata, logger, SDK_VERSION, ServerRuntimeClient } from '@sentry/core';
88
import { getTraceContextForScope } from '@sentry/opentelemetry';
99
import { isMainThread, threadId } from 'worker_threads';
1010
import { DEBUG_BUILD } from '../debug-build';
1111
import type { NodeClientOptions } from '../types';
12+
import { isCjs } from '../utils/commonjs';
13+
import { envToBool } from '../utils/envToBool';
14+
import { getSentryRelease } from './api';
1215

1316
const DEFAULT_CLIENT_REPORT_FLUSH_INTERVAL_MS = 60_000; // 60s was chosen arbitrarily
1417

@@ -21,15 +24,9 @@ export class NodeClient extends ServerRuntimeClient<NodeClientOptions> {
2124
private _logOnExitFlushListener: (() => void) | undefined;
2225

2326
public constructor(options: NodeClientOptions) {
24-
const serverName = options.serverName || global.process.env.SENTRY_NAME || os.hostname();
25-
const clientOptions: ServerRuntimeClientOptions = {
26-
...options,
27-
platform: 'node',
28-
runtime: { name: 'node', version: global.process.version },
29-
serverName,
30-
};
31-
32-
if (options.openTelemetryInstrumentations) {
27+
const clientOptions = applyDefaultOptions(options);
28+
29+
if (clientOptions.openTelemetryInstrumentations) {
3330
registerInstrumentations({
3431
instrumentations: options.openTelemetryInstrumentations,
3532
});
@@ -40,19 +37,22 @@ export class NodeClient extends ServerRuntimeClient<NodeClientOptions> {
4037
logger.log(
4138
`Initializing Sentry: process: ${process.pid}, thread: ${isMainThread ? 'main' : `worker-${threadId}`}.`,
4239
);
40+
logger.log(`Running in ${isCjs() ? 'CommonJS' : 'ESM'} mode.`);
4341

4442
super(clientOptions);
4543

44+
this.startClientReportTracking();
45+
4646
if (this.getOptions()._experiments?.enableLogs) {
4747
this._logOnExitFlushListener = () => {
4848
_INTERNAL_flushLogsBuffer(this);
4949
};
5050

51-
if (serverName) {
51+
if (clientOptions.serverName) {
5252
this.on('beforeCaptureLog', log => {
5353
log.attributes = {
5454
...log.attributes,
55-
'server.address': serverName,
55+
'server.address': clientOptions.serverName,
5656
};
5757
});
5858
}
@@ -154,3 +154,55 @@ export class NodeClient extends ServerRuntimeClient<NodeClientOptions> {
154154
return getTraceContextForScope(this, scope);
155155
}
156156
}
157+
158+
function applyDefaultOptions<T extends Partial<NodeClientOptions>>(options: T): T {
159+
const release = getRelease(options.release);
160+
const spotlight =
161+
options.spotlight ?? envToBool(process.env.SENTRY_SPOTLIGHT, { strict: true }) ?? process.env.SENTRY_SPOTLIGHT;
162+
const tracesSampleRate = getTracesSampleRate(options.tracesSampleRate);
163+
const serverName = options.serverName || global.process.env.SENTRY_NAME || os.hostname();
164+
165+
return {
166+
platform: 'node',
167+
runtime: { name: 'node', version: global.process.version },
168+
serverName,
169+
...options,
170+
dsn: options.dsn ?? process.env.SENTRY_DSN,
171+
environment: options.environment ?? process.env.SENTRY_ENVIRONMENT,
172+
sendClientReports: options.sendClientReports ?? true,
173+
release,
174+
tracesSampleRate,
175+
spotlight,
176+
debug: envToBool(options.debug ?? process.env.SENTRY_DEBUG),
177+
};
178+
}
179+
180+
function getRelease(release: NodeClientOptions['release']): string | undefined {
181+
if (release !== undefined) {
182+
return release;
183+
}
184+
185+
const detectedRelease = getSentryRelease();
186+
if (detectedRelease !== undefined) {
187+
return detectedRelease;
188+
}
189+
190+
return undefined;
191+
}
192+
193+
/**
194+
* Tries to get a `tracesSampleRate`, possibly extracted from the environment variables.
195+
*/
196+
export function getTracesSampleRate(tracesSampleRate: NodeClientOptions['tracesSampleRate']): number | undefined {
197+
if (tracesSampleRate !== undefined) {
198+
return tracesSampleRate;
199+
}
200+
201+
const sampleRateFromEnv = process.env.SENTRY_TRACES_SAMPLE_RATE;
202+
if (!sampleRateFromEnv) {
203+
return undefined;
204+
}
205+
206+
const parsed = parseFloat(sampleRateFromEnv);
207+
return isFinite(parsed) ? parsed : undefined;
208+
}

‎packages/node/src/sdk/index.ts

Lines changed: 30 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { Integration, Options } from '@sentry/core';
1+
import type { Integration } from '@sentry/core';
22
import {
33
consoleIntegration,
4-
consoleSandbox,
54
functionToStringIntegration,
65
getCurrentScope,
76
getIntegrationsToSetup,
87
hasSpansEnabled,
98
inboundFiltersIntegration,
9+
initAndBind,
1010
linkedErrorsIntegration,
1111
logger,
1212
propagationContextFromHeaders,
@@ -30,14 +30,14 @@ import { nativeNodeFetchIntegration } from '../integrations/node-fetch';
3030
import { onUncaughtExceptionIntegration } from '../integrations/onuncaughtexception';
3131
import { onUnhandledRejectionIntegration } from '../integrations/onunhandledrejection';
3232
import { processSessionIntegration } from '../integrations/processSession';
33-
import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } from '../integrations/spotlight';
33+
import { spotlightIntegration } from '../integrations/spotlight';
3434
import { getAutoPerformanceIntegrations } from '../integrations/tracing';
3535
import { makeNodeTransport } from '../transports';
3636
import type { NodeClientOptions, NodeOptions } from '../types';
3737
import { isCjs } from '../utils/commonjs';
3838
import { envToBool } from '../utils/envToBool';
39-
import { defaultStackParser, getSentryRelease } from './api';
40-
import { NodeClient } from './client';
39+
import { defaultStackParser } from './api';
40+
import { getTracesSampleRate, NodeClient } from './client';
4141
import { initOpenTelemetry, maybeInitializeEsmLoader } from './initOtel';
4242

4343
function getCjsOnlyIntegrations(): Integration[] {
@@ -74,9 +74,12 @@ export function getDefaultIntegrationsWithoutPerformance(): Integration[] {
7474
}
7575

7676
/** Get the default integrations for the Node SDK. */
77-
export function getDefaultIntegrations(options: Options): Integration[] {
77+
export function getDefaultIntegrations(options: NodeOptions): Integration[] {
7878
return [
7979
...getDefaultIntegrationsWithoutPerformance(),
80+
...(options.spotlight
81+
? [spotlightIntegration({ sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined })]
82+
: []),
8083
// We only add performance integrations if tracing is enabled
8184
// Note that this means that without tracing enabled, e.g. `expressIntegration()` will not be added
8285
// This means that generally request isolation will work (because that is done by httpIntegration)
@@ -88,64 +91,37 @@ export function getDefaultIntegrations(options: Options): Integration[] {
8891
/**
8992
* Initialize Sentry for Node.
9093
*/
91-
export function init(options: NodeOptions | undefined = {}): NodeClient | undefined {
94+
export function init(options: NodeOptions = {}): NodeClient | undefined {
9295
return _init(options, getDefaultIntegrations);
9396
}
9497

9598
/**
9699
* Initialize Sentry for Node, without any integrations added by default.
97100
*/
98-
export function initWithoutDefaultIntegrations(options: NodeOptions | undefined = {}): NodeClient {
101+
export function initWithoutDefaultIntegrations(options: NodeOptions = {}): NodeClient {
99102
return _init(options, () => []);
100103
}
101104

102105
/**
103-
* Initialize Sentry for Node, without performance instrumentation.
106+
* Initialize a Node client with the provided options and default integrations getter function.
107+
* This is an internal method the SDK uses under the hood to set up things - you should not use this as a user!
108+
* Instead, use `init()` to initialize the SDK.
109+
*
110+
* @hidden
111+
* @internal
104112
*/
105113
function _init(
106-
_options: NodeOptions | undefined = {},
107-
getDefaultIntegrationsImpl: (options: Options) => Integration[],
114+
options: NodeOptions = {},
115+
getDefaultIntegrationsImpl: (options: NodeOptions) => Integration[],
108116
): NodeClient {
109-
const options = getClientOptions(_options, getDefaultIntegrationsImpl);
110-
111-
if (options.debug === true) {
112-
if (DEBUG_BUILD) {
113-
logger.enable();
114-
} else {
115-
// use `console.warn` rather than `logger.warn` since by non-debug bundles have all `logger.x` statements stripped
116-
consoleSandbox(() => {
117-
// eslint-disable-next-line no-console
118-
console.warn('[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.');
119-
});
120-
}
121-
}
122-
123117
if (!isCjs() && options.registerEsmLoaderHooks !== false) {
124118
maybeInitializeEsmLoader();
125119
}
126120

127121
setOpenTelemetryContextAsyncContextStrategy();
128122

129-
const scope = getCurrentScope();
130-
scope.update(options.initialScope);
131-
132-
if (options.spotlight && !options.integrations.some(({ name }) => name === SPOTLIGHT_INTEGRATION_NAME)) {
133-
options.integrations.push(
134-
spotlightIntegration({
135-
sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined,
136-
}),
137-
);
138-
}
139-
140-
const client = new NodeClient(options);
141-
// The client is on the current scope, from where it generally is inherited
142-
getCurrentScope().setClient(client);
143-
144-
client.init();
145-
146-
logger.log(`Running in ${isCjs() ? 'CommonJS' : 'ESM'} mode.`);
147-
148-
client.startClientReportTracking();
123+
const clientOptions = getClientOptions(options, getDefaultIntegrationsImpl);
124+
const client = initAndBind(NodeClient, clientOptions);
149125

150126
updateScopeFromEnvVariables();
151127

@@ -197,65 +173,29 @@ export function validateOpenTelemetrySetup(): void {
197173

198174
function getClientOptions(
199175
options: NodeOptions,
200-
getDefaultIntegrationsImpl: (options: Options) => Integration[],
176+
getDefaultIntegrationsImpl: (options: NodeOptions) => Integration[],
201177
): NodeClientOptions {
202-
const release = getRelease(options.release);
203-
const spotlight =
204-
options.spotlight ?? envToBool(process.env.SENTRY_SPOTLIGHT, { strict: true }) ?? process.env.SENTRY_SPOTLIGHT;
205-
const tracesSampleRate = getTracesSampleRate(options.tracesSampleRate);
206-
207-
const mergedOptions = {
178+
// We need to make sure to extract the tracesSampleRate already here, before we pass it to `getDefaultIntegrationsImpl`
179+
// As otherwise, the check for `hasSpansEnabled` may not work in all scenarios
180+
const optionsWithTracesSampleRate = {
208181
...options,
209-
dsn: options.dsn ?? process.env.SENTRY_DSN,
210-
environment: options.environment ?? process.env.SENTRY_ENVIRONMENT,
211-
sendClientReports: options.sendClientReports ?? true,
212-
transport: options.transport ?? makeNodeTransport,
213-
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
214-
release,
215-
tracesSampleRate,
216-
spotlight,
217-
debug: envToBool(options.debug ?? process.env.SENTRY_DEBUG),
182+
tracesSampleRate: getTracesSampleRate(options.tracesSampleRate),
218183
};
219184

220185
const integrations = options.integrations;
221-
const defaultIntegrations = options.defaultIntegrations ?? getDefaultIntegrationsImpl(mergedOptions);
186+
const defaultIntegrations = options.defaultIntegrations ?? getDefaultIntegrationsImpl(optionsWithTracesSampleRate);
222187

223188
return {
224-
...mergedOptions,
189+
...optionsWithTracesSampleRate,
190+
transport: options.transport ?? makeNodeTransport,
191+
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
225192
integrations: getIntegrationsToSetup({
226193
defaultIntegrations,
227194
integrations,
228195
}),
229196
};
230197
}
231198

232-
function getRelease(release: NodeOptions['release']): string | undefined {
233-
if (release !== undefined) {
234-
return release;
235-
}
236-
237-
const detectedRelease = getSentryRelease();
238-
if (detectedRelease !== undefined) {
239-
return detectedRelease;
240-
}
241-
242-
return undefined;
243-
}
244-
245-
function getTracesSampleRate(tracesSampleRate: NodeOptions['tracesSampleRate']): number | undefined {
246-
if (tracesSampleRate !== undefined) {
247-
return tracesSampleRate;
248-
}
249-
250-
const sampleRateFromEnv = process.env.SENTRY_TRACES_SAMPLE_RATE;
251-
if (!sampleRateFromEnv) {
252-
return undefined;
253-
}
254-
255-
const parsed = parseFloat(sampleRateFromEnv);
256-
return isFinite(parsed) ? parsed : undefined;
257-
}
258-
259199
/**
260200
* Update scope and propagation context based on environmental variables.
261201
*

‎packages/node/src/sdk/initOtel.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ATTR_SERVICE_VERSION,
88
SEMRESATTRS_SERVICE_NAMESPACE,
99
} from '@opentelemetry/semantic-conventions';
10-
import { consoleSandbox, GLOBAL_OBJ, logger, SDK_VERSION } from '@sentry/core';
10+
import { consoleSandbox, enableLogger, GLOBAL_OBJ, logger, SDK_VERSION } from '@sentry/core';
1111
import { SentryPropagator, SentrySampler, SentrySpanProcessor } from '@sentry/opentelemetry';
1212
import { createAddHookMessageChannel } from 'import-in-the-middle';
1313
import moduleModule from 'module';
@@ -52,7 +52,10 @@ export function maybeInitializeEsmLoader(): void {
5252
transferList: [addHookMessagePort],
5353
});
5454
} catch (error) {
55-
logger.warn('Failed to register ESM hook', error);
55+
consoleSandbox(() => {
56+
// eslint-disable-next-line no-console
57+
console.warn('Failed to register ESM hook', error);
58+
});
5659
}
5760
}
5861
} else {
@@ -79,7 +82,7 @@ export function preloadOpenTelemetry(options: NodePreloadOptions = {}): void {
7982
const { debug } = options;
8083

8184
if (debug) {
82-
logger.enable();
85+
enableLogger();
8386
setupOpenTelemetryLogger();
8487
}
8588

‎packages/node/test/sdk/client.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('NodeClient', () => {
2323
cleanupOtel();
2424
});
2525

26-
it('sets correct metadata', () => {
26+
it('sets correct metadata & default options', () => {
2727
const options = getDefaultNodeClientOptions();
2828
const client = new NodeClient(options);
2929

@@ -48,6 +48,8 @@ describe('NodeClient', () => {
4848
runtime: { name: 'node', version: expect.any(String) },
4949
serverName: expect.any(String),
5050
tracesSampleRate: 1,
51+
debug: false,
52+
sendClientReports: true,
5153
});
5254
});
5355

‎packages/node/test/sdk/init.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,23 @@ describe('init()', () => {
123123
}),
124124
);
125125
});
126+
127+
it('does not install spotlight integration by default', () => {
128+
const client = init({
129+
dsn: PUBLIC_DSN,
130+
});
131+
132+
expect(client!.getIntegrationByName('Spotlight')).toBeUndefined();
133+
});
134+
135+
it('installs spotlight integration if spotlight=true is configured', () => {
136+
const client = init({
137+
dsn: PUBLIC_DSN,
138+
spotlight: true,
139+
});
140+
141+
expect(client!.getIntegrationByName('Spotlight')).toBeDefined();
142+
});
126143
});
127144

128145
describe('OpenTelemetry', () => {

‎packages/profiling-node/test/integration.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function makeLegacyContinuousProfilingClient(): [Sentry.NodeClient, Transport] {
3434
stackParser: Sentry.defaultStackParser,
3535
tracesSampleRate: 1,
3636
debug: true,
37+
sendClientReports: false,
3738
environment: 'test-environment',
3839
dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302',
3940
integrations: [integration],
@@ -55,6 +56,7 @@ function makeCurrentSpanProfilingClient(options: Partial<NodeClientOptions> = {}
5556
stackParser: Sentry.defaultStackParser,
5657
tracesSampleRate: 1,
5758
debug: true,
59+
sendClientReports: false,
5860
environment: 'test-environment',
5961
dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302',
6062
integrations: [integration],
@@ -84,6 +86,7 @@ function makeClientOptions(
8486
stackParser: Sentry.defaultStackParser,
8587
integrations: [_nodeProfilingIntegration()],
8688
debug: true,
89+
sendClientReports: false,
8790
transport: _opts =>
8891
Sentry.makeNodeTransport({
8992
url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302',

0 commit comments

Comments
 (0)
Please sign in to comment.