Sentry-javascript: Node suggestion: log errors in `unhandledRejection`

Created on 19 Feb 2019  ·  41Comments  ·  Source: getsentry/sentry-javascript

Package + Version

  • [ ] @sentry/browser
  • [x] @sentry/node
  • [ ] raven-js
  • [ ] raven-node _(raven for node)_
  • [ ] other:

Version:

4.6.1

Description

Out of the box, Node will log unhandled promise rejections. However, after initialising Sentry, these logs will disappear. This happens because Node won't log rejections if an unhandledRejection handler exists, and Sentry registers such a handler.

I would like to suggest that Sentry adds logging to its handler, to provide parity with the experience provided out of the box. At the very least, the docs should mention this behaviour and the need to add manually an additional handler to restore logging.

Most helpful comment

I still find it's weird that uncaughtException and unhandledRejection are handled differently. Sentry restores logging for uncaughtException—why doesn't it do the same for unhandledRejection? Users shouldn't have to remember to use that Node flag. 🤔

All 41 comments

Sounds good, however, I'm not sure if this should be behind our debug: true flag.
In the end, you are consciously attaching error handlers, which redirects error stream to the Sentry.

I'm not sure either.

Enabling Sentry doesn't have the effect of disabling logging for uncaught exceptions, so maybe the same should be true for unhandled promise rejections. (Although this difference in behaviour is more of an inconsistency in Node than Sentry.)

When I enable Sentry I understand it's reporting my errors, but I tend to think of console logging as a separate thing, especially in dev. 🤔

We don't modify default logging behavior, and for uncaught exceptions it's node itself what's triggering a log call.

I think the best solution here would be to just make a note about it in the docs and leave

process.on('unhandledRejection', (reason, p) => {
  console.log('Unhandled Rejection at:', p, 'reason:', reason);
});

snippet in case someone wants to re-enable the warning.

Docs would suffice. At least then Sentry isn't doing anything special. It's really Node that's the problem here.

or uncaught exceptions it's node itself what's triggering a log call.

On second thoughts, I'm not sure that's true.

Sentry registers an uncaughtException handler which disables the default Node behaviour (log + exit).

The handler (defaultOnFatalError) triggers its own log call: https://github.com/getsentry/sentry-javascript/blob/4.6.3/packages/node/src/handlers.ts#L282.

If we're "restoring" logging for uncaught exceptions, I believe we should do the same thing for unhandled promise rejections.

Can we re-open this? As per my last comment, I now believe Sentry should restore logging for unhandled promise rejections, like it does for uncaught exceptions.

I found this issue because I was starting to use Sentry and wondered if I had made a mistake in my code since only uncaughtException was in my log and not unhandledRejection. It would make a lot of sense for these two cases to be handled the same way.

So I would say either log both or log neither.

I found a better way to handle this, than copying code from Node's core.

λ: node --unhandled-rejections=warn app.js

Also made it obvious by including on the main docs page https://github.com/getsentry/sentry-docs/pull/1099

I still find it's weird that uncaughtException and unhandledRejection are handled differently. Sentry restores logging for uncaughtException—why doesn't it do the same for unhandledRejection? Users shouldn't have to remember to use that Node flag. 🤔

why doesn't it do the same for unhandledRejection

