-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RNMobile] Add error boundary components and exception logging #59221
Conversation
Size Change: -230 B (0%) Total Size: 1.71 MB
ℹ️ View Unchanged
|
The parameter `framesToPop` was dropped in RN version 0.62 (facebook/react-native@8bc02fd).
}; | ||
} | ||
|
||
static getDerivedStateFromError( error ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's more information about this static function: https://react.dev/reference/react/Component#static-getderivedstatefromerror
return { error }; | ||
} | ||
|
||
componentDidCatch( error ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's more information about this component function: https://react.dev/reference/react/Component#componentdidcatch
error_boundary_level: 'block', | ||
block_name: blockName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As additional context to debug the exception, we'll attach at what level the exception happened and the block's name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As shared in inline comments, most of this logic is based on React Native and JavaScript Sentry SDKs.
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @crazytonyli. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
} | ||
|
||
self.delegate?.gutenbergDidRequestLogException(exception) { | ||
callback([true]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering the argument (a.k.a the wasSent
argument in the JS function) is always true, what do you think removing it instead?
In reality, we can't really know if an even is actually being sent. We know it's received by Sentry SDK, but it's totally up to Sentry when it comes to actually sending the event to the server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's true that wasSent
won't tell if the event was received by Sentry. However, this parameter can be false
if the rawException
argument is malformed and can't be parsed to a GutenbergJSException
and hance won't be sent to the Crash logging service. Maybe we should rename it to clarify the purpose of the parameter to wasProcessed
or similar, WDYT?
gutenberg/packages/react-native-bridge/ios/RNReactNativeGutenbergBridge.swift
Lines 432 to 435 in c017e12
guard let exception = GutenbergJSException(from: rawException) else { | |
callback([false]) | |
return | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe we could consider that wasSent
is related to sending the exception to the Crash logging service, not specifically that was actually sent and processed by Sentry. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must have missed the callback([false])
line above. The current code looks good to me, because there are different argument value used. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not so sure we need to inform the JS side that the rawException is malformed. Right now, all it does is log the error, which seems we could do here in the bridge code.
I can see using a callback if the RN component will change given something failing down the call stack. The boundary component is already handling the error gracefully; everything else feels like "call and move on", much like how we call into the Sentry SDK without really knowing what happens next.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the parameter returned in the callback could be dropped. However, we need the callback to ensure that unhandled exceptions are logged. Following this code, if we call defaultHandler
right after calling the bridge function logException
, the exception is not logged because the undhandled exception leads to crashing the app before the Sentry event is sent. This is the main reason why I introduced the callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following this code, if we call defaultHandler right after calling the bridge function logException,
Ah, okay. Thanks for pointing that out. I wasn't sure where the defaultHandler
came from, but I see it's a ReactNative global, so we need to intercept that. Makes sense now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the undhandled exception leads to crashing the app
@fluiddot By "crashing the app", do you mean the iOS app, or the Gutenberg Editor? Out of curiosity, if an unexpected JS exception happens, it'd only affect the running JavaScriptCore VM, not the host app, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The iOS app. By default, React Native causes a native crash for unhandled exceptions like other exceptions that might happen in the app.
It’s true that technically it only affects the JS runtime, but currently it behaves this way. In the future, we could change this behavior to closing the editor (i.e. a soft crash of the editor) instead of crashing and present post recovery options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation!
* Add logException to Android bridge * Add logException to Android demo app * Add GutenbergJsException data class * Update logException to use data class and return bool to callback * integrate exception handling in the glue code * remove past tense 'did' * Refactor exception processing * lint * Add `logException` bridge function in demo app * Update stacktrace parsing on Android --------- Co-authored-by: Carlos Garcia <fluiddot@gmail.com>
@osullivanchris We'd appreciate your input regarding the error state when the editor encounters a non-recoverable exception (see attached screenshots to the PR). For reference, I used a similar approach as we have on the web. Thanks 🙇 ! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent work on this @fluiddot !
…ress#59221) * Add parse function to send exceptions over the RN bridge * Add RN bridge function to log exceptions to the host app * Add error boundary to blocks * Add error boundary at editor level * Log exceptions from error boundary components * Remove `in_app` stack trace parameter * Remove `getPopSize` The parameter `framesToPop` was dropped in RN version 0.62 (facebook/react-native@8bc02fd). * Simplify functions from `parseException` * Rename exception tag to `gutenberg_mobile_version` * Add `gutenbergDidRequestLogException` bridge function to demo app * Trigger callback upon sending JS exception * Format `RNReactNativeGutenbergBridge` * Update inline comments * Merge `reverseEntries` logic into `parseStacktrace` * Set second param of `parseException` (context and tags) as optional * Add unit tests of `parseException` * Add inline comment to `getReactNativeContext` * Add typing for JavaScript exception in bridge * Fix param type in `gutenbergDidRequestLogException` * Update `gutenbergDidRequestLogException` implementation of demo app * Rename `JSException` to avoid disambiguation with Crash Logging service * Add `actions` prop to `Warning` component * Allow extra styles in `Warning` component * Implement copy buttons in Error boundary component * Fix style import path in error boundary component * Change GutenbergJSException `function` param to non-optional * Rename JSException param to `message` * Rename GutenbergJSException param to `message` * Update `react-native-editor` changelog * Update unit tests of `parseException` * [RNMobile] Add error boundry handling for Android (WordPress#59385) * Add logException to Android bridge * Add logException to Android demo app * Add GutenbergJsException data class * Update logException to use data class and return bool to callback * integrate exception handling in the glue code * remove past tense 'did' * Refactor exception processing * lint * Add `logException` bridge function in demo app * Update stacktrace parsing on Android --------- Co-authored-by: Carlos Garcia <fluiddot@gmail.com> --------- Co-authored-by: Jason Johnston <jhnstn@users.noreply.github.com>
Related PRs:
What?
Enables error boundary to prevent editor crashes at editor and block levels. Additionally, it implements a bridge function to log exceptions to host apps.
Why?
It will prevent the app from crashing from exceptions that occur in the editor, as well as provide detailed information of exceptions to the host apps.
How?
Testing Instructions
Follow testing instructions from wordpress-mobile/gutenberg-mobile#6655.
Testing Instructions for Keyboard
N/A
Screenshots or screencast
Block-level error boundary
Editor-level error boundary
Web version: