Skip to content

Commit c9cdd5b

Browse files
committed
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 c9cdd5b

File tree

8 files changed

+146
-125
lines changed

8 files changed

+146
-125
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: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@ 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';
7-
import { _INTERNAL_flushLogsBuffer, applySdkMetadata, logger, SDK_VERSION, ServerRuntimeClient } from '@sentry/core';
6+
import type { DynamicSamplingContext, Scope, TraceContext } from '@sentry/core';
7+
import {
8+
_INTERNAL_flushLogsBuffer,
9+
applySdkMetadata,
10+
logger,
11+
SDK_VERSION,
12+
ServerRuntimeClient,
13+
} from '@sentry/core';
814
import { getTraceContextForScope } from '@sentry/opentelemetry';
915
import { isMainThread, threadId } from 'worker_threads';
1016
import { DEBUG_BUILD } from '../debug-build';
1117
import type { NodeClientOptions } from '../types';
18+
import { isCjs } from '../utils/commonjs';
19+
import { envToBool } from '../utils/envToBool';
20+
import { getSentryRelease } from './api';
1221

1322
const DEFAULT_CLIENT_REPORT_FLUSH_INTERVAL_MS = 60_000; // 60s was chosen arbitrarily
1423

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

2332
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) {
33+
const clientOptions = applyDefaultOptions(options);
34+
35+
if (clientOptions.openTelemetryInstrumentations) {
3336
registerInstrumentations({
3437
instrumentations: options.openTelemetryInstrumentations,
3538
});
@@ -40,19 +43,22 @@ export class NodeClient extends ServerRuntimeClient<NodeClientOptions> {
4043
logger.log(
4144
`Initializing Sentry: process: ${process.pid}, thread: ${isMainThread ? 'main' : `worker-${threadId}`}.`,
4245
);
46+
logger.log(`Running in ${isCjs() ? 'CommonJS' : 'ESM'} mode.`);
4347

4448
super(clientOptions);
4549

50+
this.startClientReportTracking();
51+
4652
if (this.getOptions()._experiments?.enableLogs) {
4753
this._logOnExitFlushListener = () => {
4854
_INTERNAL_flushLogsBuffer(this);
4955
};
5056

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

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
*

0 commit comments

Comments
 (0)