Files
react-native/Libraries/LogBox/Data/LogBoxData.js
T
Rick Hanlon 038353b89c LogBox - switch to array filter
Summary:
Fixes a bug for old versions of JSC that do not support `for of` syntax

Changelog: [Internal]

Reviewed By: fkgozali

Differential Revision: D18265611

fbshipit-source-id: 4643b6e2571c57ddd982661d188c3449f17a151e
2019-10-31 21:10:26 -07:00

233 lines
5.9 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 strict-local
* @format
*/
('use strict');
import LogBoxLog from './LogBoxLog';
import {parseCategory, parseComponentStack} from './parseLogBoxLog';
import type {LogLevel} from './LogBoxLog';
import type {Message, Category, ComponentStack} from './parseLogBoxLog';
import parseErrorStack from '../../Core/Devtools/parseErrorStack';
import type {ExceptionData} from '../../Core/NativeExceptionsManager';
export type LogBoxLogs = Set<LogBoxLog>;
export type LogData = $ReadOnly<{|
level: LogLevel,
message: Message,
category: Category,
componentStack: ComponentStack,
|}>;
export type Observer = (logs: LogBoxLogs) => void;
export type IgnorePattern = string | RegExp;
export type Subscription = $ReadOnly<{|
unsubscribe: () => void,
|}>;
const observers: Set<{observer: Observer}> = new Set();
const ignorePatterns: Set<IgnorePattern> = new Set();
let logs: LogBoxLogs = new Set();
let updateTimeout = null;
let _isDisabled = false;
export function isMessageIgnored(message: string): boolean {
for (const pattern of ignorePatterns) {
if (
(pattern instanceof RegExp && pattern.test(message)) ||
(typeof pattern === 'string' && message.includes(pattern))
) {
return true;
}
}
return false;
}
function handleUpdate(): void {
if (updateTimeout == null) {
updateTimeout = setImmediate(() => {
updateTimeout = null;
const logsSet = _isDisabled ? new Set() : logs;
observers.forEach(({observer}) => observer(logsSet));
});
}
}
export function addLog(log: LogData): void {
const errorForStackTrace = new Error();
// Parsing logs are expensive so we schedule this
// otherwise spammy logs would pause rendering.
setImmediate(() => {
// TODO: Use Error.captureStackTrace on Hermes
const stack = parseErrorStack(errorForStackTrace);
// If the next log has the same category as the previous one
// then we want to roll it up into the last log in the list
// by incrementing the count (simar to how Chrome does it).
const lastLog = Array.from(logs).pop();
if (lastLog && lastLog.category === log.category) {
lastLog.incrementCount();
} else {
logs.add(
new LogBoxLog(
log.level,
log.message,
stack,
log.category,
log.componentStack,
),
);
}
handleUpdate();
});
}
export function symbolicateLogNow(log: LogBoxLog) {
log.symbolicate(() => {
handleUpdate();
});
}
export function retrySymbolicateLogNow(log: LogBoxLog) {
log.retrySymbolicate(() => {
handleUpdate();
});
}
export function symbolicateLogLazy(log: LogBoxLog) {
log.symbolicate();
}
export function addException(error: ExceptionData): void {
// Parsing logs are expensive so we schedule this
// otherwise spammy logs would pause rendering.
setImmediate(() => {
const {category, message} = parseCategory([
error.originalMessage != null ? error.originalMessage : 'Unknown',
]);
// We don't want to store these logs because they trigger a
// state update whenever we add them to the store, which is
// expensive to noisy logs. If we later want to display these
// we will store them in a different state object.
if (isMessageIgnored(message.content)) {
return;
}
const lastLog = Array.from(logs).pop();
if (lastLog && lastLog.category === category) {
lastLog.incrementCount();
} else {
logs.add(
new LogBoxLog(
'error',
message,
error.stack,
category,
error.componentStack != null
? parseComponentStack(error.componentStack)
: [],
),
);
}
handleUpdate();
});
}
export function clear(): void {
if (logs.size > 0) {
logs.clear();
handleUpdate();
}
}
export function clearWarnings(): void {
logs = new Set(Array.from(logs).filter(log => log.level !== 'warn'));
handleUpdate();
}
export function clearErrors(): void {
logs = new Set(Array.from(logs).filter(log => log.level !== 'error'));
handleUpdate();
}
export function dismiss(log: LogBoxLog): void {
if (logs.has(log)) {
logs.delete(log);
handleUpdate();
}
}
export function addIgnorePatterns(
patterns: $ReadOnlyArray<IgnorePattern>,
): void {
// The same pattern may be added multiple times, but adding a new pattern
// can be expensive so let's find only the ones that are new.
const newPatterns = patterns.filter((pattern: IgnorePattern) => {
if (pattern instanceof RegExp) {
for (const existingPattern of ignorePatterns.entries()) {
if (
existingPattern instanceof RegExp &&
existingPattern.toString() === pattern.toString()
) {
return false;
}
}
return true;
}
return !ignorePatterns.has(pattern);
});
if (newPatterns.length === 0) {
return;
}
for (const pattern of newPatterns) {
ignorePatterns.add(pattern);
// We need to recheck all of the existing logs.
// This allows adding an ignore pattern anywhere in the codebase.
// Without this, if you ignore a pattern after the a log is created,
// then we would keep showing the log.
logs = new Set(
Array.from(logs).filter(log => !isMessageIgnored(log.message.content)),
);
}
handleUpdate();
}
export function setDisabled(value: boolean): void {
if (value === _isDisabled) {
return;
}
_isDisabled = value;
handleUpdate();
}
export function isDisabled(): boolean {
return _isDisabled;
}
export function observe(observer: Observer): Subscription {
const subscription = {observer};
observers.add(subscription);
const logsToObserve = _isDisabled ? new Set() : logs;
observer(logsToObserve);
return {
unsubscribe(): void {
observers.delete(subscription);
},
};
}