Because one is critical (unhandledException kills the process), and one is informational (thus it's a warning, not error).

If we reverse the order and emit the warning by default, we'll break CLI behavior, by emitting them despite --unhandled-rejections being set to none. Right now everything works as expected according to official node documentation. This change _would_ make it non-standard.

Once Node decides to make unhandledRejection also kill a process (which they say for 4 versions now :P), we'll make the change to be in-line with the official spec as well.

If we reverse the order and emit the warning by default, we'll break CLI behavior

@kamilogorek, but when running with node it does log unhandledRejection to console. So I don't know what the official node documentation says, but at least that's the behavior I notice.

@freeall only if you don't attach unhandledRejection handler. If you run the code below, even without the SDK, it still won't log, thus you have to be aware of what code are you running.

process.on('unhandledRejection', () => `¯\_(ツ)_/¯`);

And we state very clear here that we do that: https://docs.sentry.io/platforms/node/default-integrations/

System integrations are integrations enabled by default that combine into the standard library or the interpreter itself. They’re documented so you can see what they do and that they can be disabled if they cause issues.
OnUnhandledRejection
This integration attaches global unhandled rejection handlers.

I just don't want to change node's behavior, that's it. And behavior is: "if there's a listener, don't emit. if you still want the warning, use flag." - and this is exactly what we do.

@kamilogorek I understand where you're coming from. But I think users of Sentry would expect Sentry to not alter the behavior of their program.

If I have an unhandledRejection without Sentry I see it in my console.
If I have an unhandledRejection with Sentry I don't see it in my console.

I personally don't like that Sentry alters the behavior.

But that's how Node.js is designed ¯_(ツ)_/¯
If you add handler, warning is gone. Our SDK adds handler, because it's the only way to catch unhandled errors, which is the main purpose of the SDK.

You are of course right about how node is designed. When you attach a handler the warning is gone.
What people is asking is that you mimic the default behavior of node and log it to the console. A changed behavior is not what you expect from a tool like Sentry

Anyway, it seems you're set on this behavior so no point in keep the discussion going. But thanks for taking the time to answer :)

@freeall thanks as well, it's always good to see the both sides :)

Just to clarify: when enabling Sentry, the behaviour of unhandledException (exit + log) is preserved, but the behaviour of unhandledRejection (log) is not:

|handler|logs|exits|
|-|-|-|
|unhandledException default|yes|yes|
|unhandledException Sentry|yes|yes|
|unhandledRejection default|yes|no|
|unhandledRejection Sentry|no|no|

Right now everything works as expected according to official node documentation.

"As expected" here is assuming the user understands that Sentry is registering a listener for unhandledRejection. That's an implementation detail that users shouldn't have to worry about.

I do see your point though. Sentry should also respect --unhandled-rejections, which it wouldn't be doing if the flag was set to none and Sentry was continuing to log.

@freeall comment sums this up pretty well:

I think users of Sentry would expect Sentry to not alter the behavior of their program.

Uncaught promise rejections are no "second class errors". They can and do cause apps to break just like normal errors do.

It seems like several users have run into this problem (including me), and more will run into it in the future.

So in summary: Sentry silences errors from the console. And I think it is obvious how this is confusing, and probably not intended by most sentry users. They don't install sentry to have some subset of errors silenced from console.

So the reasoning (by @kamilogorek )

that's how Node.js is designed

Is a bit cryptic to me. How does this dictate the way sentry should behave?

Should we expect users to know about:

a) sentrys inner workings (registers event handlers)

b) node inner workings (adding handlers causes warnings to disappear)

And if they don't, well they should?

Of course you are right in saying "well, the user should know, what the code does under the hood", but the reality looks different. And you have the opportunity here to increase ease of use of sentry, or not.

It's ok to say "we don't care about this particular issue", but painting it in a "it's not a bug, it's a feature" kind of way seems insincere.

TLDR: IMOH If you want to increase ease of use, and reduce problems for newbie users, then this should be fixed.

@OliverJAsh , @freeall
What solutions did you use for this problem?

@schumannd I've added a snippet from how I load it. And I agree with you that the "it's not a bug, it's a feature" answer doesn't seem fulfilling. I fear that many programmers won't catch some bugs in their program because Sentry eats them. To me, the first priority of a tool like Sentry should be to catch bugs, and not to create them.

...
if (isUsingSentry) {
  // Log to console because Sentry overwrites standard behavior. https://github.com/getsentry/sentry-javascript/issues/1909.
  // Note that it doesn't overwrite for uncaughtException.
  process.on('unhandledRejection', console.error)
}
...

As I am using sentry in react native, it seems like I would need to do some hacky things to fix this issue. Is anyone using React Native and solving this problem in a different way?

@OliverJAsh, @kamilogorek
Can we please have this reopened and fixed?

Sentry absolutely shouldn't mess with how errors are logged in the console - and this is not just a problem with Node, as console logging is suppressed in the browser too.

It's super annoying when trying to debug things, and since you didn't bother mentioning this in the docs for JavaScript, I actually thought I had no errors when I looked in the console while testing a staging build, and therefore didn't realize it had errors, until after it was deployed to production. That's a really shitty user experience for an error reporting service, and one you need to fix if you want happy customers.

And as said before, promise rejections are not second-class errors - they can be just as fatal as any other unhandled error, and shouldn't be suppressed in any way.

So, a bit of debugging reveals the reason console logging is suppressed:

image

Sentry assigns a function to window.onunhandledrejection, and as we see here, that function returns false, thereby explicitly suppressing console logging. So yeah, Sentry _does_ change the default behavior - that's not cool.

Luckily, it stores a reference to any existing function, and calls that if it exists.
So, the hacky workaround to re-enable console logging, is to add this line, before initializing Sentry:

window.onunhandledrejection = () => true;

Now, please fix this, so we can have the default behavior without such pointless hacks 🙂

@schumannd please reopen issue

@thomas-darling I agree with the browser change, it should return true for promises as well and I can change that.

However, for node, I'm still not convinced because of one reason. It ties the code to current Node implementation. If we'll copy the internals instead of relying on flags, and promise rejection behavior will change in v14, we'll have to detect what version of node we are in and act accordingly.
It doesn't matter what we return from the listener, as internally node just checks for the listeners array and emit warning only if there're no listeners at all, and this detection cannot be modified - https://github.com/nodejs/node/blob/7cf6f9e964aa00772965391c23acda6d71972a9a/lib/internal/process/promises.js#L163-L216

