diff --git a/MIGRATION.md b/MIGRATION.md index 5c3984abae46..f6d38600849c 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1046,6 +1046,34 @@ import { instrumentLangGraph } from '@sentry/node'; import { instrumentStateGraph } from '@sentry/node'; ``` +### `childProcess` integration split into `childProcess` and `workerThreads` + +Affected SDKs: `@sentry/node` and dependents. + +The `childProcessIntegration` was split into a `childProcessIntegration` (for `child_process`) and a separate `workerThreadsIntegration` (for `worker_threads`). + +Both integrations are enabled by default, so no change is needed to keep the previous behavior. + +The deprecated `captureWorkerErrors` option was removed. Worker thread errors are always captured as events now. To opt out, remove `workerThreadsIntegration` instead: + +```js +// before +Sentry.init({ + integrations: [Sentry.childProcessIntegration({ captureWorkerErrors: false })], +}); + +// after +Sentry.init({ + integrations: integrations => integrations.filter(integration => integration.name !== 'WorkerThreads'), +}); +``` + +Note that `captureWorkerErrors: false` used to downgrade worker thread errors to a `worker_thread` breadcrumb. That breadcrumb is gone, so removing the integration drops worker thread errors entirely. + +The `includeChildProcessArgs` option stays on `childProcessIntegration`. Disabling `childProcessIntegration` no longer disables worker thread error capture, since that now lives in `workerThreadsIntegration`. + +The mechanism type of worker thread errors changed from `auto.child_process.worker_thread` to `auto.node.worker_threads`. Adjust any alerts or filters that match on it. + ### Deno default integrations renamed to match the other SDKs Affected SDKs: `@sentry/deno`. diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index fd7b1adfe01a..2821cf41f0a6 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -54,6 +54,7 @@ const DEPENDENTS: Dependent[] = [ 'NodeClient', 'NODE_VERSION', 'childProcessIntegration', + 'workerThreadsIntegration', 'systemErrorIntegration', 'pinoIntegration', // Bun will get its own runtime metrics integration diff --git a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs index b832bae748d5..31f242feae18 100644 --- a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs +++ b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs @@ -10,7 +10,7 @@ Sentry.init({ traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - integrations: [Sentry.childProcessIntegration({ captureWorkerErrors: false })], + integrations: [Sentry.childProcessIntegration(), Sentry.workerThreadsIntegration()], transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts index ce263fe21b59..6ade82637242 100644 --- a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts +++ b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts @@ -2,8 +2,22 @@ import type { Event } from '@sentry/core'; import { afterAll, describe, expect, test } from 'vitest'; import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; -const EVENT = { - // and an exception that is our ANR +const WORKER_ERROR_EVENT = { + exception: { + values: [ + { + type: 'Error', + value: 'Worker error', + mechanism: { + type: 'auto.node.worker_threads', + handled: false, + }, + }, + ], + }, +}; + +const TEST_ERROR_EVENT = { exception: { values: [ { @@ -22,19 +36,10 @@ const EVENT = { spawnfile: 'sleep', }, }, - { - timestamp: expect.any(Number), - category: 'worker_thread', - message: "Worker thread errored with 'Worker error'", - level: 'error', - data: { - threadId: expect.any(Number), - }, - }, ], }; -describe('should capture process and thread breadcrumbs', () => { +describe('should capture child process breadcrumbs and worker thread errors', () => { afterAll(() => { cleanupChildProcesses(); }); @@ -42,7 +47,9 @@ describe('should capture process and thread breadcrumbs', () => { test('ESM', async () => { await createRunner(__dirname, 'app.mjs') .withMockSentryServer() - .expect({ event: EVENT as Event }) + .expect({ event: WORKER_ERROR_EVENT as Event }) + .expect({ event: TEST_ERROR_EVENT as Event }) + .unordered() .start() .completed(); }); diff --git a/dev-packages/node-integration-tests/suites/child-process/test.ts b/dev-packages/node-integration-tests/suites/child-process/test.ts index ce78388c625d..a6712fcf04f1 100644 --- a/dev-packages/node-integration-tests/suites/child-process/test.ts +++ b/dev-packages/node-integration-tests/suites/child-process/test.ts @@ -9,7 +9,7 @@ const WORKER_EVENT: Event = { type: 'Error', value: 'Test error', mechanism: { - type: 'auto.child_process.worker_thread', + type: 'auto.node.worker_threads', handled: false, data: { threadId: expect.any(String), diff --git a/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/test.ts b/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/test.ts index 11cac0dbb128..c2f8aba9f225 100644 --- a/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/test.ts @@ -115,7 +115,7 @@ describe('OnUncaughtException integration', () => { type: 'Error', value: 'job failed', mechanism: { - type: 'auto.child_process.worker_thread', + type: 'auto.node.worker_threads', handled: false, }, stacktrace: { @@ -146,7 +146,7 @@ describe('OnUncaughtException integration', () => { type: 'Error', value: 'job failed', mechanism: { - type: 'auto.child_process.worker_thread', + type: 'auto.node.worker_threads', handled: false, }, stacktrace: { @@ -178,7 +178,7 @@ describe('OnUncaughtException integration', () => { type: 'Error', value: 'job failed', mechanism: { - type: 'auto.child_process.worker_thread', + type: 'auto.node.worker_threads', handled: false, }, stacktrace: { diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index e5d78a233dec..0e7571cc4a6e 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -102,6 +102,7 @@ export { getOtlpTracesEndpoint, processSessionIntegration, childProcessIntegration, + workerThreadsIntegration, createSentryWinstonTransport, redisIntegration, requestDataIntegration, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index aa392eb3ec4d..ce67ecefae08 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -119,6 +119,7 @@ export { otlpIntegration, getOtlpTracesEndpoint, childProcessIntegration, + workerThreadsIntegration, createSentryWinstonTransport, hapiIntegration, setupHapiErrorHandler, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 17fcf89c8f16..7332aed65692 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -142,6 +142,7 @@ export { anthropicAIIntegration, googleGenAIIntegration, childProcessIntegration, + workerThreadsIntegration, createSentryWinstonTransport, vercelAIIntegration, logger, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 3483dbff8163..b7ffef9ecf09 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -202,6 +202,7 @@ export { pinoIntegration } from './integrations/pino'; export { spotlightIntegration } from './integrations/spotlight'; export { systemErrorIntegration } from './integrations/systemError'; export { createSentryWinstonTransport } from './integrations/winston'; +export { workerThreadsIntegration } from './integrations/workerThreads'; export { cron } from './cron'; export { NODE_VERSION } from './nodeVersion'; export { defaultStackParser, getSentryRelease } from './sdk/api'; diff --git a/packages/node/src/integrations/childProcess.ts b/packages/node/src/integrations/childProcess.ts index 1c4cf4a38966..236184c89a81 100644 --- a/packages/node/src/integrations/childProcess.ts +++ b/packages/node/src/integrations/childProcess.ts @@ -1,7 +1,6 @@ import type { ChildProcess } from 'node:child_process'; import * as diagnosticsChannel from 'node:diagnostics_channel'; -import type { Worker } from 'node:worker_threads'; -import { addBreadcrumb, captureException, defineIntegration, isObjectLike } from '@sentry/core'; +import { addBreadcrumb, defineIntegration, isObjectLike } from '@sentry/core'; interface Options { /** @@ -10,19 +9,13 @@ interface Options { * @default false */ includeChildProcessArgs?: boolean; - - /** - * Whether to capture errors from worker threads. - * - * @default true - */ - captureWorkerErrors?: boolean; } const INTEGRATION_NAME = 'ChildProcess' as const; /** - * Capture breadcrumbs and events for child processes and worker threads. + * Capture breadcrumbs and events for child processes. + * For worker thread events, use `workerThreadsIntegration()` instead. */ export const childProcessIntegration = defineIntegration((options: Options = {}) => { return { @@ -33,12 +26,6 @@ export const childProcessIntegration = defineIntegration((options: Options = {}) captureChildProcessEvents(event.process as ChildProcess, options); } }); - - diagnosticsChannel.channel('worker_threads').subscribe((event: unknown) => { - if (isObjectLike(event) && 'worker' in event) { - captureWorkerThreadEvents(event.worker as Worker, options); - } - }); }, }; }); @@ -88,26 +75,3 @@ function captureChildProcessEvents(child: ChildProcess, options: Options): void } }); } - -function captureWorkerThreadEvents(worker: Worker, options: Options): void { - let threadId: number | undefined; - - worker - .on('online', () => { - threadId = worker.threadId; - }) - .on('error', error => { - if (options.captureWorkerErrors !== false) { - captureException(error, { - mechanism: { type: 'auto.child_process.worker_thread', handled: false, data: { threadId: String(threadId) } }, - }); - } else { - addBreadcrumb({ - category: 'worker_thread', - message: `Worker thread errored with '${error.message}'`, - level: 'error', - data: { threadId }, - }); - } - }); -} diff --git a/packages/node/src/integrations/workerThreads.ts b/packages/node/src/integrations/workerThreads.ts new file mode 100644 index 000000000000..27010849e0f0 --- /dev/null +++ b/packages/node/src/integrations/workerThreads.ts @@ -0,0 +1,40 @@ +import type { Worker } from 'node:worker_threads'; +import * as diagnosticsChannel from 'node:diagnostics_channel'; +import { captureException, defineIntegration, isObjectLike } from '@sentry/core'; + +const INTEGRATION_NAME = 'WorkerThreads' as const; + +/** + * Capture events and errors of worker threads. + * For child process events, use `childProcessIntegration()` instead. + */ +export const workerThreadsIntegration = defineIntegration(() => { + return { + name: INTEGRATION_NAME, + setup() { + diagnosticsChannel.channel('worker_threads').subscribe((event: unknown) => { + if (isObjectLike(event) && 'worker' in event) { + captureWorkerThreadEvents(event.worker as Worker); + } + }); + }, + }; +}); + +function captureWorkerThreadEvents(worker: Worker): void { + let threadId: number | undefined; + + worker + .on('online', () => { + threadId = worker.threadId; + }) + .on('error', error => { + captureException(error, { + mechanism: { + type: 'auto.node.worker_threads', + handled: false, + data: threadId !== undefined ? { threadId: String(threadId) } : undefined, + }, + }); + }); +} diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 2bb381588581..73014fdf690e 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -33,6 +33,7 @@ import { processSessionIntegration } from '../integrations/processSession'; import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } from '../integrations/spotlight'; import { systemErrorIntegration } from '../integrations/systemError'; import { getAutoPerformanceIntegrations } from '../integrations/tracing'; +import { workerThreadsIntegration } from '../integrations/workerThreads'; import { makeNodeTransport } from '../transports'; import type { NodeClientOptions, NodeOptions } from '../types'; import { getEntryPointType } from '../utils/entry-point'; @@ -65,6 +66,7 @@ function getBaseDefaultIntegrations(): Integration[] { localVariablesIntegration(), nodeContextIntegration(), childProcessIntegration(), + workerThreadsIntegration(), processSessionIntegration(), modulesIntegration(), ];