Git Product home page Git Product logo

stackman's Introduction

Stackman

Give Stackman an error and he will give an array of stack frames with extremely detailed information for each frame in the stack trace.

With Stackman you get access to the actual source code and surrounding lines for where the error occurred, you get to know if it happened inside a 3rd party module, in Node.js or in your own code. For a full list of information, check out the API below.

npm Build status js-standard-style sponsor

Install

npm install stackman

Basic usage

var stackman = require('stackman')()

var err = new Error('Oops!')

stackman.callsites(err, function (err, callsites) {
  if (err) throw err

  callsites.forEach(function (callsite) {
    console.log('Error occured in at %s line %d',
      callsite.getFileName(),
      callsite.getLineNumber())
  })
})

Gotchas

error.stack

This module works because V8 (the JavaScript engine behind Node.js) allows us to hook into the stack trace generator function before that stack trace is generated. It's triggered by accessing the .stack property on the Error object, so please don't do that before parsing the error to stackman, else this will not work!

If you want to output the regular stack trace, just do so after parsing the callsites:

// first call stackman.callsites with the error
stackman.callsites(err, function () {...})

// then you can print out the stack trace
console.log(err.stack)

Stackman API

var stackman = Stackman([options])

This module exposes a single function which you must call to get a stackman object.

The function takes an optional options object as its only argument. These are the available options:

  • fileCacheMax - When source files are read from disk, they are kept in memory in an LRU cache to speed up processing of future errors. You can change the max number of files kept in the LRU cache using this property (default: 500)
  • sourceMapCacheMax - When source maps are read from disk, the processed source maps are kept in memory in an LRU cache to speed up processing of future errors. You can change the max number of source maps kept in the LRU cache using this property (default: 100)

stackman.callsites(err[, options], callback)

Given an error object, this function will call the callback with an optional error as the first argument and an array of CallSite objects as the 2nd (a call site is a frame in the stack trace).

Note that any error related to loading or parsing source maps will be suppressed. If a source map related error occurs, Stackman behaves as if the sourcemap option is false.

Options:

  • sourcemap - A boolean specifying if Stackman should look for and process source maps (default: true)

var properties = stackman.properties(err)

Given an error object, this function will return an object containing all the custom properties from the original error object (beside date objects, properties of type object and function are not included in this object).

stackman.sourceContexts(callsites[, options], callback)

Convenience function to get the source context for all call sites in the callsites argument in one go (instead of iterating over the call sites and calling callsite.sourceContext() for each of them).

Calls the callback with an optional error object as the first argument and an array of source context objects as the 2nd. Each element in the context array matches a call site in the callsites array.

Options:

  • lines - Total number of lines of soruce context to be loaded with the call site line in the center (default: 5)
  • inAppLines - Total number of lines of soruce context to be loaded with the call site line in the center if callsite.isApp() is true. Overwrites lines (default: 5)
  • libraryLines - Number of lines of soruce context to be loaded with the call site line in the center if callsite.isApp() is false. Overwrites lines (default: 5)

All node core call sites and call sites where no lines were collected due to the above options being 0, will have the context value null.

CallSite API

A CallSite object is an object provided by the V8 stack trace API representing a frame in the stack trace. Stackman will decorate each CallSite object with custom functions and behavior.

callsite.sourcemap

If source map support is enabled and a source map have been found for the CallSite, this property will be a reference to a SourceMapConsumer object representing the given CallSite.

If set, all functions on the CallSite object will be source map aware. I.e. their return values will be related to the original source code and not the transpiled source code.

var val = callsite.getThis()

Inherited from V8

Returns the value of this.

To maintain restrictions imposed on strict mode functions, frames that have a strict mode function and all frames below (its caller etc.) are not allow to access their receiver and function objects. For those frames, getThis() will return undefined.

var str = callsite.getTypeName()

Inherited from V8

Returns the type of this as a string. This is the name of the function stored in the constructor field of this, if available, otherwise the object's [[Class]] internal property.

var str = callsite.getTypeNameSafely()

A safer version of callsite.getTypeName() that safely handles an exception that sometimes is thrown when using "use strict" in which case null is returned.

var fn = callsite.getFunction()

Inherited from V8

Returns the current function.

