From af0da37f5d01406cbb295a7a973b7259e513b0b8 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 1 Sep 2025 11:12:40 +0200 Subject: [PATCH 1/3] fix(node-core): Shut down OTel Traceprovider when calling `Sentry.close()` --- packages/node-core/src/sdk/client.ts | 10 ++-- packages/node-core/test/sdk/client.test.ts | 64 ++++++++++++++++++++++ packages/sveltekit/src/server/sdk.ts | 12 +++- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/packages/node-core/src/sdk/client.ts b/packages/node-core/src/sdk/client.ts index c17ee22d71b4..0d7bea423ace 100644 --- a/packages/node-core/src/sdk/client.ts +++ b/packages/node-core/src/sdk/client.ts @@ -80,9 +80,7 @@ export class NodeClient extends ServerRuntimeClient { // Eslint ignore explanation: This is already documented in super. // eslint-disable-next-line jsdoc/require-jsdoc public async flush(timeout?: number): Promise { - const provider = this.traceProvider; - - await provider?.forceFlush(); + await this.traceProvider?.forceFlush(); if (this.getOptions().sendClientReports) { this._flushOutcomes(); @@ -106,7 +104,11 @@ export class NodeClient extends ServerRuntimeClient { process.off('beforeExit', this._logOnExitFlushListener); } - return super.close(timeout); + return super + .close(timeout) + .then(allEventsSent => + this.traceProvider ? this.traceProvider.shutdown().then(() => allEventsSent) : allEventsSent, + ); } /** diff --git a/packages/node-core/test/sdk/client.test.ts b/packages/node-core/test/sdk/client.test.ts index 7f57d4772212..2084b1ff2aff 100644 --- a/packages/node-core/test/sdk/client.test.ts +++ b/packages/node-core/test/sdk/client.test.ts @@ -1,5 +1,6 @@ import { ProxyTracer } from '@opentelemetry/api'; import * as opentelemetryInstrumentationPackage from '@opentelemetry/instrumentation'; +import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { Event, EventHint, Log } from '@sentry/core'; import { getCurrentScope, getGlobalScope, getIsolationScope, Scope, SDK_VERSION } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; @@ -321,4 +322,67 @@ describe('NodeClient', () => { }); }); }); + + describe('close', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('shuts down the OTel trace provider', async () => { + const shutdownSpy = vi.fn().mockResolvedValue(true); + const forceFlushSpy = vi.fn().mockResolvedValue(undefined); + + const client = new NodeClient(getDefaultNodeClientOptions()); + + client.traceProvider = { + shutdown: shutdownSpy, + forceFlush: forceFlushSpy, + } as unknown as BasicTracerProvider; + + const result = await client.close(); + + // ensure we return the flush result rather than void from the traceProvider shutdown + expect(result).toBe(true); + + expect(shutdownSpy).toHaveBeenCalledTimes(1); + + // close calls flush and flush force-flushes the traceProvider + expect(forceFlushSpy).toHaveBeenCalledTimes(1); + }); + + it('stops client report tracking if it was started', async () => { + const processOffSpy = vi.spyOn(process, 'off'); + const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval'); + + const client = new NodeClient(getDefaultNodeClientOptions({ sendClientReports: true })); + + client.startClientReportTracking(); + + const result = await client.close(); + + expect(result).toBe(true); + + // once call directly in close to stop client reports, + // the other in core client `_isClientDoneProcessing` + expect(clearIntervalSpy).toHaveBeenCalledTimes(2); + + // removes `_clientReportOnExitFlushListener` + expect(processOffSpy).toHaveBeenNthCalledWith(1, 'beforeExit', expect.any(Function)); + }); + + it('stops log capture if it was started', async () => { + const processOffSpy = vi.spyOn(process, 'off'); + + const client = new NodeClient(getDefaultNodeClientOptions({ enableLogs: true })); + + client.startClientReportTracking(); + + const result = await client.close(); + + expect(result).toBe(true); + + // removes `_logOnExitFlushListener` + expect(processOffSpy).toHaveBeenNthCalledWith(1, 'beforeExit', expect.any(Function)); + }); + }); }); diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts index fb7a5dbbb471..0b21acf97156 100644 --- a/packages/sveltekit/src/server/sdk.ts +++ b/packages/sveltekit/src/server/sdk.ts @@ -24,5 +24,15 @@ export function init(options: NodeOptions): NodeClient | undefined { applySdkMetadata(opts, 'sveltekit', ['sveltekit', 'node']); - return initNodeSdk(opts); + const client = initNodeSdk(opts); + + if (typeof process !== 'undefined') { + process.on('sveltekit:shutdown', async () => { + if (client) { + await client.close(2000); + } + }); + } + + return client; } From 13a94f194c29dea84c85ee72939ac41c1fbe1915 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 1 Sep 2025 11:16:03 +0200 Subject: [PATCH 2/3] remove unnecessary change --- packages/sveltekit/src/server/sdk.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts index 0b21acf97156..fb7a5dbbb471 100644 --- a/packages/sveltekit/src/server/sdk.ts +++ b/packages/sveltekit/src/server/sdk.ts @@ -24,15 +24,5 @@ export function init(options: NodeOptions): NodeClient | undefined { applySdkMetadata(opts, 'sveltekit', ['sveltekit', 'node']); - const client = initNodeSdk(opts); - - if (typeof process !== 'undefined') { - process.on('sveltekit:shutdown', async () => { - if (client) { - await client.close(2000); - } - }); - } - - return client; + return initNodeSdk(opts); } From 8a05bd17244b861f69095b2252e50a173eff4b1f Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 1 Sep 2025 11:20:34 +0200 Subject: [PATCH 3/3] fix test --- packages/node-core/test/sdk/client.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/node-core/test/sdk/client.test.ts b/packages/node-core/test/sdk/client.test.ts index 2084b1ff2aff..33548d621c13 100644 --- a/packages/node-core/test/sdk/client.test.ts +++ b/packages/node-core/test/sdk/client.test.ts @@ -375,8 +375,6 @@ describe('NodeClient', () => { const client = new NodeClient(getDefaultNodeClientOptions({ enableLogs: true })); - client.startClientReportTracking(); - const result = await client.close(); expect(result).toBe(true);