From 7e243eddbeb998f5665969ea2f0badd01772565f Mon Sep 17 00:00:00 2001 From: sbs44 <83440025+sbs44@users.noreply.github.com> Date: Fri, 10 Apr 2026 08:39:44 -0400 Subject: [PATCH] fix(core): Emit `flush` event in `Client.flush()` when client has no transport `Client.flush()` short-circuited before emitting the `flush` event when the client had no transport (e.g. no DSN was configured), leaving the weight-based flushers for logs and metrics with idle `setTimeout` handles that never resolved. Emit `flush` unconditionally so the weight-based flusher listeners drain their buffers and clear the timers on every `flush()` call. --- CHANGELOG.md | 4 +++ packages/core/src/client.ts | 8 +++-- packages/core/test/lib/client.test.ts | 44 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8141da2e6276..004ff1bc1134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Other Changes + +- fix(core): Emit `flush` event in `Client.flush()` when client has no transport + ## 10.48.0 ### Important Changes diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 6c3ca949f38e..9d7091794826 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -424,12 +424,16 @@ export abstract class Client { // @ts-expect-error - PromiseLike is a subset of Promise public async flush(timeout?: number): PromiseLike { const transport = this._transport; + + // Emit `flush` unconditionally so weight-based log/metric flushers drain + // their buffers and clear their idle timers, even when no transport is + // configured (e.g. no DSN). + this.emit('flush'); + if (!transport) { return true; } - this.emit('flush'); - const clientFinished = await this._isClientDoneProcessing(timeout); const transportFlushed = await transport.flush(timeout); diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index 1548a4aecce4..a8971498cef8 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -3115,6 +3115,29 @@ describe('Client', () => { safeUnrefSpy.mockRestore(); }); + + it('flush() drains the log buffer when client has no transport', async () => { + // Client without DSN — _transport is undefined + const options = getDefaultTestClientOptions({ + enableLogs: true, + }); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + const flushLogsHandler = vi.fn(); + client.on('flushLogs', flushLogsHandler); + + // Capture a log which starts the weight-based flush timer + _INTERNAL_captureLog({ message: 'test log', level: 'info' }, scope); + + expect(flushLogsHandler).not.toHaveBeenCalled(); + + // flush() should drain the buffer (and clear the timer) even without a transport + await client.flush(); + + expect(flushLogsHandler).toHaveBeenCalledTimes(1); + }); }); describe('metric weight-based flushing', () => { @@ -3201,6 +3224,27 @@ describe('Client', () => { safeUnrefSpy.mockRestore(); }); + + it('flush() drains the metric buffer when client has no transport', async () => { + // Client without DSN — _transport is undefined + const options = getDefaultTestClientOptions({}); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + const flushMetricsHandler = vi.fn(); + client.on('flushMetrics', flushMetricsHandler); + + // Capture a metric which starts the weight-based flush timer + _INTERNAL_captureMetric({ name: 'test_metric', value: 42, type: 'counter', attributes: {} }, { scope }); + + expect(flushMetricsHandler).not.toHaveBeenCalled(); + + // flush() should drain the buffer (and clear the timer) even without a transport + await client.flush(); + + expect(flushMetricsHandler).toHaveBeenCalledTimes(1); + }); }); describe('promise buffer usage', () => {