🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@sentry-internal/node-native-stacktrace

Package Overview
Dependencies
Maintainers
9
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sentry-internal/node-native-stacktrace - npm Package Compare versions

Comparing version
0.2.3
to
0.3.0
+27
-11
lib/index.d.ts

@@ -1,4 +0,25 @@

type Thread<S = unknown> = {
import type { AsyncLocalStorage } from 'node:async_hooks';
type AsyncStorageArgs = {
/** The AsyncLocalStorage instance used to fetch the store */
asyncLocalStorage: AsyncLocalStorage<unknown>;
/**
* Optional array of keys to fetch a specific property from the store
* Key will be traversed in order through Objects/Maps to reach the desired property.
*
* This is useful if you want to capture Open Telemetry context values as state.
*
* To get this value:
* context.getValue(my_unique_symbol_ref)
*
* You would set:
* stateLookup: ['_currentContext', my_unique_symbol_ref]
*/
stateLookup?: Array<string | symbol>;
};
type Thread<A = unknown, P = unknown> = {
frames: StackFrame[];
state?: S;
/** State captured from the AsyncLocalStorage, if provided */
asyncState?: A;
/** Optional state provided when calling threadPoll */
pollState?: P;
};

@@ -11,19 +32,14 @@ type StackFrame = {

};
/**
* Registers the current thread with the native module.
*
* @param threadName The name of the thread to register. Defaults to the current thread ID.
*/
export declare function registerThread(threadName?: string): void;
export declare function registerThread(storageOrThread: AsyncStorageArgs | string, threadName?: string): void;
/**
* Tells the native module that the thread is still running and updates the state.
*
* @param state Optional state to pass to the native module.
* @param disableLastSeen If true, disables the last seen tracking for this thread.
* @param enableLastSeen If true, enables the last seen tracking for this thread.
*/
export declare function threadPoll(state?: object, disableLastSeen?: boolean): void;
export declare function threadPoll(enableLastSeen?: boolean, pollState?: object): void;
/**
* Captures stack traces for all registered threads.
*/
export declare function captureStackTrace<S = unknown>(): Record<string, Thread<S>>;
export declare function captureStackTrace<A = unknown, P = unknown>(): Record<string, Thread<A, P>>;
/**

@@ -30,0 +46,0 @@ * Returns the number of milliseconds since the last time each thread was seen.

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,KAAK,MAAM,CAAC,CAAC,GAAG,OAAO,IAAI;IACzB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,CAAA;CACV,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAyJF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,GAAE,MAAyB,GAAG,IAAI,CAE1E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,IAAI,CAM1E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,OAAO,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE3D"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAc1D,KAAK,gBAAgB,GAAG;IACtB,6DAA6D;IAC7D,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;CACtC,CAAA;AAED,KAAK,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,IAAI;IACtC,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,CAAC,CAAC;IACf,sDAAsD;IACtD,SAAS,CAAC,EAAE,CAAC,CAAC;CACf,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AA0JF,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;AAC1D,wBAAgB,cAAc,CAAC,eAAe,EAAE,gBAAgB,GAAG,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;AAmBtG;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,cAAc,GAAE,OAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAEnF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAE1F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE3D"}

@@ -156,6 +156,16 @@ "use strict";

*
* @param threadName The name of the thread to register. Defaults to the current thread ID.
* This should be called on every thread that you want to capture stack traces from.
*
* @param storageOrThreadName Either the name of the thread, or an object containing an AsyncLocalStorage instance and optional storage key.
* @param threadName The name of the thread, if the first argument is an object.
*
* threadName defaults to the `threadId` if not provided.
*/
function registerThread(threadName = String(node_worker_threads_1.threadId)) {
native.registerThread(threadName);
function registerThread(storageOrThreadName, threadName) {
if (typeof storageOrThreadName === 'object') {
native.registerThread(storageOrThreadName, threadName || String(node_worker_threads_1.threadId));
}
else {
native.registerThread(storageOrThreadName || String(node_worker_threads_1.threadId));
}
}

@@ -165,12 +175,6 @@ /**

*
* @param state Optional state to pass to the native module.
* @param disableLastSeen If true, disables the last seen tracking for this thread.
* @param enableLastSeen If true, enables the last seen tracking for this thread.
*/
function threadPoll(state, disableLastSeen) {
if (typeof state === 'object' || disableLastSeen) {
native.threadPoll(state, disableLastSeen);
}
else {
native.threadPoll();
}
function threadPoll(enableLastSeen = true, pollState) {
native.threadPoll(enableLastSeen, pollState);
}

@@ -177,0 +181,0 @@ /**

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
+428
-70

@@ -5,2 +5,5 @@ #include <chrono>

#include <node.h>
#include <node_version.h>
#include <optional>
#include <vector>

@@ -16,2 +19,9 @@ // Platform-specific includes for time functions

#ifndef NODE_MAJOR_VERSION
#error "NODE_MAJOR_VERSION is not defined"
#endif
#define SUPPORTS_ASYNC_CONTEXT_FRAME NODE_MAJOR_VERSION >= 22
#define GET_CONTINUATION_PRESERVED_EMBEDDER_DATA_V2 V8_MAJOR_VERSION >= 14
using namespace v8;

@@ -21,4 +31,12 @@ using namespace node;

static const int kMaxStackFrames = 255;
static const int kMaxStackFrames = 50;
struct AsyncLocalStorageLookup {
// Async local storage instance associated with this thread
v8::Global<v8::Value> async_local_storage;
// Optional ordered array of keys (string | symbol) to traverse nested
// Map/Object structures to fetch the final state object
std::optional<std::vector<v8::Global<v8::Value>>> storage_keys;
};
// Structure to hold information for each thread/isolate

@@ -30,4 +48,6 @@ struct ThreadInfo {

milliseconds last_seen;
// Some JSON serialized state for the thread
std::string state;
// Optional async local storage associated with this thread
std::optional<AsyncLocalStorageLookup> async_store;
// Some JSON serialized state sent via threadPoll
std::string poll_state;
};

@@ -48,17 +68,153 @@

// Type alias for a vector of JsStackFrame
using JsStackTrace = std::vector<JsStackFrame>;
using JsStackFrames = std::vector<JsStackFrame>;
struct JsStackTrace {
// The frames in the stack trace
JsStackFrames frames;
// JSON serialized string of the async state
std::string async_state;
};
struct ThreadResult {
std::string thread_name;
std::string state;
JsStackTrace stack_frames;
JsStackTrace stack_trace;
// JSON serialized string of the poll state
std::string poll_state;
};
// Function to be called when an isolate's execution is interrupted
static void ExecutionInterrupted(Isolate *isolate, void *data) {
auto promise = static_cast<std::promise<JsStackTrace> *>(data);
// Recursively sanitize a value to be safely JSON-stringifiable by:
// - Removing properties whose values are BigInt, Function, or Symbol
// (dropped for objects, omitted from arrays)
// - Breaking cycles by omitting repeated objects (undefined -> dropped/omitted)
// - Preserving primitives and traversing arrays/objects
static v8::Local<v8::Value>
SanitizeForJSON(v8::Isolate *isolate, v8::Local<v8::Context> context,
v8::Local<v8::Value> value,
std::vector<v8::Local<v8::Object>> &ancestors) {
// Fast-path for primitives that are always JSON-compatible
if (value->IsNull() || value->IsBoolean() || value->IsNumber() ||
value->IsString()) {
return value;
}
// Values that JSON.stringify cannot handle directly
if (value->IsBigInt() || value->IsSymbol() || value->IsFunction() ||
value->IsUndefined()) {
// Returning undefined here lets callers decide to drop (object) or null
// (array)
return v8::Undefined(isolate);
}
// Arrays
if (value->IsArray()) {
auto arr = value.As<v8::Array>();
// Cycle detection
auto arr_obj = value.As<v8::Object>();
for (auto &a : ancestors) {
if (a->StrictEquals(arr_obj)) {
return v8::Undefined(isolate);
}
}
auto length = arr->Length();
auto out = v8::Array::New(isolate, 0);
ancestors.push_back(arr_obj);
uint32_t out_index = 0;
for (uint32_t i = 0; i < length; ++i) {
auto maybeEl = arr->Get(context, i);
v8::Local<v8::Value> el;
if (!maybeEl.ToLocal(&el)) {
el = v8::Undefined(isolate);
}
auto sanitized = SanitizeForJSON(isolate, context, el, ancestors);
if (!sanitized->IsUndefined()) {
out->Set(context, out_index++, sanitized)
.Check(); // omit undefined entries entirely
}
}
ancestors.pop_back();
return out;
}
// Objects (including Dates, RegExps, Maps as objects; we only traverse
// enumerable own props)
if (value->IsObject()) {
auto obj = value.As<v8::Object>();
// Cycle detection
for (auto &a : ancestors) {
if (a->StrictEquals(obj)) {
return v8::Undefined(isolate);
}
}
ancestors.push_back(obj);
// Collect own enumerable property names (string-keyed)
auto maybe_props = obj->GetPropertyNames(context);
if (maybe_props.IsEmpty()) {
ancestors.pop_back();
return obj; // Nothing enumerable to sanitize
}
auto props = maybe_props.ToLocalChecked();
auto out = v8::Object::New(isolate);
auto len = props->Length();
for (uint32_t i = 0; i < len; ++i) {
auto maybeKey = props->Get(context, i);
if (maybeKey.IsEmpty())
continue;
auto key = maybeKey.ToLocalChecked();
if (!key->IsString()) {
// Skip symbol and non-string keys to match JSON behavior
continue;
}
auto maybeVal = obj->Get(context, key);
if (maybeVal.IsEmpty())
continue;
auto val = maybeVal.ToLocalChecked();
auto sanitized = SanitizeForJSON(isolate, context, val, ancestors);
if (!sanitized->IsUndefined()) {
out->Set(context, key, sanitized).Check();
}
// else: undefined -> drop property
}
ancestors.pop_back();
return out;
}
// Fallback: return as-is (shouldn't hit here for other exotic types)
return value;
}
std::string JSONStringify(Isolate *isolate, Local<Value> value) {
auto context = isolate->GetCurrentContext();
// Sanitize the value first to avoid JSON failures (e.g., BigInt, cycles)
std::vector<v8::Local<v8::Object>> ancestors;
auto sanitized = SanitizeForJSON(isolate, context, value, ancestors);
if (sanitized->IsUndefined()) {
// Nothing serializable
return "";
}
auto maybe_json = v8::JSON::Stringify(context, sanitized);
if (maybe_json.IsEmpty()) {
return "";
}
v8::String::Utf8Value utf8(isolate, maybe_json.ToLocalChecked());
return *utf8 ? *utf8 : "";
}
// Function to get stack frames from a V8 stack trace
JsStackFrames GetStackFrames(Isolate *isolate) {
auto stack = StackTrace::CurrentStackTrace(isolate, kMaxStackFrames,
StackTrace::kDetailed);
JsStackTrace frames;
JsStackFrames frames;
if (!stack.IsEmpty()) {

@@ -97,7 +253,110 @@ for (int i = 0; i < stack->GetFrameCount(); i++) {

promise->set_value(frames);
return frames;
}
#if SUPPORTS_ASYNC_CONTEXT_FRAME
// Function to fetch the thread state from the async context store
std::string GetThreadState(Isolate *isolate,
const AsyncLocalStorageLookup &store) {
// Node.js stores the async local storage in the isolate's
// "ContinuationPreservedEmbedderData" map, keyed by the
// AsyncLocalStorage instance.
// https://github.com/nodejs/node/blob/c6316f9db9869864cea84e5f07585fa08e3e06d2/src/async_context_frame.cc#L37
#if GET_CONTINUATION_PRESERVED_EMBEDDER_DATA_V2
auto data = isolate->GetContinuationPreservedEmbedderDataV2().As<Value>();
#else
auto data = isolate->GetContinuationPreservedEmbedderData();
#endif
auto async_local_storage_local = store.async_local_storage.Get(isolate);
if (data.IsEmpty() || !data->IsMap() || async_local_storage_local.IsEmpty()) {
return "";
}
auto map = data.As<v8::Map>();
auto context = isolate->GetCurrentContext();
auto maybe_root_store = map->Get(context, async_local_storage_local);
if (maybe_root_store.IsEmpty()) {
return "";
}
auto root_store = maybe_root_store.ToLocalChecked();
if (store.storage_keys.has_value()) {
// Walk the keys to get the desired nested value
const auto &keys = store.storage_keys.value();
auto current = root_store;
for (auto &gkey : keys) {
auto local_key = gkey.Get(isolate);
if (!(local_key->IsString() || local_key->IsSymbol())) {
continue;
}
v8::MaybeLocal<v8::Value> maybeValue;
if (current->IsMap()) {
auto map_val = current.As<v8::Map>();
maybeValue = map_val->Get(context, local_key);
} else if (current->IsObject()) {
auto obj_val = current.As<v8::Object>();
maybeValue = obj_val->Get(context, local_key);
} else {
return "";
}
if (maybeValue.IsEmpty()) {
return "";
}
current = maybeValue.ToLocalChecked();
}
root_store = current;
}
return JSONStringify(isolate, root_store);
}
#endif
struct InterruptArgs {
std::promise<JsStackTrace> promise;
const std::optional<AsyncLocalStorageLookup> *store;
};
// Function to be called when an isolate's execution is interrupted
static void ExecutionInterrupted(Isolate *isolate, void *data) {
auto args = static_cast<InterruptArgs *>(data);
Locker locker(isolate);
HandleScope handle_scope(isolate);
if (isolate->IsExecutionTerminating()) {
args->promise.set_value({{}, ""});
delete args;
return;
}
auto frames = GetStackFrames(isolate);
std::string state = "";
#if SUPPORTS_ASYNC_CONTEXT_FRAME
if (args->store && args->store->has_value()) {
state = GetThreadState(isolate, args->store->value());
}
#endif
args->promise.set_value({frames, state});
delete args;
}
// Function to capture the stack trace of a single isolate
JsStackTrace CaptureStackTrace(Isolate *isolate) {
JsStackTrace
CaptureStackTrace(Isolate *isolate,
const std::optional<AsyncLocalStorageLookup> &store) {
if (isolate->IsExecutionTerminating()) {
return JsStackTrace{{}, ""};
}
std::promise<JsStackTrace> promise;

@@ -107,4 +366,5 @@ auto future = promise.get_future();

// The v8 isolate must be interrupted to capture the stack trace
// Execution resumes automatically after ExecutionInterrupted returns
isolate->RequestInterrupt(ExecutionInterrupted, &promise);
isolate->RequestInterrupt(ExecutionInterrupted,
new InterruptArgs{std::move(promise), &store});
return future.get();

@@ -116,27 +376,40 @@ }

auto capture_from_isolate = args.GetIsolate();
auto current_context = capture_from_isolate->GetCurrentContext();
std::vector<std::future<ThreadResult>> futures;
std::vector<ThreadResult> results;
{
std::vector<std::future<ThreadResult>> futures;
std::lock_guard<std::mutex> lock(threads_mutex);
for (auto [thread_isolate, thread_info] : threads) {
for (auto &thread : threads) {
auto thread_isolate = thread.first;
auto &thread_info = thread.second;
if (thread_isolate == capture_from_isolate)
continue;
auto thread_name = thread_info.thread_name;
auto state = thread_info.state;
auto poll_state = thread_info.poll_state;
futures.emplace_back(std::async(
std::launch::async,
[thread_name, state](Isolate *isolate) -> ThreadResult {
return ThreadResult{thread_name, state, CaptureStackTrace(isolate)};
[thread_isolate, thread_name, poll_state](
const std::optional<AsyncLocalStorageLookup> &async_store)
-> ThreadResult {
return ThreadResult{thread_name,
CaptureStackTrace(thread_isolate, async_store),
poll_state};
},
thread_isolate));
std::cref(thread_info.async_store)));
}
for (auto &fut : futures) {
results.emplace_back(fut.get());
}
}
auto current_context = capture_from_isolate->GetCurrentContext();
Local<Object> output = Object::New(capture_from_isolate);
for (auto &future : futures) {
auto result = future.get();
for (auto &result : results) {
auto key =

@@ -148,5 +421,5 @@ String::NewFromUtf8(capture_from_isolate, result.thread_name.c_str(),

Local<Array> jsFrames =
Array::New(capture_from_isolate, result.stack_frames.size());
for (size_t i = 0; i < result.stack_frames.size(); ++i) {
const auto &frame = result.stack_frames[i];
Array::New(capture_from_isolate, result.stack_trace.frames.size());
for (size_t i = 0; i < result.stack_trace.frames.size(); ++i) {
const auto &frame = result.stack_trace.frames[i];
Local<Object> frameObj = Object::New(capture_from_isolate);

@@ -201,5 +474,6 @@ frameObj

if (!result.state.empty()) {
if (!result.poll_state.empty()) {
v8::MaybeLocal<v8::String> stateStr = v8::String::NewFromUtf8(
capture_from_isolate, result.state.c_str(), NewStringType::kNormal);
capture_from_isolate, result.poll_state.c_str(),
NewStringType::kNormal);
if (!stateStr.IsEmpty()) {

@@ -212,3 +486,3 @@ v8::MaybeLocal<v8::Value> maybeStateVal =

->Set(current_context,
String::NewFromUtf8(capture_from_isolate, "state",
String::NewFromUtf8(capture_from_isolate, "pollState",
NewStringType::kInternalized)

@@ -222,2 +496,22 @@ .ToLocalChecked(),

if (!result.stack_trace.async_state.empty()) {
v8::MaybeLocal<v8::String> stateStr = v8::String::NewFromUtf8(
capture_from_isolate, result.stack_trace.async_state.c_str(),
NewStringType::kNormal);
if (!stateStr.IsEmpty()) {
v8::MaybeLocal<v8::Value> maybeStateVal =
v8::JSON::Parse(current_context, stateStr.ToLocalChecked());
v8::Local<v8::Value> stateVal;
if (maybeStateVal.ToLocal(&stateVal)) {
threadObj
->Set(current_context,
String::NewFromUtf8(capture_from_isolate, "asyncState",
NewStringType::kInternalized)
.ToLocalChecked(),
stateVal)
.Check();
}
}
}
output->Set(current_context, key, threadObj).Check();

@@ -237,30 +531,100 @@ }

// Function to register a thread and update it's last seen time
void RegisterThreadInternal(
Isolate *isolate, const std::string &thread_name,
std::optional<AsyncLocalStorageLookup> async_store) {
std::lock_guard<std::mutex> lock(threads_mutex);
auto found = threads.find(isolate);
if (found == threads.end()) {
threads.emplace(isolate, ThreadInfo{thread_name, milliseconds::zero(),
std::move(async_store), ""});
// Register a cleanup hook to remove this thread when the isolate is
// destroyed
node::AddEnvironmentCleanupHook(isolate, Cleanup, isolate);
}
}
// Function to register a thread and update its last seen time
void RegisterThread(const FunctionCallbackInfo<Value> &args) {
auto isolate = args.GetIsolate();
auto context = isolate->GetCurrentContext();
if (args.Length() != 1 || !args[0]->IsString()) {
if (args.Length() == 1 && args[0]->IsString()) {
v8::String::Utf8Value utf8(isolate, args[0]);
std::string thread_name(*utf8 ? *utf8 : "");
RegisterThreadInternal(isolate, thread_name, std::nullopt);
} else if (args.Length() == 2 && args[0]->IsObject() && args[1]->IsString()) {
v8::String::Utf8Value utf8(isolate, args[1]);
std::string thread_name(*utf8 ? *utf8 : "");
auto obj = args[0].As<Object>();
auto async_local_storage_val =
obj->Get(context, String::NewFromUtf8(isolate, "asyncLocalStorage",
NewStringType::kInternalized)
.ToLocalChecked());
if (async_local_storage_val.IsEmpty() ||
!async_local_storage_val.ToLocalChecked()->IsObject()) {
isolate->ThrowException(Exception::Error(
String::NewFromUtf8(isolate,
"The first argument must be an object with an "
"asyncLocalStorage property",
NewStringType::kInternalized)
.ToLocalChecked()));
return;
}
std::optional<std::vector<v8::Global<v8::Value>>> storage_keys =
std::nullopt;
auto storage_key_val =
obj->Get(context, String::NewFromUtf8(isolate, "stateLookup",
NewStringType::kInternalized)
.ToLocalChecked());
if (!storage_key_val.IsEmpty()) {
auto local_val = storage_key_val.ToLocalChecked();
if (!local_val->IsUndefined() && !local_val->IsNull()) {
if (local_val->IsArray()) {
auto arr = local_val.As<v8::Array>();
std::vector<v8::Global<v8::Value>> keys_vec;
uint32_t length = arr->Length();
for (uint32_t i = 0; i < length; ++i) {
auto maybeEl = arr->Get(context, i);
if (maybeEl.IsEmpty())
continue;
auto el = maybeEl.ToLocalChecked();
if (el->IsString() || el->IsSymbol()) {
keys_vec.emplace_back(isolate, el);
}
}
if (!keys_vec.empty()) {
storage_keys = std::move(keys_vec);
}
}
}
}
auto store = AsyncLocalStorageLookup{
v8::Global<v8::Value>(isolate,
async_local_storage_val.ToLocalChecked()),
std::move(storage_keys)};
RegisterThreadInternal(isolate, thread_name, std::move(store));
} else {
isolate->ThrowException(Exception::Error(
String::NewFromUtf8(
isolate, "registerThread(name) requires a single name argument",
isolate,
"Incorrect arguments. Expected: \n"
"- registerThread(threadName: string) or \n"
"- registerThread(storage: {asyncLocalStorage: AsyncLocalStorage; "
"stateLookup?: Array<string | symbol>}, "
"threadName: string)",
NewStringType::kInternalized)
.ToLocalChecked()));
return;
}
v8::String::Utf8Value utf8(isolate, args[0]);
std::string thread_name(*utf8 ? *utf8 : "");
{
std::lock_guard<std::mutex> lock(threads_mutex);
auto found = threads.find(isolate);
if (found == threads.end()) {
threads.emplace(isolate,
ThreadInfo{thread_name, milliseconds::zero(), ""});
// Register a cleanup hook to remove this thread when the isolate is
// destroyed
node::AddEnvironmentCleanupHook(isolate, Cleanup, isolate);
}
}
}

@@ -288,3 +652,3 @@

// Fallback for other platforms using steady_clock. Note: this will be
// monotonic but is not gaurenteed to ignore time spent while suspended.
// monotonic but is not guaranteed to ignore time spent while suspended.
return steady_clock::now();

@@ -297,20 +661,12 @@ #endif

auto isolate = args.GetIsolate();
auto context = isolate->GetCurrentContext();
std::string state_str;
if (args.Length() > 0 && args[0]->IsValue()) {
MaybeLocal<String> maybe_json = v8::JSON::Stringify(context, args[0]);
if (!maybe_json.IsEmpty()) {
v8::String::Utf8Value utf8_state(isolate, maybe_json.ToLocalChecked());
state_str = *utf8_state ? *utf8_state : "";
} else {
state_str = "";
}
} else {
state_str = "";
bool enable_last_seen = true;
if (args.Length() > 0 && args[0]->IsBoolean()) {
enable_last_seen = args[0]->BooleanValue(isolate);
}
bool disable_last_seen = false;
if (args.Length() > 1 && args[1]->IsBoolean()) {
disable_last_seen = args[1]->BooleanValue(isolate);
std::string poll_state = "";
if (args.Length() > 1 && args[1]->IsObject()) {
auto obj = args[1].As<Object>();
poll_state = JSONStringify(isolate, obj);
}

@@ -323,8 +679,9 @@

auto &thread_info = found->second;
thread_info.state = state_str;
if (disable_last_seen) {
thread_info.last_seen = milliseconds::zero();
} else {
thread_info.poll_state = std::move(poll_state);
if (enable_last_seen) {
thread_info.last_seen = duration_cast<milliseconds>(
GetUnbiasedMonotonicTime().time_since_epoch());
} else {
thread_info.last_seen = milliseconds::zero();
}

@@ -338,2 +695,3 @@ }

Isolate *isolate = args.GetIsolate();
Local<Object> result = Object::New(isolate);

@@ -364,3 +722,3 @@ milliseconds now = duration_cast<milliseconds>(

Local<Context> context) {
auto isolate = context->GetIsolate();
auto isolate = v8::Isolate::GetCurrent();

@@ -367,0 +725,0 @@ exports

{
"name": "@sentry-internal/node-native-stacktrace",
"version": "0.2.3",
"version": "0.3.0",
"main": "lib/index.js",

@@ -26,3 +26,3 @@ "types": "lib/index.d.ts",

"fix:clang": "node scripts/clang-format.mjs --fix",
"build": "yarn build:lib && yarn build:bindings:configure && yarn build:bindings",
"build": "yarn clean && yarn build:lib && yarn build:bindings:configure && yarn build:bindings",
"build:lib": "tsc",

@@ -33,6 +33,6 @@ "build:bindings:configure": "node-gyp configure",

"build:bindings:arm64": "node-gyp build --arch=arm64 && node scripts/copy-target.mjs",
"build:dev": "yarn clean && yarn build:bindings:configure && yarn build",
"build:tarball": "npm pack",
"clean": "node-gyp clean && rm -rf lib && rm -rf build",
"test": "yarn test:install && node ./test/prepare.mjs && vitest run --silent=false --disable-console-intercept",
"clean": "node-gyp clean && rm -rf lib && rm -rf build && rm -f *.tgz",
"test": "yarn test:install && yarn test:prepare && vitest run --poolOptions.forks.singleFork --silent=false --disable-console-intercept",
"test:prepare": "node ./test/prepare.mjs",
"test:install": "cross-env ALWAYS_THROW=true yarn install"

@@ -71,4 +71,4 @@ },

"volta": {
"node": "24.1.0"
"node": "24.9.0"
}
}
+57
-26

@@ -8,6 +8,8 @@ # `@sentry-internal/node-native-stacktrace`

blocking via periodic heartbeats. When the time from the last heartbeat crosses
a threshold, JavaScript stack traces can be captured. The heartbeats can
optionally include state information which is included with the corresponding
stack trace.
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

@@ -74,3 +76,3 @@ [Event Loop Blocked Detection](https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/event-loop-block/)

},
'2': { // Worker thread
'2': { // Worker thread
frames: [

@@ -110,5 +112,5 @@ {

### 1. Set up thread heartbeats
### 1. Register threads with `AsyncLocalStorage` state tracking and heartbeats
Send regular heartbeats with optional state information:
Send regular heartbeats:

@@ -120,12 +122,15 @@ ```ts

} from "@sentry-internal/node-native-stacktrace";
import { AsyncLocalStorage } from "node:async_hooks";
// Register this thread
registerThread();
// Create async local storage for state tracking
const asyncLocalStorage = new AsyncLocalStorage();
// Set some state in the async local storage
asyncLocalStorage.enterWith({ someState: "value" });
// Send heartbeats every 200ms with optional state
// Register this thread with async local storage
registerThread({ asyncLocalStorage });
// Send heartbeats every 200ms
setInterval(() => {
threadPoll({
endpoint: "/api/current-request",
userId: getCurrentUserId(),
});
threadPoll();
}, 200);

@@ -157,3 +162,3 @@ ```

console.error("Stack trace:", blockedThread.frames);
console.error("Last known state:", blockedThread.state);
console.error("Async state:", blockedThread.asyncState);
}

@@ -170,17 +175,44 @@ }

Registers the current thread for monitoring. Must be called from each thread you
want to capture stack traces from.
#### `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.
#### `captureStackTrace<State>(): Record<string, Thread<State>>`
```ts
type AsyncStorageArgs = {
/** AsyncLocalStorage instance to fetch state from */
asyncLocalStorage: AsyncLocalStorage<unknown>;
/**
* Optional array of keys to pick a specific property from the store.
* Key will be traversed in order through Objects/Maps to reach the desired property.
*
* This is useful if you want to capture Open Telemetry context values as state.
*
* To get this value:
* context.getValue(MY_UNIQUE_SYMBOL_REF)
*
* You would set:
* stateLookup: ['_currentContext', MY_UNIQUE_SYMBOL_REF]
*/
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 the stack trace of the calling thread itself.
but will not capture a stack trace for the calling thread itself.
```ts
type Thread<S> = {
type Thread<A = unknown, P = unknown> = {
frames: StackFrame[];
state?: S;
/** State captured from the AsyncLocalStorage */
asyncState?: A;
/** Optional state provided when calling threadPoll */
pollState?: P;
};

@@ -196,12 +228,11 @@

#### `threadPoll<State>(state?: State, disableLastSeen?: boolean): void`
#### `threadPoll<State>(disableLastSeen?: boolean, pollState?: object): void`
Sends a heartbeat from the current thread with optional state information. The
state object will be serialized and included as a JavaScript object with the
corresponding stack trace.
Sends a heartbeat from the current thread.
- `state` (optional): An object containing state information to include with the
stack trace.
- `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.

@@ -208,0 +239,0 @@ #### `getThreadsLastSeen(): Record<string, number>`

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet