mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/48783 ## Overview This is the final boss of the new owner stacks feature. With owner stacks, we don't need to parse message strings to find the component stack for logbox. Instead, we can access the component stack directly with `captureOwnerStack`. This means we don't need to install the LogBox console.error patch and can greatly simplify the process of handling errors and make it more reliable. To do this, we rely only on adding LogBox to the ExceptionManager: - `reactConsoleErrorHandler` -> `LogBox.addConsoleLog` - `reportException` -> `LogBox.addException` Changelog: [General][Fixed] - Remove LogBox patch, de-duplicating errors ## Benefits As a side effect, this removes a lot of duplicate errors. For example, currently if a component throws, you get 2 errors: {F1974436906} After this, there's just the one you expect: {F1974436908} ## Followups After this lands and doesn't need reverted for some reason, we can delete a ton of code from logbox for finding and detecting stacks from errors. Reviewed By: javache Differential Revision: D68380668 fbshipit-source-id: 68112f1e3992fada4d6aaffdf9bd618ce1834f7b
285 lines
7.9 KiB
JavaScript
285 lines
7.9 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
* @flow strict
|
|
* @format
|
|
*/
|
|
|
|
import type {IgnorePattern, LogData} from './Data/LogBoxData';
|
|
import type {ExtendedExceptionData} from './Data/parseLogBoxLog';
|
|
|
|
import Platform from '../Utilities/Platform';
|
|
import RCTLog from '../Utilities/RCTLog';
|
|
import * as React from 'react';
|
|
|
|
export type {LogData, ExtendedExceptionData, IgnorePattern};
|
|
|
|
let LogBox;
|
|
|
|
interface ILogBox {
|
|
install(): void;
|
|
uninstall(): void;
|
|
isInstalled(): boolean;
|
|
ignoreLogs($ReadOnlyArray<IgnorePattern>): void;
|
|
ignoreAllLogs(value?: boolean): void;
|
|
clearAllLogs(): void;
|
|
addLog(log: LogData): void;
|
|
addConsoleLog(level: 'warn' | 'error', ...args: Array<mixed>): void;
|
|
addException(error: ExtendedExceptionData): void;
|
|
}
|
|
|
|
/**
|
|
* LogBox displays logs in the app.
|
|
*/
|
|
if (__DEV__) {
|
|
const LogBoxData = require('./Data/LogBoxData');
|
|
const {
|
|
parseLogBoxLog,
|
|
parseComponentStack,
|
|
} = require('./Data/parseLogBoxLog');
|
|
|
|
let originalConsoleWarn;
|
|
let consoleWarnImpl: (...args: Array<mixed>) => void;
|
|
|
|
let isLogBoxInstalled: boolean = false;
|
|
|
|
LogBox = {
|
|
install(): void {
|
|
if (isLogBoxInstalled) {
|
|
return;
|
|
}
|
|
|
|
isLogBoxInstalled = true;
|
|
|
|
if (global.RN$registerExceptionListener != null) {
|
|
global.RN$registerExceptionListener(
|
|
(error: ExtendedExceptionData & {preventDefault: () => mixed}) => {
|
|
if (global.RN$isRuntimeReady?.() || !error.isFatal) {
|
|
error.preventDefault();
|
|
addException(error);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
// Trigger lazy initialization of module.
|
|
require('../NativeModules/specs/NativeLogBox');
|
|
|
|
// IMPORTANT: we only overwrite `console.error` and `console.warn` once.
|
|
// When we uninstall we keep the same reference and only change its
|
|
// internal implementation
|
|
const isFirstInstall = originalConsoleWarn == null;
|
|
if (isFirstInstall) {
|
|
// We only patch warning for legacy reasons.
|
|
// This will be removed in the future, once warnings
|
|
// are fully moved to fusebox. Error handling is done
|
|
// via the ExceptionManager.
|
|
originalConsoleWarn = console.warn.bind(console);
|
|
|
|
// $FlowExpectedError[cannot-write]
|
|
console.warn = (...args) => {
|
|
consoleWarnImpl(...args);
|
|
};
|
|
}
|
|
|
|
consoleWarnImpl = registerWarning;
|
|
|
|
if (Platform.isTesting) {
|
|
LogBoxData.setDisabled(true);
|
|
}
|
|
|
|
RCTLog.setWarningHandler((...args) => {
|
|
registerWarning(...args);
|
|
});
|
|
},
|
|
|
|
uninstall(): void {
|
|
if (!isLogBoxInstalled) {
|
|
return;
|
|
}
|
|
|
|
isLogBoxInstalled = false;
|
|
|
|
// IMPORTANT: we don't re-assign to `console` in case the method has been
|
|
// decorated again after installing LogBox. E.g.:
|
|
// Before uninstalling: original > LogBox > OtherErrorHandler
|
|
// After uninstalling: original > LogBox (noop) > OtherErrorHandler
|
|
consoleWarnImpl = originalConsoleWarn;
|
|
},
|
|
|
|
isInstalled(): boolean {
|
|
return isLogBoxInstalled;
|
|
},
|
|
|
|
/**
|
|
* Silence any logs that match the given strings or regexes.
|
|
*/
|
|
ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
|
|
LogBoxData.addIgnorePatterns(patterns);
|
|
},
|
|
|
|
/**
|
|
* Toggle error and warning notifications
|
|
* Note: this only disables notifications, uncaught errors will still open a full screen LogBox.
|
|
* @param ignore whether to ignore logs or not
|
|
*/
|
|
ignoreAllLogs(value?: ?boolean): void {
|
|
LogBoxData.setDisabled(value == null ? true : value);
|
|
},
|
|
|
|
clearAllLogs(): void {
|
|
LogBoxData.clear();
|
|
},
|
|
|
|
addLog(log: LogData): void {
|
|
if (isLogBoxInstalled) {
|
|
LogBoxData.addLog(log);
|
|
}
|
|
},
|
|
|
|
addConsoleLog(level: 'warn' | 'error', ...args: Array<mixed>) {
|
|
if (isLogBoxInstalled) {
|
|
let filteredLevel: 'warn' | 'error' | 'fatal' = level;
|
|
try {
|
|
let format = args[0];
|
|
if (typeof format === 'string') {
|
|
const filterResult =
|
|
require('../LogBox/Data/LogBoxData').checkWarningFilter(
|
|
// For legacy reasons, we strip the warning prefix from the message.
|
|
// Can remove this once we remove the warning module altogether.
|
|
format.replace(/^Warning: /, ''),
|
|
);
|
|
if (filterResult.monitorEvent !== 'warning_unhandled') {
|
|
if (filterResult.suppressCompletely) {
|
|
return;
|
|
}
|
|
|
|
if (filterResult.suppressDialog_LEGACY === true) {
|
|
filteredLevel = 'warn';
|
|
} else if (filterResult.forceDialogImmediately === true) {
|
|
filteredLevel = 'fatal'; // Do not downgrade. These are real bugs with same severity as throws.
|
|
}
|
|
args[0] = filterResult.finalFormat;
|
|
}
|
|
}
|
|
|
|
const result = parseLogBoxLog(args);
|
|
const category = result.category;
|
|
const message = result.message;
|
|
let componentStackType = result.componentStackType;
|
|
let componentStack = result.componentStack;
|
|
if (
|
|
(!componentStack || componentStack.length === 0) &&
|
|
// $FlowExpectedError[prop-missing]
|
|
React.captureOwnerStack
|
|
) {
|
|
const ownerStack = React.captureOwnerStack();
|
|
if (ownerStack != null && ownerStack.length > 0) {
|
|
const parsedComponentStack = parseComponentStack(ownerStack);
|
|
componentStack = parsedComponentStack.stack;
|
|
componentStackType = parsedComponentStack.type;
|
|
}
|
|
}
|
|
if (!LogBoxData.isMessageIgnored(message.content)) {
|
|
LogBoxData.addLog({
|
|
level: filteredLevel,
|
|
category,
|
|
message,
|
|
componentStack,
|
|
componentStackType,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
LogBoxData.reportLogBoxError(err);
|
|
}
|
|
}
|
|
},
|
|
|
|
addException,
|
|
};
|
|
|
|
function addException(error: ExtendedExceptionData): void {
|
|
if (isLogBoxInstalled) {
|
|
LogBoxData.addException(error);
|
|
}
|
|
}
|
|
|
|
const isRCTLogAdviceWarning = (...args: Array<mixed>) => {
|
|
// RCTLogAdvice is a native logging function designed to show users
|
|
// a message in the console, but not show it to them in Logbox.
|
|
return typeof args[0] === 'string' && args[0].startsWith('(ADVICE)');
|
|
};
|
|
|
|
const registerWarning = (...args: Array<mixed>): void => {
|
|
// Let warnings within LogBox itself fall through.
|
|
if (LogBoxData.isLogBoxErrorMessage(String(args[0]))) {
|
|
return;
|
|
} else {
|
|
// Be sure to pass LogBox warnings through.
|
|
originalConsoleWarn(...args);
|
|
}
|
|
|
|
try {
|
|
if (!isRCTLogAdviceWarning(...args)) {
|
|
const {category, message, componentStack, componentStackType} =
|
|
parseLogBoxLog(args);
|
|
|
|
if (!LogBoxData.isMessageIgnored(message.content)) {
|
|
LogBoxData.addLog({
|
|
level: 'warn',
|
|
category,
|
|
message,
|
|
componentStack,
|
|
componentStackType,
|
|
});
|
|
}
|
|
}
|
|
} catch (err) {
|
|
LogBoxData.reportLogBoxError(err);
|
|
}
|
|
};
|
|
} else {
|
|
LogBox = {
|
|
install(): void {
|
|
// Do nothing.
|
|
},
|
|
|
|
uninstall(): void {
|
|
// Do nothing.
|
|
},
|
|
|
|
isInstalled(): boolean {
|
|
return false;
|
|
},
|
|
|
|
ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
|
|
// Do nothing.
|
|
},
|
|
|
|
ignoreAllLogs(value?: ?boolean): void {
|
|
// Do nothing.
|
|
},
|
|
|
|
clearAllLogs(): void {
|
|
// Do nothing.
|
|
},
|
|
|
|
addLog(log: LogData): void {
|
|
// Do nothing.
|
|
},
|
|
|
|
addConsoleLog(level: 'warn' | 'error', ...args: Array<mixed>): void {
|
|
// Do nothing.
|
|
},
|
|
|
|
addException(error: ExtendedExceptionData): void {
|
|
// Do nothing.
|
|
},
|
|
};
|
|
}
|
|
|
|
export default (LogBox: ILogBox);
|