Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { localVariablesIntegration } from './integrations/local-variables';
export { modulesIntegration } from './integrations/modules';
export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception';
export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection';
// eslint-disable-next-line deprecation/deprecation
export { anrIntegration, disableAnrDetectionForCallback } from './integrations/anr';

export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } from './integrations/tracing/express';
Expand Down
49 changes: 41 additions & 8 deletions packages/node/src/integrations/anr/common.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,77 @@
import type { Contexts, DsnComponents, Primitive, SdkMetadata } from '@sentry/core';

/**
* Configuration options for the ANR (Application Not Responding) integration.
*
* These options control how the ANR detection system monitors the Node.js event loop
* and reports blocking events.
*
* @deprecated The ANR integration has been deprecated. Use `eventLoopBlockIntegration` from `@sentry/node-native` instead.
*/
export interface AnrIntegrationOptions {
/**
* Interval to send heartbeat messages to the ANR worker.
* Interval to send heartbeat messages to the ANR worker thread.
*
* Defaults to 50ms.
* The main thread sends heartbeat messages to the worker thread at this interval
* to indicate that the event loop is still responsive. Lower values provide more
* precise detection but may increase overhead.
*
* @default 50 (milliseconds)
*/
pollInterval: number;

/**
* Threshold in milliseconds to trigger an ANR event.
*
* Defaults to 5000ms.
* When the worker thread doesn't receive a heartbeat message for this duration,
* it considers the main thread to be blocked and triggers an ANR event.
*
* @default 5000 (milliseconds)
*/
anrThreshold: number;

/**
* Whether to capture a stack trace when the ANR event is triggered.
*
* Defaults to `false`.
* When enabled, uses the Node.js inspector API to capture the stack trace
* of the blocking code. This provides more detailed information about what
* caused the ANR but requires the debugger to be enabled.
*
* **Note:** This opens the inspector API and required ports.
*
* This uses the node debugger which enables the inspector API and opens the required ports.
* @default false
*/
captureStackTrace: boolean;

/**
* Maximum number of ANR events to send.
* Maximum number of ANR events to send per application session.
*
* Once this limit is reached, the ANR worker thread will exit to prevent
* sending duplicate events. This helps avoid spamming Sentry with repeated
* ANR events from the same blocking issue.
*
* Defaults to 1.
* @default 1
*/
maxAnrEvents: number;

/**
* Tags to include with ANR events.
* Static tags to include with all ANR events.
*
* These tags will be attached to every ANR event sent by this integration,
* useful for categorizing or filtering ANR events in Sentry.
*/
staticTags: { [key: string]: Primitive };

/**
* @ignore Internal use only.
*
* If this is supplied, stack frame filenames will be rewritten to be relative to this path.
* This is used internally for better stack trace readability.
*/
appRootPath: string | undefined;
}

// eslint-disable-next-line deprecation/deprecation
export interface WorkerStartData extends AnrIntegrationOptions {
debug: boolean;
sdkMetadata: SdkMetadata;
Expand Down
61 changes: 60 additions & 1 deletion packages/node/src/integrations/anr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const INTEGRATION_NAME = 'Anr';

type AnrInternal = { startWorker: () => void; stopWorker: () => void };

// eslint-disable-next-line deprecation/deprecation
const _anrIntegration = ((options: Partial<AnrIntegrationOptions> = {}) => {
if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
throw new Error('ANR detection requires Node 16.17.0 or later');
Expand Down Expand Up @@ -114,8 +115,59 @@ const _anrIntegration = ((options: Partial<AnrIntegrationOptions> = {}) => {
} as Integration & AnrInternal;
}) satisfies IntegrationFn;

// eslint-disable-next-line deprecation/deprecation
type AnrReturn = (options?: Partial<AnrIntegrationOptions>) => Integration & AnrInternal;

/**
* Application Not Responding (ANR) integration for Node.js applications.
*
* @deprecated The ANR integration has been deprecated. Use `eventLoopBlockIntegration` from `@sentry/node-native` instead.
*
* Detects when the Node.js main thread event loop is blocked for more than the configured
* threshold (5 seconds by default) and reports these as Sentry events.
*
* ## How it works
*
* ANR detection uses a worker thread to monitor the event loop in the main app thread.
* The main app thread sends a heartbeat message to the ANR worker thread every 50ms by default.
* If the ANR worker does not receive a heartbeat message for the configured threshold duration,
* it triggers an ANR event.
*
* ## Requirements
*
* - Node.js 16.17.0 or higher
* - Only supported in the Node.js runtime (not browsers)
* - Not supported for Node.js clusters
*
* ## Performance Impact
*
* Overhead should be minimal:
* - Main thread: Only polling the ANR worker over IPC every 50ms
* - Worker thread: Consumes around 10-20 MB of RAM
* - When ANR detected: Brief pause in debugger to capture stack trace (negligible compared to the blocking)
*
* ## Configuration Options
*
* - `pollInterval`: Interval to send heartbeat messages (default: 50ms)
* - `anrThreshold`: Threshold in milliseconds to trigger an ANR event (default: 5000ms)
* - `captureStackTrace`: Whether to capture stack traces using the Node.js inspector API (default: false)
* - `maxAnrEvents`: Maximum number of ANR events to send (default: 1)
* - `staticTags`: Tags to include with ANR events
*
* @example
* ```javascript
* Sentry.init({
* dsn: "https://[email protected]/0",
* integrations: [
* Sentry.anrIntegration({
* anrThreshold: 5000,
* captureStackTrace: true,
* pollInterval: 50,
* }),
* ],
* });
* ```
*/
export const anrIntegration = defineIntegration(_anrIntegration) as AnrReturn;

/**
Expand All @@ -125,6 +177,7 @@ export const anrIntegration = defineIntegration(_anrIntegration) as AnrReturn;
*/
async function _startWorker(
client: NodeClient,
// eslint-disable-next-line deprecation/deprecation
integrationOptions: Partial<AnrIntegrationOptions>,
): Promise<() => void> {
const dsn = client.getDsn();
Expand Down Expand Up @@ -229,7 +282,13 @@ async function _startWorker(
export function disableAnrDetectionForCallback<T>(callback: () => T): T;
export function disableAnrDetectionForCallback<T>(callback: () => Promise<T>): Promise<T>;
/**
* Disables ANR detection for the duration of the callback
* Temporarily disables ANR detection for the duration of a callback function.
*
* This utility function allows you to disable ANR detection during operations that
* are expected to block the event loop, such as intensive computational tasks or
* synchronous I/O operations.
*
* @deprecated The ANR integration has been deprecated. Use `eventLoopBlockIntegration` from `@sentry/node-native` instead.
*/
export function disableAnrDetectionForCallback<T>(callback: () => T | Promise<T>): T | Promise<T> {
const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as AnrInternal | undefined;
Expand Down
Loading