Sounds good, regarding the browser change :+1:

As for Node, if you don't fix logging in Sentry, you basically just force all your users to do it themselves - with the added risk that some will do it wrong, and some will not even realize they need it, until after they get bitten by a production bug, like I did. That's not a good developer experience...

@thomas-darling how would you like to get it fixed? Reproduce the same code that's inside node's code?

At the very top of our docs there's a very visible note what to do in order to get default console logging - https://docs.sentry.io/platforms/node/

image

I do see your point - having to replicate the node behavior would be a potential maintenance issue, and it does help that this can, for node, be solved with a simple command line flag.

But if you don't want to replicate the node behavior, then at least log a warning to the console when that flag is not specified, so users are aware that errors are being suppressed, and how to avoid that.

This becomes even more important in the next version of node, where unhandled rejections will, by default, crash the process - which, as I understand it, won't happen when Sentry adds its handler.
Users who rely on that new default behavior in node, could potentially be in for a nasty surprise, if they later install Sentry and their process then suddenly continues, despite a fatal error having occurred.
That's the kind of thing that could lead to data loss or other disasters.

As I see it there a few options:

  1. Replicate Node.js's way of doing it
  2. Just write to console.error when there's an unhandled rejection
  3. Suppress the error so the developer will never see it

I think option 1 or 2 both seem fine. Your customer sees the error and can fix it.
What you absolutely shouldn't do is option 3, where your customer doesn't see errors and Sentry causes errors to go in to production (oh, but the irony for a error reporting tool). This is the current behavior and this should really stop! Sentry should help me catch errors, not make it worse.

Even if you choose option 2, at least developers will see the rejection and will notice that they wanted a different behavior (like a crash), and can implement that. But without knowing there even was a rejection, they can't do much about it.

This should do the work. https://github.com/getsentry/sentry-javascript/pull/2312
I didn't add a way to add your own callback, as writing code below would have the exact same effect:

```js
Sentry.init({
integrations: [
new Sentry.Integrations.OnUnhandledRejection({
mode: 'none'
})
]
});

process.on('unhandledRejection', (reason) => {
// your callback
})

for me that results in TypeError: undefined is not a constructor. Might be, because I am now using the @sentry/react-native package. Btw does that package have the same issue?

@schumannd @sentry/react-native doesn't use @sentry/node, so it doesn't have this integration. For that, you'll just need to update a version once we release sentry/browser and it'll work just fine (as change to return true from handlers is default and non-configurable).

@kamilogorek looks good to me 👍

Thank you for doing this! Would you be able to ping here once this is released?

@OliverJAsh ping :)

Just to check I understand correctly: if I use Node's --unhandled-rejections=strict flag, Node will raise the unhandled rejection as an exception, and then Sentry will intercept that exception and report it? That's what I think I'm seeing.

I ask because when I tried enabling --unhandled-rejections=strict, it seemed that the OnUnhandledRejections integration had no effect—the event listener was never called.

It would be great if we could add some docs around this!

Docs PR is already in progress https://github.com/getsentry/sentry-docs/pull/1351/

@OliverJAsh this change has nothing to do with the cli flag. It's behavior is untouched. The thing that changed is that OnUnhandledRejection integration got a new option that allows you to make it behave like the cli flag.

Sentry.init({
  integrations: [
    new Sentry.Integrations.OnUnhandledRejection({
      mode: 'none'
    })
  ]
});

is (conceptually) same as --unhandled-rejection=none and the same goes for warn and strict.
When you use warn (which is the default now), it'll log the warning and error itself, but the process will be kept alive.
When you use strict, it'll log, capture the event, flush it (wait until its delivered) and kill the process with exit code 1.

That makes sense. I understand the integration doesn't change the behaviour of the Node flag. However, can I just check I correctly understand how Sentry behaves (outside of this integration) with regards to the Node flag?

Just to check I understand correctly: if I use Node's --unhandled-rejections=strict flag, Node will raise the unhandled rejection as an exception, and then Sentry will intercept that exception and report it? That's what I think I'm seeing.

@schumannd FYI, this morning we released @sentry/react-native 1.10.0 [EDIT: whoops, should be 1.1.0], which updates its dependency to use the latest version of @sentry/browser (which includes the return-true-instead-of-false fix mentioned above).

@lobsterkatie the latest release of @sentry/react-native seems to be 1.3.7..

So trying to install 1.10.0 does not work. How do I get the fix?

@schumannd @lobsterkatie meant 1.1.0, as this is when we updated to 5.9.0 of @sentry/browser. Handler option that set the level of logging should work just fine in the recent version of @sentry/react-native as well.

Was this page helpful?
0 / 5 - 0 ratings