To maintain restrictions imposed on strict mode functions, frames that have a strict mode function and all frames below (its caller etc.) are not allow to access their receiver and function objects. For those frames, getFunction() will return undefined.

var str = callsite.getFunctionName()

Inherited from V8

Returns the name of the current function, typically its name property. If a name property is not available an attempt will be made to try to infer a name from the function's context.

var str = callsite.getFunctionNameSanitized()

Guaranteed to always return the most meaningful function name. If none can be determined, the string <anonymous> will be returned.

var str = callsite.getMethodName()

Inherited from V8

Returns the name of the property of this or one of its prototypes that holds the current function.

var str = callsite.getFileName()

Inherited from V8 if callsite.sourcemap is undefined

If this function was defined in a script returns the name of the script.

var str = callsite.getRelativeFileName()

Returns a filename realtive to process.cwd().

var num = callsite.getLineNumber()

Inherited from V8 if callsite.sourcemap is undefined

If this function was defined in a script returns the current line number.

var num = callsite.getColumnNumber()

Inherited from V8 if callsite.sourcemap is undefined

If this function was defined in a script returns the current column number.

var str = callsite.getEvalOrigin()

Inherited from V8

If this function was created using a call to eval returns a CallSite object representing the location where eval was called.

Note that since Node.js v12.11.0, this function returns undefined unless eval was used.

var str = callsite.getModuleName()

Returns the name of the module if isModule() is true. Otherwise returns null.

var bool = callsite.isToplevel()

Inherited from V8

Is this a toplevel invocation, that is, is this the global object?

var bool = callsite.isEval()

Inherited from V8

Does this call take place in code defined by a call to eval?

var bool = callsite.isNative()

Inherited from V8

Is this call in native V8 code?

var bool = callsite.isConstructor()

Inherited from V8

Is this a constructor call?

var bool = callsite.isApp()

Is this inside the app? (i.e. not native, not node code and not a module inside the node_modules directory)

var bool = callsite.isModule()

Is this inside the node_modules directory?

var bool = callsite.isNode()

Is this inside node core?

callsite.sourceContext([lines, ]callback)

Get the source code surrounding the call site line.

If the callsite is a node core call site, the callback will be called with an error.

Arguments:

  • lines - Total number of lines of soruce context to be loaded with the call site line in the center (default: 5)
  • callback - called when the source context have been loaded with an optional error object as the first argument and a source context object as the 2nd

Source Context

The source context objects provided by callsite.sourceContext contains the following properties:

  • pre - The lines before the main callsite line
  • line - The main callsite line
  • post - The lines after the main callsite line

Troubleshooting

To enable debug mode, set the environment variable DEBUG=stackman.

Acknowledgements

This project was kindly sponsored by Elastic.

License

MIT

stackman's People

Contributors

rexxars avatar watson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stackman's Issues

Stackman does not resolve filename (path) of sourcemap generated by webpack

Description of the issue

Stackman generate a wrong filepath when the sourcemap is generated with Webpack and using webpack:// URLs.

For example, the error triggered when using elastic-apm-node with Webpack & Typescript:

[...]
this.apmAgent.captureError(err);
[...]
error while getting callsite source context: ENOENT: no such file or directory, open '/home/kerwan/workspace/kmp-agency/kmp-flow-launcher/dist/webpack:/boilerplate-node-typescript/src/launchers/webhooks/webhook.launcher.ts'
error while getting callsite source context: ENOENT: no such file or directory, open '/home/kerwan/workspace/kmp-agency/kmp-flow-launcher/dist/webpack:/boilerplate-node-typescript/src/launchers/webhooks/abstract-webhook.launcher.ts'
error while getting callsite source context: ENOENT: no such file or directory, open '/home/kerwan/workspace/kmp-agency/kmp-flow-launcher/dist/webpack:/boilerplate-node-typescript/src/services/webpack:/boilerplate-node-typescript/src/app.ts'
error while getting callsite source context: ENOENT: no such file or directory, open '/home/kerwan/workspace/kmp-agency/kmp-flow-launcher/dist/webpack:/boilerplate-node-typescript/src/services/webpack:/boilerplate-node-typescript/src/app.ts'

