earlyjs: Integrate c++ pipeline with console.error

Summary:
## Changes
If the c++ pipeline is active:

If someone calls console.error:
- The c++ pipeline will report it as a soft error

If someone reports an error:
- The c++ pipeline will log it via console.error

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D64506069

fbshipit-source-id: 293cd1774f19ee03d7eb61572b7b1f03a47a4756
This commit is contained in:
Ramanpreet Nara
2024-10-29 14:24:27 -07:00
committed by Facebook GitHub Bot
parent 5b53c07329
commit cc6f75bb75
4 changed files with 101 additions and 22 deletions
+25 -11
View File
@@ -142,7 +142,11 @@ let inExceptionHandler = false;
*/
function handleException(e: mixed, isFatal: boolean) {
// TODO(T196834299): We should really use a c++ turbomodule for this
if (!global.RN$handleException || !global.RN$handleException(e, isFatal)) {
const reportToConsole = true;
if (
!global.RN$handleException ||
!global.RN$handleException(e, isFatal, reportToConsole)
) {
let error: Error;
if (e instanceof Error) {
error = e;
@@ -158,7 +162,7 @@ function handleException(e: mixed, isFatal: boolean) {
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
* parameters */
// $FlowFixMe[incompatible-call]
reportException(error, isFatal, /*reportToConsole*/ true);
reportException(error, isFatal, reportToConsole);
} finally {
inExceptionHandler = false;
}
@@ -173,7 +177,10 @@ function reactConsoleErrorHandler(...args) {
if (!console.reportErrorsAsExceptions) {
return;
}
if (inExceptionHandler) {
if (
inExceptionHandler ||
(global.RN$inExceptionHandler && global.RN$inExceptionHandler())
) {
// The fundamental trick here is that are multiple entry point to logging errors:
// (see D19743075 for more background)
//
@@ -227,14 +234,21 @@ function reactConsoleErrorHandler(...args) {
error.name = 'console.error';
}
reportException(
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
* parameters */
// $FlowFixMe[incompatible-call]
error,
false, // isFatal
false, // reportToConsole
);
const isFatal = false;
const reportToConsole = false;
if (
!global.RN$handleException ||
!global.RN$handleException(error, isFatal, reportToConsole)
) {
reportException(
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
* parameters */
// $FlowFixMe[incompatible-call]
error,
isFatal,
reportToConsole,
);
}
}
/**
@@ -62,6 +62,22 @@ jsi::Object wrapInErrorIfNecessary(
: Error.callAsConstructor(runtime, value).getObject(runtime);
return error;
}
class SetFalseOnDestruct {
std::shared_ptr<bool> _value;
public:
SetFalseOnDestruct(const SetFalseOnDestruct&) = delete;
SetFalseOnDestruct& operator=(const SetFalseOnDestruct&) = delete;
SetFalseOnDestruct(SetFalseOnDestruct&&) = delete;
SetFalseOnDestruct& operator=(SetFalseOnDestruct&&) = delete;
explicit SetFalseOnDestruct(std::shared_ptr<bool> value)
: _value(std::move(value)) {}
~SetFalseOnDestruct() {
*_value = false;
}
};
} // namespace
namespace facebook::react {
@@ -162,7 +178,7 @@ std::ostream& operator<<(
JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
: _onJsError(std::move(onJsError)),
_hasHandledFatalError(false){
_inErrorHandler(std::make_shared<bool>(false)){
};
@@ -171,7 +187,8 @@ JsErrorHandler::~JsErrorHandler() {}
void JsErrorHandler::handleError(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal) {
bool isFatal,
bool logToConsole) {
// TODO: Current error parsing works and is stable. Can investigate using
// REGEX_HERMES to get additional Hermes data, though it requires JS setup
if (_isRuntimeReady) {
@@ -191,13 +208,17 @@ void JsErrorHandler::handleError(
}
}
emitError(runtime, error, isFatal);
handleErrorWithCppPipeline(runtime, error, isFatal, logToConsole);
}
void JsErrorHandler::emitError(
void JsErrorHandler::handleErrorWithCppPipeline(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal) {
bool isFatal,
bool logToConsole) {
*_inErrorHandler = true;
SetFalseOnDestruct temp{_inErrorHandler};
auto message = error.getMessage();
auto errorObj = wrapInErrorIfNecessary(runtime, error.value());
auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
@@ -278,6 +299,14 @@ void JsErrorHandler::emitError(
isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError"));
data.setProperty(runtime, "isComponentError", isComponentError);
if (logToConsole) {
auto console = runtime.global().getPropertyAsObject(runtime, "console");
auto errorFn = console.getPropertyAsFunction(runtime, "error");
auto finalMessage =
jsi::String::createFromUtf8(runtime, parsedError.message);
errorFn.callWithThis(runtime, console, finalMessage);
}
std::shared_ptr<bool> shouldPreventDefault = std::make_shared<bool>(false);
auto preventDefault = jsi::Function::createFromHostFunction(
runtime,
@@ -330,4 +359,8 @@ void JsErrorHandler::notifyOfFatalError() {
_hasHandledFatalError = true;
}
bool JsErrorHandler::inErrorHandler() {
return *_inErrorHandler;
}
} // namespace facebook::react
@@ -43,13 +43,18 @@ class JsErrorHandler {
explicit JsErrorHandler(OnJsError onJsError);
~JsErrorHandler();
void handleError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
void handleError(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal,
bool logToConsole = true);
bool hasHandledFatalError();
void registerErrorListener(
const std::function<void(jsi::Runtime&, jsi::Value)>& listener);
void setRuntimeReady();
bool isRuntimeReady();
void notifyOfFatalError();
bool inErrorHandler();
private:
/**
@@ -60,11 +65,16 @@ class JsErrorHandler {
* teardown get reported properly.
**/
OnJsError _onJsError;
bool _hasHandledFatalError;
bool _hasHandledFatalError{};
bool _isRuntimeReady{};
std::shared_ptr<bool> _inErrorHandler;
std::vector<std::function<void(jsi::Runtime&, jsi::Value)>> _errorListeners;
void emitError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
void handleErrorWithCppPipeline(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal,
bool logToConsole);
};
} // namespace facebook::react
@@ -409,6 +409,21 @@ void ReactInstance::initializeRuntime(
defineReactInstanceFlags(runtime, options);
defineReadOnlyGlobal(
runtime,
"RN$inExceptionHandler",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "inExceptionHandler"),
0,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& /*runtime*/,
const jsi::Value& /*unused*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
return jsErrorHandler->inErrorHandler();
}));
// TODO(T196834299): We should really use a C++ turbomodule for this
defineReadOnlyGlobal(
runtime,
@@ -416,7 +431,7 @@ void ReactInstance::initializeRuntime(
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "handleException"),
2,
3,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& runtime,
const jsi::Value& /*unused*/,
@@ -425,7 +440,7 @@ void ReactInstance::initializeRuntime(
if (count < 2) {
throw jsi::JSError(
runtime,
"handleException requires 2 arguments: error, isFatal");
"handleException requires 3 arguments: error, isFatal, logToConsole (optional)");
}
auto isFatal = isTruthy(runtime, args[1]);
@@ -439,7 +454,14 @@ void ReactInstance::initializeRuntime(
auto jsError =
jsi::JSError(runtime, jsi::Value(runtime, args[0]));
jsErrorHandler->handleError(runtime, jsError, isFatal);
if (count == 2) {
jsErrorHandler->handleError(runtime, jsError, isFatal);
} else {
auto logToConsole = isTruthy(runtime, args[2]);
jsErrorHandler->handleError(
runtime, jsError, isFatal, logToConsole);
}
return jsi::Value(true);
}));