diff --git a/packages/react-native/ReactCxxPlatform/react/logging/DefaultLogger.cpp b/packages/react-native/ReactCxxPlatform/react/logging/DefaultLogger.cpp new file mode 100644 index 00000000000..4e2d9e3dc49 --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/logging/DefaultLogger.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "DefaultLogger.h" + +#include +#include +#include + +namespace facebook::react { + +Logger getDefaultLogger() { + return [](const std::string& message, unsigned int logLevel) { + static std::string tag = "[JavaScript]: "; + switch (logLevel) { + case ReactNativeLogLevelFatal: + case ReactNativeLogLevelError: + LOG(ERROR) << tag << message; + break; + case ReactNativeLogLevelWarning: + LOG(WARNING) << tag << message; + break; + case ReactNativeLogLevelInfo: + default: + LOG(INFO) << tag << message; + break; + } + }; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/logging/DefaultLogger.h b/packages/react-native/ReactCxxPlatform/react/logging/DefaultLogger.h new file mode 100644 index 00000000000..8820d17cd93 --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/logging/DefaultLogger.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::react { + +using Logger = + std::function; + +Logger getDefaultLogger(); + +} // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/logging/DefaultOnJsErrorHandler.h b/packages/react-native/ReactCxxPlatform/react/logging/DefaultOnJsErrorHandler.h new file mode 100644 index 00000000000..d2d88fc8cb2 --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/logging/DefaultOnJsErrorHandler.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::react { + +static inline JsErrorHandler::OnJsError getDefaultOnJsErrorFunc() { + return [](jsi::Runtime& /*runtime*/, + const JsErrorHandler::ProcessedError& error) { + LOG(ERROR) << "[onJsError called]: " << error << std::endl; + }; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/logging/LogOnce.cpp b/packages/react-native/ReactCxxPlatform/react/logging/LogOnce.cpp new file mode 100644 index 00000000000..c35fa9b3423 --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/logging/LogOnce.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "LogOnce.h" + +#include + +#include + +namespace facebook::react { + +LogOnceWrapper::~LogOnceWrapper() { + std::string message = str(); + + static std::unordered_set loggedMessages; + if (loggedMessages.find(message) != loggedMessages.end()) { + return; + } + + switch (severity_) { + case Severity::Info: + LOG(INFO) << message; + break; + case Severity::Warning: + LOG(WARNING) << message; + break; + case Severity::Error: + LOG(ERROR) << message; + break; + case Severity::Fatal: + default: + LOG(FATAL) << message; + break; + } + + loggedMessages.insert(message); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/logging/LogOnce.h b/packages/react-native/ReactCxxPlatform/react/logging/LogOnce.h new file mode 100644 index 00000000000..ad66d1f21b4 --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/logging/LogOnce.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include + +#define LOG_INFO_ONCE LogOnceWrapper(LogOnceWrapper::Severity::Info) +#define LOG_WARNING_ONCE LogOnceWrapper(LogOnceWrapper::Severity::Warning) +#define LOG_ERROR_ONCE LogOnceWrapper(LogOnceWrapper::Severity::Error) +#define LOG_FATAL_ONCE LogOnceWrapper(LogOnceWrapper::Severity::Fatal) + +namespace facebook::react { + +class LogOnceWrapper : public std::stringstream { + public: + enum class Severity { Info, Warning, Error, Fatal }; + + LogOnceWrapper(Severity severity = Severity::Info) : severity_(severity) {} + + ~LogOnceWrapper(); + + private: + Severity severity_{Severity::Info}; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/logging/NativeExceptionsManager.cpp b/packages/react-native/ReactCxxPlatform/react/logging/NativeExceptionsManager.cpp new file mode 100644 index 00000000000..344da5a7c15 --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/logging/NativeExceptionsManager.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "NativeExceptionsManager.h" + +#include + +namespace facebook::react { + +void NativeExceptionsManager::reportFatalException( + jsi::Runtime& rt, + std::string message, + std::vector stack, + int32_t exceptionId) { + reportException( + rt, + ExceptionData{ + .message = std::move(message), + .stack = std::move(stack), + .id = exceptionId, + .isFatal = true}); +} + +void NativeExceptionsManager::reportSoftException( + jsi::Runtime& rt, + std::string message, + std::vector stack, + int32_t exceptionId) { + reportException( + rt, + ExceptionData{ + .message = std::move(message), + .stack = std::move(stack), + .id = exceptionId, + .isFatal = false}); +} + +void NativeExceptionsManager::reportException( + jsi::Runtime& runtime, + const ExceptionData& data) { + if (onJsError_) { + std::vector frames; + frames.reserve(data.stack.size()); + for (const auto& frame : data.stack) { + frames.push_back(JsErrorHandler::ProcessedError::StackFrame{ + .file = frame.file, + .methodName = frame.methodName, + .lineNumber = frame.lineNumber, + .column = frame.column}); + } + JsErrorHandler::ProcessedError processedError{ + .message = data.message, + .originalMessage = std::nullopt, + .name = std::nullopt, + .componentStack = std::nullopt, + .stack = frames, + .id = data.id, + .isFatal = data.isFatal, + .extraData = jsi::Object(runtime), + }; + onJsError_(runtime, processedError); + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/logging/NativeExceptionsManager.h b/packages/react-native/ReactCxxPlatform/react/logging/NativeExceptionsManager.h new file mode 100644 index 00000000000..685709ec5be --- /dev/null +++ b/packages/react-native/ReactCxxPlatform/react/logging/NativeExceptionsManager.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +// https://github.com/facebook/react-native/blob/v0.71.1/Libraries/Core/NativeExceptionsManager.js#L17-L35 +using StackFrame = NativeExceptionsManagerStackFrame< + std::optional, + std::optional, + std::optional, + std::string, + std::optional>; + +template <> +struct Bridging + : NativeExceptionsManagerStackFrameBridging {}; + +using ExceptionData = NativeExceptionsManagerExceptionData< + std::string, + std::optional, + std::optional, + std::optional, + std::vector, + int32_t, + bool, + std::optional>>; + +template <> +struct Bridging + : NativeExceptionsManagerExceptionDataBridging {}; + +class NativeExceptionsManager + : public NativeExceptionsManagerCxxSpec { + public: + NativeExceptionsManager( + JsErrorHandler::OnJsError onJsError, + std::shared_ptr jsInvoker) + : NativeExceptionsManagerCxxSpec(jsInvoker), + onJsError_(std::move(onJsError)) {} + + void reportFatalException( + jsi::Runtime& rt, + std::string message, + std::vector stack, + int32_t exceptionId); + + void reportSoftException( + jsi::Runtime& rt, + std::string message, + std::vector stack, + int32_t exceptionId); + + void reportException(jsi::Runtime& runtime, const ExceptionData& data); + + void updateExceptionMessage( + jsi::Runtime& rt, + jsi::String message, + jsi::Array stack, + double exceptionId) { + // This method is not in modern React Native: + // https://github.com/search?q=repo%3Afacebook%2Freact-native+updateExceptionMessage&type=code + } + + void dismissRedbox(jsi::Runtime& rt) { + // Redbox is not supported at this time, LogBox is: + // https://reactnative.dev/blog/2020/07/06/version-0.63 + } + + private: + JsErrorHandler::OnJsError onJsError_; +}; + +} // namespace facebook::react