Captures and cleans stack traces for JavaScript applications
npx @tessl/cli install tessl/npm-stack-utils@2.0.0Stack Utils is a JavaScript library for capturing, cleaning, and manipulating stack traces in Node.js applications. It provides powerful utilities to remove internal Node.js lines from stack traces, shorten file paths relative to a working directory, and parse individual stack trace lines into structured objects. Originally extracted from the node-tap testing framework, it's particularly useful for testing frameworks, error handling systems, and debugging tools that need to present clean, readable stack traces.
npm install stack-utilsconst StackUtils = require('stack-utils');const StackUtils = require('stack-utils');
// Create a new instance with default settings
const stack = new StackUtils({
cwd: process.cwd(),
internals: StackUtils.nodeInternals()
});
// Clean a stack trace from an error
console.log(stack.clean(new Error().stack));
// Capture current stack trace as string
const currentStack = stack.captureString();
console.log(currentStack);
// Get information about current location
const location = stack.at();
console.log(location); // { line: 15, column: 23, file: 'app.js' }Creates a new StackUtils instance with customizable filtering and formatting options.
/**
* Creates a new StackUtils instance
* @param options - Configuration options
*/
new StackUtils(options);
interface StackUtilsOptions {
/** Array of npm package names to ignore in stack traces */
ignoredPackages?: string[];
/** Array of RegExp patterns for internal lines to filter */
internals?: RegExp[];
/** Current working directory for relative path resolution */
cwd?: string;
/** Optional function to wrap CallSite objects */
wrapCallSite?: (callSite: CallSite) => CallSite;
}Usage Example:
const stack = new StackUtils({
cwd: process.cwd(),
ignoredPackages: ['lodash', 'bluebird'],
internals: StackUtils.nodeInternals().concat([
/\/my-internal-module\//
]),
wrapCallSite: (callSite) => {
// Custom CallSite manipulation for source maps
return callSite;
}
});Returns default regular expressions for filtering Node.js internal stack trace lines.
/**
* Get default RegExp patterns for Node.js internals
* @returns Array of RegExp objects for filtering Node.js internal lines
*/
static nodeInternals(): RegExp[];Removes internal lines and formats stack traces for better readability.
/**
* Clean and format a stack trace by removing internal lines
* @param stack - Stack trace string or array of lines
* @param indent - Number of spaces to indent each line
* @returns Cleaned stack trace string
*/
clean(stack: string | string[], indent?: number): string;Usage Examples:
// Clean an error stack trace
try {
throw new Error('Something went wrong');
} catch (err) {
const cleanStack = stack.clean(err.stack);
console.log(cleanStack);
}
// Clean with custom indentation
const indentedStack = stack.clean(err.stack, 4);
// Clean an array of stack lines
const stackLines = err.stack.split('\n');
const cleaned = stack.clean(stackLines);Captures the current stack trace and returns it as a cleaned string.
/**
* Capture current stack trace as cleaned string
* @param limit - Maximum number of stack frames or start function
* @param fn - Function to start capture from
* @returns Cleaned stack trace string
*/
captureString(limit?: number | function, fn?: function): string;Usage Examples:
// Capture current stack with default settings
const stack1 = stackUtils.captureString();
// Limit to 5 frames
const stack2 = stackUtils.captureString(5);
// Start capture from specific function
function myFunction() {
return stackUtils.captureString(myFunction);
}
// Using limit and start function
const stack3 = stackUtils.captureString(10, myFunction);Captures the current stack trace as an array of CallSite objects for programmatic analysis.
/**
* Capture current stack trace as CallSite objects
* @param limit - Maximum number of stack frames or start function
* @param fn - Function to start capture from
* @returns Array of CallSite objects
*/
capture(limit?: number | function, fn?: function): CallSite[];Captures information about the current execution location as a serializable object.
/**
* Get current execution location information
* @param fn - Function to start capture from
* @returns Object with location information
*/
at(fn?: function): LocationInfo;
interface LocationInfo {
/** Line number */
line?: number;
/** Column number */
column?: number;
/** File path (relative to cwd) */
file?: string;
/** Whether this is a constructor call */
constructor?: boolean;
/** Eval origin information if applicable */
evalOrigin?: string;
/** Whether this is native code */
native?: boolean;
/** Type name */
type?: string;
/** Function name */
function?: string;
/** Method name */
method?: string;
}Usage Examples:
function exampleFunction() {
const location = stack.at();
console.log(`Called from ${location.file}:${location.line}:${location.column}`);
console.log(`Function: ${location.function}`);
}
// Get location starting from specific function
function wrapper() {
const location = stack.at(wrapper);
return location;
}Parses a single stack trace line into a structured object with detailed information.
/**
* Parse a single stack trace line into structured object
* @param line - Single line from a stack trace
* @returns Parsed line information or null if no match
*/
parseLine(line: string): ParsedLine | null;
interface ParsedLine {
/** Line number */
line?: number;
/** Column number */
column?: number;
/** File path */
file?: string;
/** Whether this is a constructor call */
constructor?: boolean;
/** Eval origin information */
evalOrigin?: string;
/** Eval line number */
evalLine?: number;
/** Eval column number */
evalColumn?: number;
/** Eval file path */
evalFile?: string;
/** Whether this is native code */
native?: boolean;
/** Function name */
function?: string;
/** Method name */
method?: string;
}Usage Examples:
// Parse individual stack trace lines
const stackTrace = new Error().stack;
const lines = stackTrace.split('\n');
lines.forEach(line => {
const parsed = stack.parseLine(line);
if (parsed) {
console.log(`${parsed.function} in ${parsed.file}:${parsed.line}`);
}
});
// Handle different line formats
const testLines = [
' at myFunction (app.js:10:5)',
' at new Constructor (lib.js:25:12)',
' at eval (eval at <anonymous> (app.js:5:1), <anonymous>:1:1)',
' at native'
];
testLines.forEach(line => {
const result = stack.parseLine(line);
console.log(result);
});The native V8 CallSite interface provides detailed information about stack frames.
interface CallSite {
getThis(): any;
getTypeName(): string | null;
getFunction(): Function;
getFunctionName(): string | null;
getMethodName(): string | null;
getFileName(): string | null;
getLineNumber(): number | null;
getColumnNumber(): number | null;
getEvalOrigin(): string | null;
isToplevel(): boolean;
isEval(): boolean;
isNative(): boolean;
isConstructor(): boolean;
}Stack Utils gracefully handles various edge cases:
process object is not availableconst StackUtils = require('stack-utils');
class TestFramework {
constructor() {
this.stack = new StackUtils({
ignoredPackages: ['tap', 'mocha', 'jest'],
cwd: process.cwd()
});
}
reportError(error) {
console.error('Test failed:');
console.error(this.stack.clean(error.stack, 2));
}
getCurrentTestLocation() {
return this.stack.at(this.reportError);
}
}const StackUtils = require('stack-utils');
const logger = {
stack: new StackUtils({
ignoredPackages: ['winston', 'pino'],
cwd: process.cwd()
}),
error(message, error) {
console.log({
message,
stack: this.stack.clean(error.stack),
location: this.stack.at(this.error)
});
}
};const StackUtils = require('stack-utils');
function createDebugger(namespace) {
const stack = new StackUtils();
return function debug(message) {
const location = stack.at(debug);
console.log(`[${namespace}] ${location.file}:${location.line} - ${message}`);
};
}
const debug = createDebugger('myapp');
debug('Something happened'); // [myapp] app.js:15 - Something happened