The API I envision is similar to window.onerror but not necessarily the same. This would allow us to make RCTExceptionsManager.reportUnhandledException a lot smaller (we'd move much of the code into JS to keep the current behavior) and make it easier to do custom error handling.
@ide Hi. I would very much appreciate such an API. In the mean time is there a way to prevent an production app from crashing on any random JavaScript exception?
This would be great for adding something like Bugsnag to report exceptions when apps are in production.
Edit: In fact, I think ErrorUtils.setGlobalHandler((err, isFatal) => { ... }); might do the job here.
We're looking to log something anytime there is an error, so we can catch bugs early. This would be very useful.
Thank you for reporting this issue and appreciate your patience. We've notified the core team for an update on this issue. We're looking for a response within the next 30 days or the issue may be closed.
would be definitely useful!:+1:
@ide, what is the current recommended approach (or hack) for custom handling of global errors?
You could either hook into the native layer or monkeypatch the JS ExceptionsManager.
Is it a bad idea to set my own global error handler?
My scenario was to
Hi @ajwhite. Any luck completing the 2 points you mentioned?
var ErrorUtils = {
_inGuard: 0,
_globalHandler: null,
setGlobalHandler: function(fun) {
ErrorUtils._globalHandler = fun;
},
//...
'setGlobalHandler' would overwrite a lot of the RN behaviour (eg. RedBox in development). Understanding that we could just monkey patch _globalHandler, the "_" seems alarming. So maybe an API like addGlobalHandler, so that we could add custom handler without breaking the default behaviour and and without messing with "internal" variables ? @ide @satya164
In production mode I call setGlobalHandler with my own handler, which does the two things listed above -- logging to sentry & reloading the bridge. Seems to work just fine.
@ajwhite Cool. How do you reload the bridge? Would really appreciate it if you could share a gist or something.
@ajwhite And you have a DEV check I suppose?
Exactly @qbig
Basically
if (!__DEV__) {
ErrorUtils.setGlobalHandler(error => {
sentry.capture(error);
NativeModules.BridgeReloader.reload()
});
}
Then your BridgeReloader would be an objC module that simply calls [_bridge reload]
Or in an Android module, you just get the running activity, finish it, and restart it
@ReactMethod
public void reload() {
Activity activity = getCurrentActivity();
Intent intent = activity.getIntent();
activity.finish();
activity.startActivity(intent);
}
That's neat:) @ajwhite Thanks so much!
I guess you are not using the raven-js plugin? which does something like :
ErrorUtils.setGlobalHandler(Raven.captureException.bind(Raven));
But I don't like the fact that it is overwriting the default exception handler.
So I was considering something like
var defaultHandler = ErrorUtils._globalHandler;
ErrorUtils._globalHandler = function(...args){
defaultHandler(...args);
Raven.captureException(...args);
// other custom handler
};
Just took a read through the latest Raven source. The version I had used months back would report exceptions by loading an image.. That obviously doesn't work well for us here in React Native land. Their latest version looks like it has full support now though!
But I don't like the fact that it is overwriting the default exception handler.
That would work. I just re-read the source for the native error handler. It looks like it does two things:
__DEV__ modeI had no idea until now that it reloads the bridge by default. I don't know if this is a new development or not, but that's pretty cool. Unfortunately the application I work on is basically a "kiosk mode" type of thing running 24/7. I notice the handler has a maxRetries, which wouldn't work for me, so I'll likely bypass this as I am currently.
So keeping a reference to the original handler and calling it is probably a good thing in your example
@ide @satya164 is there any suggested way to add the handler ?
I think ErrorUtils.setGlobalHandler is the recommended way. Maybe the API should be changed to let you get access to the default handler.
@ide like a getter for "_globalHandler" ?
Yes, making it part of the public API.
var ErrorUtils = {
_inGuard: 0,
_globalHandler: null,
getGlobalHandler : function() {
return ErrorUtils._globalHandler;
},
setGlobalHandler: function(fun) {
ErrorUtils._globalHandler = fun;
},
reportError: function(error) {
ErrorUtils._globalHandler && ErrorUtils._globalHandler(error);
},
reportFatalError: function(error) {
ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true);
},
// ...
Would do ? I'd be more than happy to PR
@qbig Looks reasonable.
This issue appears to have been solved by #5575. Closing this issue out for now.
The ErrorUtils.setGlobalHandler works great when the app's process is attached to the debugger (regardless if in debug / release bundle): I see a red screen with the error message, and my own custom handler is called fine.
But how do I handle errors for production apps, where a debugger won't be attached? When a debugger isn't attached and a crash occurs, the app doesn't show a red screen and just closes the app and goes to the home screen. In this case, my custom handler isn't called at all.
(Specifically describing the flow for iOS bundled app here)
Hi, I'm fairly new to both RN and javascript. I've put the following into my code... It catches errors and warnings in an array before reporting them as normal to the console.
It sits at the top of my main.js file. It seems to work, but is this a terrible idea?
const orig_console_warn = console.warn;
const orig_console_error = console.error;
let errors = [];
let warnings = [];
console.warn = function() {
warnings.push(arguments);
orig_console_warn.apply(console, arguments);
};
console.error = function() {
errors.push(arguments);
orig_console_error.apply(console, arguments);
};
I then periodically clear the array and send the data to some reporting system.
@jlo1 Did you ever find a solution for non-debug mode?
I'm pretty sure that global.ErrorUtils.setGlobalHandler works in production mode too on Android, fyi.
What about iOS? Is there a platform-agnostic way to do this?
I ended up trapping console.log, console.info, console.warn and console.error and writing the data to an in-memory buffer that I can view in my app. I also send error and warn messages to a server that records the data so I can see error from my clients in one central place.
RCTSetFatalHandler(^(NSError *error) {
[Bugly reportError:error];
[rootView.bridge reload];
});
Most helpful comment
Exactly @qbig
Basically
Then your
BridgeReloaderwould be an objC module that simply calls[_bridge reload]Or in an Android module, you just get the running activity, finish it, and restart it