@sentry-internal/node-native-stacktrace
Advanced tools
+27
-11
@@ -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"} |
+16
-12
@@ -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
@@ -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":";;AAmNA,wCAMC;AAOD,gCAEC;AAKD,8CAEC;AAOD,gDAEC;AAjPD,qCAA+D;AAC/D,yCAA0C;AAC1C,+CAA6C;AAC7C,6DAA+C;AAC/C,6CAAyC;AACzC,uCAAkC;AAElC,MAAM,MAAM,GAAG,IAAA,wBAAU,GAAE,CAAC;AAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,IAAA,kBAAS,GAAE,CAAC;AAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,IAAA,cAAK,GAAE,CAAC;AAClD,MAAM,GAAG,GAAG,IAAA,iBAAM,EAAC,uBAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC1C,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AA2CtG,sCAAsC;AACtC,SAAS,eAAe;IACtB,2CAA2C;IAC3C,IAAI,kBAAG,CAAC,gCAAgC,CAAC,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,kBAAG,CAAC,gCAAgC,CAAC,CAAC;QACtD,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED,oGAAoG;IACpG,IAAI,kBAAG,CAAC,+BAA+B,CAAC,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,IAAA,gBAAI,EAAC,IAAA,mBAAO,EAAC,kBAAG,CAAC,+BAA+B,CAAC,CAAC,EAAE,eAAe,UAAU,OAAO,CAAC,CAAC;QACzG,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,OAAO,CAAC,mCAAmC,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,yJAAyJ,CAAC,CAAC;YACxK,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,sFAAsF;IACtF,0GAA0G;IAC1G,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,mCAAmC,CAAC,CAAC;YACtD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,mCAAmC,CAAC,CAAC;YACtD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,mCAAmC,CAAC,CAAC;YACtD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,mCAAmC,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QAED,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,qCAAqC,CAAC,CAAC;YACxD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,qCAAqC,CAAC,CAAC;YACxD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,qCAAqC,CAAC,CAAC;YACxD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,qCAAqC,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,kCAAkC,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,kCAAkC,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,kCAAkC,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;gBAClB,OAAO,OAAO,CAAC,kCAAkC,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;gBAC1D,CAAC;YACH,CAAC;YACD,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;gBACvB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,wCAAwC,CAAC,CAAC;gBAC3D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,wCAAwC,CAAC,CAAC;gBAC3D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,wCAAwC,CAAC,CAAC;gBAC3D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,wCAAwC,CAAC,CAAC;gBAC3D,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,yCAAyC,CAAC,CAAC;gBAC5D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,yCAAyC,CAAC,CAAC;gBAC5D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,yCAAyC,CAAC,CAAC;gBAC5D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,yCAAyC,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;YAED,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;gBACvB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,0CAA0C,CAAC,CAAC;gBAC7D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,0CAA0C,CAAC,CAAC;gBAC7D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,0CAA0C,CAAC,CAAC;gBAC7D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBAClB,OAAO,OAAO,CAAC,0CAA0C,CAAC,CAAC;gBAC7D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,iBAAiB,UAAU,OAAO,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;AAIjC;;;;;;;;;GASG;AACH,SAAgB,cAAc,CAAC,mBAA+C,EAAE,UAAmB;IACjG,IAAI,OAAO,mBAAmB,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,CAAC,cAAc,CAAC,mBAAmB,EAAE,UAAU,IAAI,MAAM,CAAC,8BAAQ,CAAC,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,cAAc,CAAC,mBAAmB,IAAI,MAAM,CAAC,8BAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,UAAU,CAAC,iBAA0B,IAAI,EAAE,SAAkB;IAC3E,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,SAAgB,iBAAiB;IAC/B,OAAO,MAAM,CAAC,iBAAiB,EAAQ,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB;IAChC,OAAO,MAAM,CAAC,kBAAkB,EAAE,CAAC;AACrC,CAAC"} |
+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 |
+6
-6
| { | ||
| "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
2948211
18.47%332
6.41%236
15.12%