Skip to content

Commit 90a6a25

Browse files
authored
fix(core): Explicitly flush log buffer in client.close() (#19371)
Adds an explicit `_INTERNAL_flushLogsBuffer(this)` call at the start of the base Client.close() method, before flush(timeout) is called. Previously, the log buffer flush during `close()` relied on an indirect event chain: `close() → flush() → emit('flush') → setupWeightBasedFlushing handler → _INTERNAL_flushLogsBuffer`. While this works, it's fragile and could break if someone refactors the event mechanism. The explicit call ensures logs are always flushed on close regardless of the event wiring. The call is placed before flush() so the log envelope is queued to the transport before transport.flush(timeout) drains all pending sends. _INTERNAL_flushLogsBuffer safely handles empty buffers (returns early), so there's no overhead when logs are disabled or the buffer is empty. All platform SDKs (NodeClient, LightNodeClient, DenoClient, BrowserClient, CloudflareClient) ultimately call super.close() which reaches this base implementation, so all runtimes benefit automatically. closes #19347
1 parent afba737 commit 90a6a25

File tree

2 files changed

+20
-0
lines changed

2 files changed

+20
-0
lines changed

packages/core/src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
445445
*/
446446
// @ts-expect-error - PromiseLike is a subset of Promise
447447
public async close(timeout?: number): PromiseLike<boolean> {
448+
_INTERNAL_flushLogsBuffer(this);
448449
const result = await this.flush(timeout);
449450
this.getOptions().enabled = false;
450451
this.emit('close');

packages/core/test/lib/client.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
withMonitor,
1414
} from '../../src';
1515
import * as integrationModule from '../../src/integration';
16+
import * as logsInternalModule from '../../src/logs/internal';
1617
import { _INTERNAL_captureLog } from '../../src/logs/internal';
1718
import { _INTERNAL_captureMetric } from '../../src/metrics/internal';
1819
import * as traceModule from '../../src/tracing/trace';
@@ -2212,6 +2213,24 @@ describe('Client', () => {
22122213
expect(getSentCount()).toBe(1);
22132214
});
22142215

2216+
test('close flushes the logs buffer', async () => {
2217+
vi.useRealTimers();
2218+
2219+
const flushLogsSpy = vi
2220+
.spyOn(logsInternalModule, '_INTERNAL_flushLogsBuffer')
2221+
.mockImplementation(() => undefined);
2222+
2223+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
2224+
const client = new TestClient(options);
2225+
2226+
await client.close();
2227+
2228+
expect(flushLogsSpy).toHaveBeenCalledTimes(1);
2229+
expect(flushLogsSpy).toHaveBeenCalledWith(client);
2230+
2231+
flushLogsSpy.mockRestore();
2232+
});
2233+
22152234
test('multiple concurrent flush calls should just work', async () => {
22162235
vi.useRealTimers();
22172236
expect.assertions(3);

0 commit comments

Comments
 (0)