Skip to content

Commit 8abe737

Browse files
rubennortefacebook-github-bot
authored andcommitted
Centralize public access to LogBox in LogBox module
Summary: Some components are using `LogBoxData` directly, forcing logs to be shown on the screen even when LogBox is uninstalled. This changes all accesses to `LogBoxData` to go through `LogBox` so `uninstall` is used correctly. It also changes when LogBox is installed, moving it from `AppContainer` to `InitializeCore` (which happens earlier) so we can capture more logs in LogBox. Changelog: [General][Changed] Initialized LogBox earlier and centralized access in LogBox module Reviewed By: rickhanlonii Differential Revision: D27999361 fbshipit-source-id: 1115ef6b71e08cc33743d205da0064fbe9a74a0e
1 parent ab45509 commit 8abe737

File tree

7 files changed

+238
-66
lines changed

7 files changed

+238
-66
lines changed

Libraries/Core/ExceptionsManager.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ function reportException(
105105
}
106106

107107
if (__DEV__ && isHandledByLogBox) {
108-
const LogBoxData = require('../LogBox/Data/LogBoxData');
109-
LogBoxData.addException({
108+
const LogBox = require('../LogBox/LogBox');
109+
LogBox.addException({
110110
...data,
111111
isComponentError: !!e.isComponentError,
112112
});

Libraries/Core/InitializeCore.js

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ require('./setUpSegmentFetcher');
4343
if (__DEV__) {
4444
require('./checkNativeVersion');
4545
require('./setUpDeveloperTools');
46+
require('../LogBox/LogBox').install();
4647
}
4748

4849
const GlobalPerformanceLogger = require('../Utilities/GlobalPerformanceLogger');

Libraries/LogBox/LogBox.js

+114-47
Original file line numberDiff line numberDiff line change
@@ -11,56 +11,69 @@
1111
import Platform from '../Utilities/Platform';
1212
import RCTLog from '../Utilities/RCTLog';
1313

14-
import type {IgnorePattern} from './Data/LogBoxData';
14+
import type {IgnorePattern, LogData} from './Data/LogBoxData';
15+
import type {ExtendedExceptionData} from './Data/parseLogBoxLog';
16+
17+
export type {LogData, ExtendedExceptionData, IgnorePattern};
1518

1619
let LogBox;
1720

21+
interface ILogBox {
22+
install(): void;
23+
uninstall(): void;
24+
isInstalled(): boolean;
25+
ignoreLogs($ReadOnlyArray<IgnorePattern>): void;
26+
ignoreAllLogs(?boolean): void;
27+
clearAllLogs(): void;
28+
addLog(log: LogData): void;
29+
addException(error: ExtendedExceptionData): void;
30+
}
31+
1832
/**
1933
* LogBox displays logs in the app.
2034
*/
2135
if (__DEV__) {
2236
const LogBoxData = require('./Data/LogBoxData');
2337
const {parseLogBoxLog, parseInterpolation} = require('./Data/parseLogBoxLog');
2438

25-
// LogBox needs to insert itself early,
26-
// in order to access the component stacks appended by React DevTools.
27-
const {error, warn} = console;
28-
let errorImpl = error.bind(console);
29-
let warnImpl = warn.bind(console);
39+
let originalConsoleError;
40+
let originalConsoleWarn;
41+
let consoleErrorImpl;
42+
let consoleWarnImpl;
3043

31-
(console: any).error = function(...args) {
32-
errorImpl(...args);
33-
};
34-
(console: any).warn = function(...args) {
35-
warnImpl(...args);
36-
};
44+
let isLogBoxInstalled: boolean = false;
3745

3846
LogBox = {
39-
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
40-
LogBoxData.addIgnorePatterns(patterns);
41-
},
47+
install(): void {
48+
if (isLogBoxInstalled) {
49+
return;
50+
}
4251

43-
ignoreAllLogs: (value?: ?boolean): void => {
44-
LogBoxData.setDisabled(value == null ? true : value);
45-
},
52+
isLogBoxInstalled = true;
4653

47-
uninstall: (): void => {
48-
errorImpl = error;
49-
warnImpl = warn;
50-
delete (console: any).disableLogBox;
51-
},
52-
53-
install: (): void => {
5454
// Trigger lazy initialization of module.
5555
require('../NativeModules/specs/NativeLogBox');
5656

57-
errorImpl = function(...args) {
58-
registerError(...args);
59-
};
57+
// IMPORTANT: we only overwrite `console.error` and `console.warn` once.
58+
// When we uninstall we keep the same reference and only change its
59+
// internal implementation
60+
const isFirstInstall = originalConsoleError == null;
61+
if (isFirstInstall) {
62+
originalConsoleError = console.error.bind(console);
63+
originalConsoleWarn = console.warn.bind(console);
64+
65+
// $FlowExpectedError[cannot-write]
66+
console.error = (...args) => {
67+
consoleErrorImpl(...args);
68+
};
69+
// $FlowExpectedError[cannot-write]
70+
console.warn = (...args) => {
71+
consoleWarnImpl(...args);
72+
};
73+
}
6074

61-
warnImpl = function(...args) {
62-
registerWarning(...args);
63-
};
75+
consoleErrorImpl = registerError;
76+
consoleWarnImpl = registerWarning;
6477

6578
if ((console: any).disableYellowBox === true) {
6679
LogBoxData.setDisabled(true);
@@ -88,6 +101,50 @@ if (__DEV__) {
88101
registerWarning(...args);
89102
});
90103
},
104+
105+
uninstall(): void {
106+
if (!isLogBoxInstalled) {
107+
return;
108+
}
109+
110+
isLogBoxInstalled = false;
111+
112+
// IMPORTANT: we don't re-assign to `console` in case the method has been
113+
// decorated again after installing LogBox. E.g.:
114+
// Before uninstalling: original > LogBox > OtherErrorHandler
115+
// After uninstalling: original > LogBox (noop) > OtherErrorHandler
116+
consoleErrorImpl = originalConsoleError;
117+
consoleWarnImpl = originalConsoleWarn;
118+
delete (console: any).disableLogBox;
119+
},
120+
121+
isInstalled(): boolean {
122+
return isLogBoxInstalled;
123+
},
124+
125+
ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
126+
LogBoxData.addIgnorePatterns(patterns);
127+
},
128+
129+
ignoreAllLogs(value?: ?boolean): void {
130+
LogBoxData.setDisabled(value == null ? true : value);
131+
},
132+
133+
clearAllLogs(): void {
134+
LogBoxData.clear();
135+
},
136+
137+
addLog(log: LogData): void {
138+
if (isLogBoxInstalled) {
139+
LogBoxData.addLog(log);
140+
}
141+
},
142+
143+
addException(error: ExtendedExceptionData): void {
144+
if (isLogBoxInstalled) {
145+
LogBoxData.addException(error);
146+
}
147+
},
91148
};
92149

93150
const isRCTLogAdviceWarning = (...args) => {
@@ -103,7 +160,7 @@ if (__DEV__) {
103160
const registerWarning = (...args): void => {
104161
// Let warnings within LogBox itself fall through.
105162
if (LogBoxData.isLogBoxErrorMessage(String(args[0]))) {
106-
error.call(console, ...args);
163+
originalConsoleError(...args);
107164
return;
108165
}
109166

@@ -113,7 +170,7 @@ if (__DEV__) {
113170

114171
if (!LogBoxData.isMessageIgnored(message.content)) {
115172
// Be sure to pass LogBox warnings through.
116-
warn.call(console, ...args);
173+
originalConsoleWarn(...args);
117174

118175
LogBoxData.addLog({
119176
level: 'warn',
@@ -131,7 +188,7 @@ if (__DEV__) {
131188
const registerError = (...args): void => {
132189
// Let errors within LogBox itself fall through.
133190
if (LogBoxData.isLogBoxErrorMessage(args[0])) {
134-
error.call(console, ...args);
191+
originalConsoleError(...args);
135192
return;
136193
}
137194

@@ -144,7 +201,7 @@ if (__DEV__) {
144201
//
145202
// The 'warning' module needs to be handled here because React internally calls
146203
// `console.error('Warning: ')` with the component stack already included.
147-
error.call(console, ...args);
204+
originalConsoleError(...args);
148205
return;
149206
}
150207

@@ -169,7 +226,7 @@ if (__DEV__) {
169226
// Interpolate the message so they are formatted for adb and other CLIs.
170227
// This is different than the message.content above because it includes component stacks.
171228
const interpolated = parseInterpolation(args);
172-
error.call(console, interpolated.message.content);
229+
originalConsoleError(interpolated.message.content);
173230

174231
LogBoxData.addLog({
175232
level,
@@ -184,28 +241,38 @@ if (__DEV__) {
184241
};
185242
} else {
186243
LogBox = {
187-
ignoreLogs: (patterns: $ReadOnlyArray<IgnorePattern>): void => {
244+
install(): void {
245+
// Do nothing.
246+
},
247+
248+
uninstall(): void {
249+
// Do nothing.
250+
},
251+
252+
isInstalled(): boolean {
253+
return false;
254+
},
255+
256+
ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
257+
// Do nothing.
258+
},
259+
260+
ignoreAllLogs(value?: ?boolean): void {
188261
// Do nothing.
189262
},
190263

191-
ignoreAllLogs: (value?: ?boolean): void => {
264+
clearAllLogs(): void {
192265
// Do nothing.
193266
},
194267

195-
install: (): void => {
268+
addLog(log: LogData): void {
196269
// Do nothing.
197270
},
198271

199-
uninstall: (): void => {
272+
addException(error: ExtendedExceptionData): void {
200273
// Do nothing.
201274
},
202275
};
203276
}
204277

205-
module.exports = (LogBox: {
206-
ignoreLogs($ReadOnlyArray<IgnorePattern>): void,
207-
ignoreAllLogs(?boolean): void,
208-
install(): void,
209-
uninstall(): void,
210-
...
211-
});
278+
module.exports = (LogBox: ILogBox);

Libraries/LogBox/__tests__/LogBox-test.js

+107-2
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ function mockFilterResult(returnValues) {
3131

3232
describe('LogBox', () => {
3333
const {error, warn} = console;
34+
let consoleError;
35+
let consoleWarn;
3436

3537
beforeEach(() => {
3638
jest.resetModules();
37-
console.error = jest.fn();
38-
console.warn = jest.fn();
39+
console.error = consoleError = jest.fn();
40+
console.warn = consoleWarn = jest.fn();
3941
console.disableYellowBox = false;
4042
});
4143

@@ -276,4 +278,107 @@ describe('LogBox', () => {
276278
},
277279
});
278280
});
281+
282+
it('ignores console methods after uninstalling', () => {
283+
jest.mock('../Data/LogBoxData');
284+
285+
LogBox.install();
286+
LogBox.uninstall();
287+
288+
console.log('Test');
289+
console.warn('Test');
290+
console.error('Test');
291+
292+
expect(LogBoxData.addLog).not.toHaveBeenCalled();
293+
});
294+
295+
it('does not add logs after uninstalling', () => {
296+
jest.mock('../Data/LogBoxData');
297+
298+
LogBox.install();
299+
LogBox.uninstall();
300+
301+
LogBox.addLog({
302+
level: 'warn',
303+
category: 'test',
304+
message: {content: 'Some warning', substitutions: []},
305+
componentStack: [],
306+
});
307+
308+
expect(LogBoxData.addLog).not.toHaveBeenCalled();
309+
});
310+
311+
it('does not add exceptions after uninstalling', () => {
312+
jest.mock('../Data/LogBoxData');
313+
314+
LogBox.install();
315+
LogBox.uninstall();
316+
317+
LogBox.addException({
318+
message: 'Some error',
319+
originalMessage: null,
320+
name: 'Error',
321+
componentStack: null,
322+
stack: [],
323+
id: 12,
324+
isFatal: true,
325+
isComponentError: false,
326+
});
327+
328+
expect(LogBoxData.addException).not.toHaveBeenCalled();
329+
});
330+
331+
it('preserves decorations of console.error after installing/uninstalling', () => {
332+
LogBox.install();
333+
334+
const originalConsoleError = console.error;
335+
console.error = message => {
336+
originalConsoleError('Custom: ' + message);
337+
};
338+
339+
console.error('before uninstalling');
340+
341+
expect(consoleError).toHaveBeenCalledWith('Custom: before uninstalling');
342+
343+
LogBox.uninstall();
344+
345+
console.error('after uninstalling');
346+
347+
expect(consoleError).toHaveBeenCalledWith('Custom: after uninstalling');
348+
349+
LogBox.install();
350+
351+
console.error('after installing for the second time');
352+
353+
expect(consoleError).toHaveBeenCalledWith(
354+
'Custom: after installing for the second time',
355+
);
356+
});
357+
358+
it('preserves decorations of console.warn after installing/uninstalling', () => {
359+
LogBox.install();
360+
361+
const originalConsoleWarn = console.warn;
362+
console.warn = message => {
363+
originalConsoleWarn('Custom: ' + message);
364+
};
365+
366+
console.warn('before uninstalling');
367+
368+
expect(consoleWarn).toHaveBeenCalledWith('Custom: before uninstalling');
369+
370+
LogBox.uninstall();
371+
372+
console.warn('after uninstalling');
373+
374+
expect(consoleWarn).toHaveBeenCalledWith('Custom: after uninstalling');
375+
376+
LogBox.install();
377+
378+
console.warn('after installing for the second time');
379+
380+
expect(consoleWarn).toHaveBeenCalledWith(
381+
'Custom: after installing for the second time',
382+
);
383+
});
279384
});

0 commit comments

Comments
 (0)