For example, the sourcemap URL for webpack://boilerplate-node-typescript/./src/launchers/abstract-launcher.ts
is resolved to /home/kerwan/workspace/kmp-agency/kmp-flow-launcher/dist/webpack:/boilerplate-node-typescript/src/launchers/webhooks/webhook.launcher.ts
when it should be /home/kerwan/workspace/kmp-agency/kmp-flow-launcher/src/launchers/webhooks/webhook.launcher.ts.

Steps to reproduce

Setup a project with webpack, typescript and ts-loader with the following configuration:

package.json:

{
  [...]
  "devDependencies": {
    "ts-loader": "^8.0.11",
    "typescript": "^4.1.2",
    "webpack": "^5.10.0",
    "webpack-cli": "^4.2.0",
  },
  "dependencies": {
    "stackman": "^4.0.1"
  }
  [...]
}

webpack.config.js:

const path = require('path')

const {NODE_ENV} = process.env

module.exports = {
  mode: NODE_ENV,
  target: 'node',
  devtool: 'source-map',
  entry: path.resolve(__dirname, 'src/app.ts'),
  output: {
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.ts$/,
        include: /src/,
        loader: 'ts-loader'
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.js', '.json']
  },
}

With this configuration it will generate in the folder dist the file main.js and the sourcemap main.js.map.

Workaround

Temporarily, we have changed the devtool config in webpack to use relative paths without the namespace:

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    devtoolFallbackModuleFilenameTemplate: '../[resource-path]',
    devtoolModuleFilenameTemplate: '../[resource-path]'
  },
}

Long stack traces

When using stackman with long stacktraces it gives empty frames:

{ properties: {}, frames: [] }
var Promise = require('bluebird');
var stackman = require('stackman')();

Promise.longStackTraces();
Promise.resolve().then(function outer() {
    return Promise.resolve().then(function inner() {
        return Promise.resolve().then(function evenMoreInner() {
            a.b.c.d()
        }).catch(function catcher(e) {
            stackman(e, function (stack) {
                console.log(stack);
            });
        });
    });
});

`properties` throws `RangeError: Invalid time value` when the error object has field of type `Date` with value `Invalid Date`

If you call the properties method on an error object that has a field of type Date with a value Invalid Date, it tries to call the toISOString method of this object and throws an error: RangeError: Invalid time value.

To reproduce this, do the following:

var stackman = require('stackman')()

var err = new Error('Oops!')
err.number = Number.NaN;
err.date = new Date('invalid')

var props = stackman.properties(err)
console.log(props)

If you comment out err.date = new Date('invalid') or change the value to a valid date, the code works.

babel-register support

Hi, I tried using this tool with babel-register, but the source lines are coming back incorrectly. I tried using both values for sourcemap property with the same weird results. Any idea how to get this to work? Thanks.

Note: new Error().stack displays the correct lines/cols even with babel-register. I think that's because babel patches stacks.

Provide CHANGELOG or release notes

It'll be great to have resource to learn on what changes were published.

It's especially helpful across major upgrades, currently (without inspecting the code changes) it's not clear what breaking changes were introduced with v4

Async function error stack trace

Hi, I'm using Elastic APM in our node projects and I found it lose stack trace when an async function throws an error. In the source code I found the error stack is first handled by stackman, and the frames will lose "From previous event" part. I found if frames length is 0, error will be handled by error-stack-parser, and the error stack can be parsed correctly.
So can the stckman also add this feature? Async functions are very commonly in node.js.
code like this:

const id = someAsyncFunction();
anotherFunction(id);

error like this:

nodejs.unhandledRejectionError: Invalid value Promise { <pending> }
    at Object.escape (/node_modules/sequelize/lib/sql-string.js:65:11)
    ......
    at processImmediate (internal/timers.js:439:21)
From previous event:
    at Function.findAll (/node_modules/sequelize/lib/model.js:1755:8)
    ......
name: "unhandledRejectionError"

Sync API

This module looks like just what I need (getting context for the nearest call site outside by lib) but being async makes it inapplicable to my use case where I'm wrapping synchronous functions with the possibility of them throwing an error (in which case I'd like to show context around the callsites).

Plugging into Winston

This is terrific, but I'd like to plug this into Winston so we can log caller's file, function/method name and line number from source map. First, how efficient is this and is there a way to get a synchronous response so as to return a formatted string back to Winston? Is there a more efficient method for our purposes than to use stackman? Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.