Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions packages/node-core/src/light/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { defaultStackParser, getSentryRelease } from '../sdk/api';
import { makeNodeTransport } from '../transports';
import type { NodeClientOptions, NodeOptions } from '../types';
import { getSpotlightConfig } from '../utils/spotlight';
import { setupVercelSigtermFlush } from '../utils/vercel';
import { setAsyncLocalStorageAsyncContextStrategy } from './asyncLocalStorageStrategy';
import { LightNodeClient } from './client';
import { httpIntegration } from './integrations/httpIntegration';
Expand Down Expand Up @@ -130,13 +131,8 @@ function _init(

updateScopeFromEnvVariables();

// Ensure we flush events when vercel functions are ended
// See: https://vercel.com/docs/functions/functions-api-reference#sigterm-signal
if (process.env.VERCEL) {
process.on('SIGTERM', async () => {
// We have 500ms for processing here, so we try to make sure to have enough time to send the events
await client.flush(200);
});
setupVercelSigtermFlush(client);
}

return client;
Expand Down
8 changes: 2 additions & 6 deletions packages/node-core/src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { systemErrorIntegration } from '../integrations/systemError';
import { makeNodeTransport } from '../transports';
import type { NodeClientOptions, NodeOptions } from '../types';
import { getSpotlightConfig } from '../utils/spotlight';
import { setupVercelSigtermFlush } from '../utils/vercel';
import { defaultStackParser, getSentryRelease } from './api';
import { NodeClient } from './client';
import { initializeEsmLoader } from './esmLoader';
Expand Down Expand Up @@ -147,13 +148,8 @@ function _init(
enhanceDscWithOpenTelemetryRootSpanName(client);
setupEventContextTrace(client);

// Ensure we flush events when vercel functions are ended
// See: https://vercel.com/docs/functions/functions-api-reference#sigterm-signal
if (process.env.VERCEL) {
process.on('SIGTERM', async () => {
// We have 500ms for processing here, so we try to make sure to have enough time to send the events
await client.flush(200);
});
setupVercelSigtermFlush(client);
}

return client;
Expand Down
20 changes: 20 additions & 0 deletions packages/node-core/src/utils/vercel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Client } from '@sentry/core';

// A repeated `init()` replaces the client, so a single listener flushes the latest one instead of adding one per init.
let sigtermClient: Client | undefined;

async function flushOnSigterm(): Promise<void> {
// We have 500ms for processing here, so we try to make sure to have enough time to send the events
await sigtermClient?.flush(200);
}

/**
* Ensure we flush events when vercel functions are ended.
* See: https://vercel.com/docs/functions/functions-api-reference#sigterm-signal
*/
export function setupVercelSigtermFlush(client: Client): void {
sigtermClient = client;
if (!process.listeners('SIGTERM').includes(flushOnSigterm)) {
process.on('SIGTERM', flushOnSigterm);
}
}
24 changes: 24 additions & 0 deletions packages/node-core/test/light/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,30 @@ describe('Light Mode | SDK', () => {

expect(client?.getOptions().release).toBe('test-release@1.0.0');
});

it('registers one SIGTERM handler on Vercel that flushes the latest client across repeated inits', () => {
const originalVercelEnv = process.env.VERCEL;
process.env.VERCEL = '1';

const baselineListeners = process.listeners('SIGTERM');

const firstClient = mockLightSdkInit();
const latestClient = mockLightSdkInit();
const firstFlushSpy = vi.spyOn(firstClient!, 'flush').mockResolvedValue(true);
const latestFlushSpy = vi.spyOn(latestClient!, 'flush').mockResolvedValue(true);

const postInitListeners = process.listeners('SIGTERM');
const addedListeners = postInitListeners.filter(l => !baselineListeners.includes(l));
expect(addedListeners).toHaveLength(1);

process.emit('SIGTERM');

expect(firstFlushSpy).not.toHaveBeenCalled();
expect(latestFlushSpy).toHaveBeenCalledWith(200);

process.off('SIGTERM', addedListeners[0] as any);
process.env.VERCEL = originalVercelEnv;
});
});

describe('initWithoutDefaultIntegrations', () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/node-core/test/sdk/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,30 @@ describe('init()', () => {
process.env.VERCEL = originalVercelEnv;
});

it('registers one SIGTERM handler on Vercel that flushes the latest client across repeated inits', () => {
const originalVercelEnv = process.env.VERCEL;
process.env.VERCEL = '1';

const baselineListeners = process.listeners('SIGTERM');

const firstClient = init({ dsn: PUBLIC_DSN, skipOpenTelemetrySetup: true });
const latestClient = init({ dsn: PUBLIC_DSN, skipOpenTelemetrySetup: true });
const firstFlushSpy = vi.spyOn(firstClient as NodeClient, 'flush').mockResolvedValue(true);
const latestFlushSpy = vi.spyOn(latestClient as NodeClient, 'flush').mockResolvedValue(true);

const postInitListeners = process.listeners('SIGTERM');
const addedListeners = postInitListeners.filter(l => !baselineListeners.includes(l));
expect(addedListeners).toHaveLength(1);

process.emit('SIGTERM');

expect(firstFlushSpy).not.toHaveBeenCalled();
expect(latestFlushSpy).toHaveBeenCalledWith(200);

process.off('SIGTERM', addedListeners[0] as any);
process.env.VERCEL = originalVercelEnv;
});

it('does not register a SIGTERM handler when not running on Vercel', () => {
const originalVercelEnv = process.env.VERCEL;
delete process.env.VERCEL;
Expand Down
Loading