From 9fef2d988d8a3cfb18b1e913cb81d2e1664ae51b Mon Sep 17 00:00:00 2001 From: Atharv Pandey Date: Fri, 31 Jul 2026 03:58:53 +0530 Subject: [PATCH 1/2] feat: split childProcess integration into childProcess and workerThreads integrations Splits worker thread handling out of childProcessIntegration into a new workerThreadsIntegration. The deprecated captureWorkerErrors option is removed from both integrations; worker thread errors are now always captured. Exports the new integration from the astro server barrel and updates the e2e tests to expect worker thread error events with the auto.worker_thread mechanism. Uses unordered event matching in the process-thread e2e test since the two captured events can arrive in either order. Updates MIGRATION.md for the rename. Fixes #18698 Signed-off-by: Atharv Pandey --- MIGRATION.md | 11 +++++ .../suites/breadcrumbs/process-thread/app.mjs | 2 +- .../suites/breadcrumbs/process-thread/test.ts | 33 +++++++++------ .../suites/child-process/test.ts | 2 +- .../public-api/OnUncaughtException/test.ts | 6 +-- packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + .../node/src/integrations/childProcess.ts | 42 ++----------------- .../integrations/workerThreadsIntegration.ts | 40 ++++++++++++++++++ packages/node/src/sdk/index.ts | 2 + 12 files changed, 85 insertions(+), 57 deletions(-) create mode 100644 packages/node/src/integrations/workerThreadsIntegration.ts diff --git a/MIGRATION.md b/MIGRATION.md index 5c3984abae46..565d9908a6a9 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1046,6 +1046,17 @@ 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`). + +The deprecated `captureWorkerErrors` option was removed from both integrations. Worker thread errors are always captured now, and disabling `childProcessIntegration` no longer disables worker thread error capture, since that is handled by `workerThreadsIntegration`. + +> **TODO(v11):** Document how the two integrations are configured and what users who customized +> `childProcessIntegration` need to change. + ### Deno default integrations renamed to match the other SDKs Affected SDKs: `@sentry/deno`. 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..dc294d79a2a8 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.worker_thread', + 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', () => { +conditionalTest({ min: 20 })('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..9151d824cdba 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.worker_thread', 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..38777548ad79 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.worker_thread', handled: false, }, stacktrace: { @@ -146,7 +146,7 @@ describe('OnUncaughtException integration', () => { type: 'Error', value: 'job failed', mechanism: { - type: 'auto.child_process.worker_thread', + type: 'auto.worker_thread', handled: false, }, stacktrace: { @@ -178,7 +178,7 @@ describe('OnUncaughtException integration', () => { type: 'Error', value: 'job failed', mechanism: { - type: 'auto.child_process.worker_thread', + type: 'auto.worker_thread', 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..a3d6862bcd63 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -186,6 +186,7 @@ export { export * as logger from './logs/exports'; export { childProcessIntegration } from './integrations/childProcess'; +export { workerThreadsIntegration } from './integrations/workerThreadsIntegration'; export { consoleIntegration } from './integrations/console'; export { nodeContextIntegration } from './integrations/context'; export { contextLinesIntegration } from './integrations/contextlines'; 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/workerThreadsIntegration.ts b/packages/node/src/integrations/workerThreadsIntegration.ts new file mode 100644 index 000000000000..a68bfabad0aa --- /dev/null +++ b/packages/node/src/integrations/workerThreadsIntegration.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.worker_thread', + handled: false, + data: { ...(threadId !== undefined ? { threadId: String(threadId) } : {}) }, + }, + }); + }); +} diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 2bb381588581..991847f9cd0c 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -20,6 +20,7 @@ import { detectOrchestrionSetup } from '@sentry/server-utils/orchestrion'; import { registerDiagnosticsChannelInjection } from '@sentry/server-utils/orchestrion/register'; import { DEBUG_BUILD } from '../debug-build'; import { childProcessIntegration } from '../integrations/childProcess'; +import { workerThreadsIntegration } from '../integrations/workerThreadsIntegration'; import { consoleIntegration } from '../integrations/console'; import { nodeContextIntegration } from '../integrations/context'; import { contextLinesIntegration } from '../integrations/contextlines'; @@ -65,6 +66,7 @@ function getBaseDefaultIntegrations(): Integration[] { localVariablesIntegration(), nodeContextIntegration(), childProcessIntegration(), + workerThreadsIntegration(), processSessionIntegration(), modulesIntegration(), ]; From e1dea81f48ec1f21d480a7f010b91fab81ef64fa Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Sat, 22 Aug 2026 14:52:32 +0300 Subject: [PATCH 2/2] fixup! feat: split childProcess integration into childProcess and workerThreads integrations --- MIGRATION.md | 23 ++++++++++++++++--- .../scripts/consistentExports.ts | 1 + .../suites/breadcrumbs/process-thread/test.ts | 4 ++-- .../suites/child-process/test.ts | 2 +- .../public-api/OnUncaughtException/test.ts | 6 ++--- packages/node/src/index.ts | 2 +- ...ThreadsIntegration.ts => workerThreads.ts} | 4 ++-- packages/node/src/sdk/index.ts | 2 +- 8 files changed, 31 insertions(+), 13 deletions(-) rename packages/node/src/integrations/{workerThreadsIntegration.ts => workerThreads.ts} (88%) diff --git a/MIGRATION.md b/MIGRATION.md index 565d9908a6a9..f6d38600849c 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1052,10 +1052,27 @@ Affected SDKs: `@sentry/node` and dependents. The `childProcessIntegration` was split into a `childProcessIntegration` (for `child_process`) and a separate `workerThreadsIntegration` (for `worker_threads`). -The deprecated `captureWorkerErrors` option was removed from both integrations. Worker thread errors are always captured now, and disabling `childProcessIntegration` no longer disables worker thread error capture, since that is handled by `workerThreadsIntegration`. +Both integrations are enabled by default, so no change is needed to keep the previous behavior. -> **TODO(v11):** Document how the two integrations are configured and what users who customized -> `childProcessIntegration` need to change. +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 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/test.ts b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts index dc294d79a2a8..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 @@ -9,7 +9,7 @@ const WORKER_ERROR_EVENT = { type: 'Error', value: 'Worker error', mechanism: { - type: 'auto.worker_thread', + type: 'auto.node.worker_threads', handled: false, }, }, @@ -39,7 +39,7 @@ const TEST_ERROR_EVENT = { ], }; -conditionalTest({ min: 20 })('should capture child process breadcrumbs and worker thread errors', () => { +describe('should capture child process breadcrumbs and worker thread errors', () => { afterAll(() => { cleanupChildProcesses(); }); 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 9151d824cdba..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.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 38777548ad79..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.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.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.worker_thread', + type: 'auto.node.worker_threads', handled: false, }, stacktrace: { diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index a3d6862bcd63..b7ffef9ecf09 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -186,7 +186,6 @@ export { export * as logger from './logs/exports'; export { childProcessIntegration } from './integrations/childProcess'; -export { workerThreadsIntegration } from './integrations/workerThreadsIntegration'; export { consoleIntegration } from './integrations/console'; export { nodeContextIntegration } from './integrations/context'; export { contextLinesIntegration } from './integrations/contextlines'; @@ -203,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/workerThreadsIntegration.ts b/packages/node/src/integrations/workerThreads.ts similarity index 88% rename from packages/node/src/integrations/workerThreadsIntegration.ts rename to packages/node/src/integrations/workerThreads.ts index a68bfabad0aa..27010849e0f0 100644 --- a/packages/node/src/integrations/workerThreadsIntegration.ts +++ b/packages/node/src/integrations/workerThreads.ts @@ -31,9 +31,9 @@ function captureWorkerThreadEvents(worker: Worker): void { .on('error', error => { captureException(error, { mechanism: { - type: 'auto.worker_thread', + type: 'auto.node.worker_threads', handled: false, - data: { ...(threadId !== undefined ? { threadId: String(threadId) } : {}) }, + data: threadId !== undefined ? { threadId: String(threadId) } : undefined, }, }); }); diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 991847f9cd0c..73014fdf690e 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -20,7 +20,6 @@ import { detectOrchestrionSetup } from '@sentry/server-utils/orchestrion'; import { registerDiagnosticsChannelInjection } from '@sentry/server-utils/orchestrion/register'; import { DEBUG_BUILD } from '../debug-build'; import { childProcessIntegration } from '../integrations/childProcess'; -import { workerThreadsIntegration } from '../integrations/workerThreadsIntegration'; import { consoleIntegration } from '../integrations/console'; import { nodeContextIntegration } from '../integrations/context'; import { contextLinesIntegration } from '../integrations/contextlines'; @@ -34,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';