@sentry-internal/node-native-stacktrace
A native Node.js module that can capture JavaScript stack traces for registered
main or worker threads from any other thread, even if event loops are blocked.
The module also provides a means to create a watchdog system to track event loop
blocking via periodic heartbeats. When the time from the last heartbeat crosses
a threshold, JavaScript stack traces can be captured.
For Node.js >= v24, this module can also capture state from AsyncLocalStorage
at the time of stack trace capture, which can help provide context on what the
thread was working on when it became blocked.
This native module is used for Sentry's
Event Loop Blocked Detection
feature.
Basic Usage
1. Register threads you want to monitor
In your main thread or worker threads:
import { registerThread } from "@sentry-internal/node-native-stacktrace";
registerThread();
2. Capture stack traces from any thread
import { captureStackTrace } from "@sentry-internal/node-native-stacktrace";
const stacks = captureStackTrace();
console.log(stacks);
Example Output
Stack traces show where each thread is currently executing:
{
'0': {
frames: [
{
function: 'from',
filename: 'node:buffer',
lineno: 298,
colno: 28
},
{
function: 'pbkdf2Sync',
filename: 'node:internal/crypto/pbkdf2',
lineno: 78,
colno: 17
},
{
function: 'longWork',
filename: '/app/test.js',
lineno: 20,
colno: 29
},
{
function: '?',
filename: '/app/test.js',
lineno: 24,
colno: 1
}
]
},
'2': {
frames: [
{
function: 'from',
filename: 'node:buffer',
lineno: 298,
colno: 28
},
{
function: 'pbkdf2Sync',
filename: 'node:internal/crypto/pbkdf2',
lineno: 78,
colno: 17
},
{
function: 'longWork',
filename: '/app/worker.js',
lineno: 10,
colno: 29
},
{
function: '?',
filename: '/app/worker.js',
lineno: 14,
colno: 1
}
]
}
}
Advanced Usage: Automatic blocked event loop Detection
Set up automatic detection of blocked event loops:
1. Register threads with AsyncLocalStorage state tracking and heartbeats
Send regular heartbeats:
import {
registerThread,
threadPoll,
} from "@sentry-internal/node-native-stacktrace";
import { AsyncLocalStorage } from "node:async_hooks";
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.enterWith({ someState: "value" });
registerThread({ asyncLocalStorage });
setInterval(() => {
threadPoll();
}, 200);
2. Monitor from a watchdog thread
Monitor all registered threads from a dedicated thread:
import {
captureStackTrace,
getThreadsLastSeen,
} from "@sentry-internal/node-native-stacktrace";
const THRESHOLD = 1000;
setInterval(() => {
const threadsLastSeen = getThreadsLastSeen();
for (const [threadId, timeSinceLastSeen] of Object.entries(threadsLastSeen)) {
if (timeSinceLastSeen > THRESHOLD) {
const stackTraces = captureStackTrace();
const blockedThread = stackTraces[threadId];
console.error(`🚨 Thread ${threadId} blocked for ${timeSinceLastSeen}ms`);
console.error("Stack trace:", blockedThread.frames);
console.error("Async state:", blockedThread.asyncState);
}
}
}, 500);
API Reference
Functions
registerThread(threadName?: string): void
registerThread(asyncStorage: AsyncStorageArgs, threadName?: string): void
Registers the current thread for stack trace capture. Must be called from each
thread you want to capture stack traces from.
threadName (optional): Name for the thread. Defaults to the current thread
ID.
asyncStorage (optional): AsyncStorageArgs to fetch state from
AsyncLocalStorage on stack trace capture.
type AsyncStorageArgs = {
asyncLocalStorage: AsyncLocalStorage<unknown>;
stateLookup?: Array<string | symbol>;
};
captureStackTrace<State>(): Record<string, Thread<A, P>>
Captures stack traces from all registered threads. Can be called from any thread
but will not capture a stack trace for the calling thread itself.
type Thread<A = unknown, P = unknown> = {
frames: StackFrame[];
asyncState?: A;
pollState?: P;
};
type StackFrame = {
function: string;
filename: string;
lineno: number;
colno: number;
};
threadPoll<State>(disableLastSeen?: boolean, pollState?: object): void
Sends a heartbeat from the current thread.
disableLastSeen (optional): If true, disables the tracking of the last
seen time for this thread.
pollState (optional): An object containing state to include with the next
stack trace capture. This can be used instead of or in addition to
AsyncLocalStorage based state tracking.
getThreadsLastSeen(): Record<string, number>
Returns the time in milliseconds since each registered thread called
threadPoll().