Files
react-native/Libraries/LogBox/LogBox.js
T
Rick Hanlon 9c64bd5739 Properly handle LogBox errors during tests
Summary:
This diff fixes an issue where errors in LogBox during tests would cause the tests to crash.

The crash is due to the NativeExceptionsManager module not being mocked (as all native module need to be in tests). The fix is to properly mock the NativeExceptionManger.

This fix exposed an infinite loop issue where failures in LogBox will be logged to the ExceptionManager, which logs to the console, which logs to LogBox, creating a loop. This diff also fixes that look by moving the LogBox internal error check to the top of the monkey patched console methods.

Changelog: [Internal]

Differential Revision: D20428590

fbshipit-source-id: 7289a480c99ba8dee67772178b7629afb40b330a
2020-03-19 20:31:01 -07:00

207 lines
5.6 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import Platform from '../Utilities/Platform';
import RCTLog from '../Utilities/RCTLog';
import * as LogBoxData from './Data/LogBoxData';
import {parseLogBoxLog, parseInterpolation} from './Data/parseLogBoxLog';
import type {IgnorePattern} from './Data/LogBoxData';
let LogBox;
/**
* LogBox displays logs in the app.
*/
if (__DEV__) {
// LogBox needs to insert itself early,
// in order to access the component stacks appended by React DevTools.
const {error, warn} = console;
let errorImpl = error.bind(console);
let warnImpl = warn.bind(console);
(console: any).error = function(...args) {
errorImpl(...args);
};
(console: any).warn = function(...args) {
warnImpl(...args);
};
LogBox = {
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
LogBoxData.addIgnorePatterns(patterns);
},
ignoreAllLogs: (value?: ?boolean): void => {
LogBoxData.setDisabled(!!value);
},
uninstall: (): void => {
errorImpl = error;
warnImpl = warn;
delete (console: any).disableLogBox;
},
install: (): void => {
// Trigger lazy initialization of module.
require('../NativeModules/specs/NativeLogBox');
errorImpl = function(...args) {
registerError(...args);
};
warnImpl = function(...args) {
registerWarning(...args);
};
if ((console: any).disableYellowBox === true) {
LogBoxData.setDisabled(true);
console.warn(
'console.disableYellowBox has been deprecated and will be removed in a future release. Please use LogBox.ignoreAllLogs(value) instead.',
);
}
(Object.defineProperty: any)(console, 'disableYellowBox', {
configurable: true,
get: () => LogBoxData.isDisabled(),
set: value => {
LogBoxData.setDisabled(value);
console.warn(
'console.disableYellowBox has been deprecated and will be removed in a future release. Please use LogBox.ignoreAllLogs(value) instead.',
);
},
});
if (Platform.isTesting) {
LogBoxData.setDisabled(true);
}
RCTLog.setWarningHandler((...args) => {
registerWarning(...args);
});
},
};
const isRCTLogAdviceWarning = (...args) => {
// 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 isWarningModuleWarning = (...args) => {
return typeof args[0] === 'string' && args[0].startsWith('Warning: ');
};
const registerWarning = (...args): void => {
// Let warnings within LogBox itself fall through.
if (LogBoxData.isLogBoxErrorMessage(String(args[0]))) {
error.call(console, ...args);
return;
}
try {
if (!isRCTLogAdviceWarning(...args)) {
const {category, message, componentStack} = parseLogBoxLog(args);
if (!LogBoxData.isMessageIgnored(message.content)) {
// Be sure to pass LogBox warnings through.
warn.call(console, ...args);
LogBoxData.addLog({
level: 'warn',
category,
message,
componentStack,
});
}
}
} catch (err) {
LogBoxData.reportLogBoxError(err);
}
};
const registerError = (...args): void => {
// Let errors within LogBox itself fall through.
if (LogBoxData.isLogBoxErrorMessage(args[0])) {
error.call(console, ...args);
return;
}
try {
if (!isWarningModuleWarning(...args)) {
// Only show LogBox for the `warning` module, otherwise pass through and skip.
error.call(console, ...args);
return;
}
const format = args[0].replace('Warning: ', '');
const filterResult = LogBoxData.checkWarningFilter(format);
if (filterResult.suppressCompletely) {
return;
}
let level = 'error';
if (filterResult.suppressDialog_LEGACY === true) {
level = 'warn';
} else if (filterResult.forceDialogImmediately === true) {
level = 'fatal'; // Do not downgrade. These are real bugs with same severity as throws.
}
// Unfortunately, we need to add the Warning: prefix back for downstream dependencies.
args[0] = `Warning: ${filterResult.finalFormat}`;
const {category, message, componentStack} = parseLogBoxLog(args);
if (!LogBoxData.isMessageIgnored(message.content)) {
// Interpolate the message so they are formatted for adb and other CLIs.
// This is different than the message.content above because it includes component stacks.
const interpolated = parseInterpolation(args);
error.call(console, interpolated.message.content);
LogBoxData.addLog({
level,
category,
message,
componentStack,
});
}
} catch (err) {
LogBoxData.reportLogBoxError(err);
}
};
} else {
LogBox = {
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
// Do nothing.
},
ignoreAllLogs: (value?: ?boolean): void => {
// Do nothing.
},
install: (): void => {
// Do nothing.
},
uninstall: (): void => {
// Do nothing.
},
};
}
module.exports = (LogBox: {
ignoreLogs($ReadOnlyArray<IgnorePattern>): void,
ignoreAllLogs(?boolean): void,
install(): void,
uninstall(): void,
...
});