diff --git a/packages/deno/src/client.ts b/packages/deno/src/client.ts index 160396df57af..295b7757b9fd 100644 --- a/packages/deno/src/client.ts +++ b/packages/deno/src/client.ts @@ -1,7 +1,8 @@ import type { ServerRuntimeClientOptions } from '@sentry/core/server'; -import { _INTERNAL_flushLogsBuffer, SDK_VERSION } from '@sentry/core'; +import { _INTERNAL_flushLogsBuffer, _INTERNAL_flushMetricsBuffer, SDK_VERSION } from '@sentry/core'; import { ServerRuntimeClient } from '@sentry/core/server'; import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils'; +import process from 'node:process'; import type { DenoClientOptions } from './types'; function getHostName(): string | undefined { @@ -22,6 +23,7 @@ function getHostName(): string | undefined { */ export class DenoClient extends ServerRuntimeClient { private _logOnExitFlushListener: (() => void) | undefined; + private _metricsOnExitFlushListener: (() => void) | undefined; /** * Creates a new Deno SDK instance. @@ -55,6 +57,10 @@ export class DenoClient extends ServerRuntimeClient { _INTERNAL_flushLogsBuffer(this); }; + this._metricsOnExitFlushListener = () => { + _INTERNAL_flushMetricsBuffer(this); + }; + if (serverName) { this.on('beforeCaptureLog', log => { log.attributes = { @@ -64,7 +70,11 @@ export class DenoClient extends ServerRuntimeClient { }); } + // Unlike unload, beforeExit lets the transport finish asynchronous sends. + process.on('beforeExit', this._logOnExitFlushListener); + process.on('beforeExit', this._metricsOnExitFlushListener); globalThis.addEventListener('unload', this._logOnExitFlushListener); + globalThis.addEventListener('unload', this._metricsOnExitFlushListener); } /** @inheritDoc */ @@ -81,9 +91,15 @@ export class DenoClient extends ServerRuntimeClient { // @ts-expect-error - PromiseLike is a subset of Promise public async close(timeout?: number | undefined): PromiseLike { if (this._logOnExitFlushListener) { + process.off('beforeExit', this._logOnExitFlushListener); globalThis.removeEventListener('unload', this._logOnExitFlushListener); } + if (this._metricsOnExitFlushListener) { + process.off('beforeExit', this._metricsOnExitFlushListener); + globalThis.removeEventListener('unload', this._metricsOnExitFlushListener); + } + return super.close(timeout); } } diff --git a/packages/deno/test/mod.test.ts b/packages/deno/test/mod.test.ts index f769ad0ff2d2..ad5e226b9661 100644 --- a/packages/deno/test/mod.test.ts +++ b/packages/deno/test/mod.test.ts @@ -1,3 +1,4 @@ +import process from 'node:process'; import type { Envelope, Event, Log } from '@sentry/core'; import { createStackParser, forEachEnvelopeItem } from '@sentry/core'; import { nodeStackLineParser } from '@sentry/core/server'; @@ -69,7 +70,7 @@ function expectCaptureExceptionEvent(event: Event | undefined): void { filename: 'app:///test/mod.test.ts', function: '?', in_app: true, - lineno: 45, + lineno: 46, }, { colno: 12, @@ -77,7 +78,7 @@ function expectCaptureExceptionEvent(event: Event | undefined): void { filename: 'app:///test/mod.test.ts', function: 'something', in_app: true, - lineno: 42, + lineno: 43, }, ], ); @@ -231,7 +232,8 @@ Deno.test('preserves existing log attributes when adding server.address', () => assertEquals(log.attributes?.['server.address'], 'test-server'); }); -Deno.test('close() removes unload listener', async () => { +Deno.test('close() removes log and metric exit listeners', async () => { + const originalListeners = process.listeners('beforeExit'); const removeEventListenerCalls: Array = []; const originalRemoveEventListener = globalThis.removeEventListener; globalThis.removeEventListener = ((event: string, ...args: unknown[]) => { @@ -248,9 +250,16 @@ Deno.test('close() removes unload listener', async () => { transport: makeTestTransport(() => {}), }); + const addedListeners = process.listeners('beforeExit').filter(listener => !originalListeners.includes(listener)); + await client.close(); - assertEquals(removeEventListenerCalls.includes('unload'), true); + assertEquals(addedListeners.length, 2); + assertEquals(process.listeners('beforeExit'), originalListeners); + assertEquals( + removeEventListenerCalls.filter(event => event === 'unload'), + ['unload', 'unload'], + ); } finally { globalThis.removeEventListener = originalRemoveEventListener; }