beginner-corp / slack

:tada:✨ Slack API client for Node and browsers.

Home Page:https://www.npmjs.com/package/slack

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

proposal: more helpful stack traces

danprince opened this issue · comments

We've got a number of complex workflows that call out to multiple Slack APIs and we're finding it tricky to work with the stack traces that we get from this library.

Error: channel_not_found
    at _res (/kumu/node_modules/slack/src/_exec.js:50:17)
    at IncomingMessage.__end (/kumu/node_modules/tiny-json-http/_write.js:61:9)
    at emitNone (events.js:110:20)
    at IncomingMessage.emit (events.js:207:7)
    at endReadableNT (_stream_readable.js:1047:12)
    at _combinedTickCallback (internal/process/next_tick.js:102:11)
    at process._tickDomainCallback (internal/process/next_tick.js:198:9)

The response message is a useful component, but I have no idea which of the API calls caused it.

We're using native async/await all over our codebase and most of our asynchronous call stacks end up looking synchronous. It's only when we call out to code that uses promises or callbacks, that the stack traces disappear.

However, converting this library to async/await would be a load of effort and it'd be a shame to no longer support Node 4.X.X.

Tentative solution incoming, based on what I've been working with locally.

I've slightly changed the implementation of exec to grab the current stack trace when it is called. Then use it to overwrite the less useful stack traces.

module.exports = function exec(url, form, callback) {
  var trace = new Error()

  if (!callback) {
    var pfy = promisify(_exec)
    return pfy(url, form).catch(function(err) {
      err.stack = trace.stack
      throw err
    })
  }
  else {
    _exec(url, form, function(err, res) {
      if (err) {
        err.stack = trace.stack
        callback(err)
      }
      else {
        callback(null, res)
      }
    })
  }
}

And here's the result:

Error
    at Object.exec (/kumu/server/node_modules/slack/src/_exec.js:10:15)
    at slackRateLimitRetry_1.slackRateLimitRetry (/kumu/server/build/models/Channel.js:103:101)
    at Object.slackRateLimitRetry (/kumu/server/build/utils/slackRateLimitRetry.js:12:22)
    at Channel.syncMembers (/kumu/server/build/models/Channel.js:103:55)
    at Function.syncPrivateChannels (/kumu/server/build/models/Channel.js:78:31)
    at <anonymous>

Looks like error.stack is the message that the console prints when it throws, which means error.message is lost, but that can be fixed with:

err.stack = trace.stack.replace(/^Error/, `Error: ${err.message}`)

I'm a bit conflicted as to whether this would be a good pattern for the library to follow. I don't like this kind of magic in general, because it usually ends up making stuff more confusing.

On the other hand, it's super annoying to see production errors coming in with the first form of stack trace, having no idea which call caused it (we've got ~50 API call sites across our application).

Some options:

  1. Keep this out of the library, we'll maintain a private fork
  2. Add it to the library as opt-in with an environment variable
  3. Add it to the library as out-out with an environment variable
  4. Save the original stack as a new property on the error (we'll sort it in our error middleware)
  5. Halfway house: provide api context in errors Error: (conversation.members) channel_not_found

@brianleroux Let me know what you think is best and I can wrap this up, or get started on a PR.

I like this. Lets add it. Error is currently and likely for a long time unspecified so there may be future work but that'd be true if we didn't make things better. Lets go for it!

Merged in #123

fyi published in 11.0.1 with some minor updates