126ad19fcSEvan Bacon/** 226ad19fcSEvan Bacon * Copyright (c) 650 Industries. 326ad19fcSEvan Bacon * Copyright (c) Meta Platforms, Inc. and affiliates. 426ad19fcSEvan Bacon * 526ad19fcSEvan Bacon * This source code is licensed under the MIT license found in the 626ad19fcSEvan Bacon * LICENSE file in the root directory of this source tree. 726ad19fcSEvan Bacon */ 826ad19fcSEvan Bacon 926ad19fcSEvan Baconimport { IgnorePattern, LogData } from './Data/LogBoxData'; 1026ad19fcSEvan Baconimport { ExtendedExceptionData } from './Data/parseLogBoxLog'; 1126ad19fcSEvan Bacon 1226ad19fcSEvan Baconexport { LogData, ExtendedExceptionData, IgnorePattern }; 1326ad19fcSEvan Bacon 1426ad19fcSEvan Baconlet LogBox: ILogBox; 1526ad19fcSEvan Bacon 1626ad19fcSEvan Baconinterface ILogBox { 1726ad19fcSEvan Bacon install(): void; 1826ad19fcSEvan Bacon uninstall(): void; 1926ad19fcSEvan Bacon isInstalled(): boolean; 2026ad19fcSEvan Bacon ignoreLogs(patterns: readonly IgnorePattern[]): void; 2126ad19fcSEvan Bacon ignoreAllLogs(ignore?: boolean): void; 2226ad19fcSEvan Bacon clearAllLogs(): void; 2326ad19fcSEvan Bacon addLog(log: LogData): void; 2426ad19fcSEvan Bacon addException(error: ExtendedExceptionData): void; 2526ad19fcSEvan Bacon} 2626ad19fcSEvan Bacon 2726ad19fcSEvan Bacon/** 2826ad19fcSEvan Bacon * LogBox displays logs in the app. 2926ad19fcSEvan Bacon */ 3026ad19fcSEvan Baconif (__DEV__) { 3126ad19fcSEvan Bacon const LogBoxData = require('./Data/LogBoxData'); 3226ad19fcSEvan Bacon const { parseLogBoxLog, parseInterpolation } = 3326ad19fcSEvan Bacon require('./Data/parseLogBoxLog') as typeof import('./Data/parseLogBoxLog'); 3426ad19fcSEvan Bacon 3526ad19fcSEvan Bacon let originalConsoleError: typeof console.error | undefined; 3626ad19fcSEvan Bacon let consoleErrorImpl: typeof console.error | undefined; 3726ad19fcSEvan Bacon 3826ad19fcSEvan Bacon let isLogBoxInstalled: boolean = false; 3926ad19fcSEvan Bacon 4026ad19fcSEvan Bacon LogBox = { 4126ad19fcSEvan Bacon install(): void { 4226ad19fcSEvan Bacon if (isLogBoxInstalled) { 4326ad19fcSEvan Bacon return; 4426ad19fcSEvan Bacon } 4526ad19fcSEvan Bacon 4626ad19fcSEvan Bacon isLogBoxInstalled = true; 4726ad19fcSEvan Bacon 4826ad19fcSEvan Bacon // Trigger lazy initialization of module. 4926ad19fcSEvan Bacon // require("../NativeModules/specs/NativeLogBox"); 5026ad19fcSEvan Bacon 5126ad19fcSEvan Bacon // IMPORTANT: we only overwrite `console.error` and `console.warn` once. 5226ad19fcSEvan Bacon // When we uninstall we keep the same reference and only change its 5326ad19fcSEvan Bacon // internal implementation 5426ad19fcSEvan Bacon const isFirstInstall = originalConsoleError == null; 5526ad19fcSEvan Bacon if (isFirstInstall) { 5626ad19fcSEvan Bacon originalConsoleError = console.error.bind(console); 5726ad19fcSEvan Bacon 5826ad19fcSEvan Bacon console.error = (...args) => { 5926ad19fcSEvan Bacon consoleErrorImpl?.(...args); 6026ad19fcSEvan Bacon }; 6126ad19fcSEvan Bacon } 6226ad19fcSEvan Bacon 6326ad19fcSEvan Bacon consoleErrorImpl = registerError; 6426ad19fcSEvan Bacon 65*4a8c0978SEvan Bacon if (process.env.NODE_ENV === 'test') { 6626ad19fcSEvan Bacon LogBoxData.setDisabled(true); 6726ad19fcSEvan Bacon } 6826ad19fcSEvan Bacon }, 6926ad19fcSEvan Bacon 7026ad19fcSEvan Bacon uninstall(): void { 7126ad19fcSEvan Bacon if (!isLogBoxInstalled) { 7226ad19fcSEvan Bacon return; 7326ad19fcSEvan Bacon } 7426ad19fcSEvan Bacon 7526ad19fcSEvan Bacon isLogBoxInstalled = false; 7626ad19fcSEvan Bacon 7726ad19fcSEvan Bacon // IMPORTANT: we don't re-assign to `console` in case the method has been 7826ad19fcSEvan Bacon // decorated again after installing LogBox. E.g.: 7926ad19fcSEvan Bacon // Before uninstalling: original > LogBox > OtherErrorHandler 8026ad19fcSEvan Bacon // After uninstalling: original > LogBox (noop) > OtherErrorHandler 8126ad19fcSEvan Bacon consoleErrorImpl = originalConsoleError; 8226ad19fcSEvan Bacon delete (console as any).disableLogBox; 8326ad19fcSEvan Bacon }, 8426ad19fcSEvan Bacon 8526ad19fcSEvan Bacon isInstalled(): boolean { 8626ad19fcSEvan Bacon return isLogBoxInstalled; 8726ad19fcSEvan Bacon }, 8826ad19fcSEvan Bacon 8926ad19fcSEvan Bacon ignoreLogs(patterns: readonly IgnorePattern[]): void { 9026ad19fcSEvan Bacon LogBoxData.addIgnorePatterns(patterns); 9126ad19fcSEvan Bacon }, 9226ad19fcSEvan Bacon 9326ad19fcSEvan Bacon ignoreAllLogs(value?: boolean): void { 9426ad19fcSEvan Bacon LogBoxData.setDisabled(value == null ? true : value); 9526ad19fcSEvan Bacon }, 9626ad19fcSEvan Bacon 9726ad19fcSEvan Bacon clearAllLogs(): void { 9826ad19fcSEvan Bacon LogBoxData.clear(); 9926ad19fcSEvan Bacon }, 10026ad19fcSEvan Bacon 10126ad19fcSEvan Bacon addLog(log: LogData): void { 10226ad19fcSEvan Bacon if (isLogBoxInstalled) { 10326ad19fcSEvan Bacon LogBoxData.addLog(log); 10426ad19fcSEvan Bacon } 10526ad19fcSEvan Bacon }, 10626ad19fcSEvan Bacon 10726ad19fcSEvan Bacon addException(error: ExtendedExceptionData): void { 10826ad19fcSEvan Bacon if (isLogBoxInstalled) { 10926ad19fcSEvan Bacon LogBoxData.addException(error); 11026ad19fcSEvan Bacon } 11126ad19fcSEvan Bacon }, 11226ad19fcSEvan Bacon }; 11326ad19fcSEvan Bacon 11426ad19fcSEvan Bacon const isWarningModuleWarning = (...args: any) => { 11526ad19fcSEvan Bacon return typeof args[0] === 'string' && args[0].startsWith('Warning: '); 11626ad19fcSEvan Bacon }; 11726ad19fcSEvan Bacon 11826ad19fcSEvan Bacon const registerError = (...args: Parameters<typeof console.error>): void => { 11926ad19fcSEvan Bacon // Let errors within LogBox itself fall through. 12026ad19fcSEvan Bacon if (LogBoxData.isLogBoxErrorMessage(args[0])) { 12126ad19fcSEvan Bacon originalConsoleError?.(...args); 12226ad19fcSEvan Bacon return; 12326ad19fcSEvan Bacon } 12426ad19fcSEvan Bacon 12526ad19fcSEvan Bacon try { 12626ad19fcSEvan Bacon if (!isWarningModuleWarning(...args)) { 12726ad19fcSEvan Bacon // Only show LogBox for the 'warning' module, otherwise pass through. 12826ad19fcSEvan Bacon // By passing through, this will get picked up by the React console override, 12926ad19fcSEvan Bacon // potentially adding the component stack. React then passes it back to the 13026ad19fcSEvan Bacon // React Native ExceptionsManager, which reports it to LogBox as an error. 13126ad19fcSEvan Bacon // 13226ad19fcSEvan Bacon // The 'warning' module needs to be handled here because React internally calls 13326ad19fcSEvan Bacon // `console.error('Warning: ')` with the component stack already included. 13426ad19fcSEvan Bacon originalConsoleError?.(...args); 13526ad19fcSEvan Bacon return; 13626ad19fcSEvan Bacon } 13726ad19fcSEvan Bacon 13826ad19fcSEvan Bacon const { category, message, componentStack } = parseLogBoxLog(args); 13926ad19fcSEvan Bacon 14026ad19fcSEvan Bacon if (!LogBoxData.isMessageIgnored(message.content)) { 14126ad19fcSEvan Bacon // Interpolate the message so they are formatted for adb and other CLIs. 14226ad19fcSEvan Bacon // This is different than the message.content above because it includes component stacks. 14326ad19fcSEvan Bacon const interpolated = parseInterpolation(args); 14426ad19fcSEvan Bacon originalConsoleError?.(interpolated.message.content); 14526ad19fcSEvan Bacon 14626ad19fcSEvan Bacon LogBoxData.addLog({ 14726ad19fcSEvan Bacon // Always show the static rendering issues as full screen since they 14826ad19fcSEvan Bacon // are too confusing otherwise. 14926ad19fcSEvan Bacon level: /did not match\. Server:/.test(message.content) ? 'fatal' : 'error', 15026ad19fcSEvan Bacon category, 15126ad19fcSEvan Bacon message, 15226ad19fcSEvan Bacon componentStack, 15326ad19fcSEvan Bacon }); 15426ad19fcSEvan Bacon } 15526ad19fcSEvan Bacon } catch (err) { 15626ad19fcSEvan Bacon LogBoxData.reportUnexpectedLogBoxError(err); 15726ad19fcSEvan Bacon } 15826ad19fcSEvan Bacon }; 15926ad19fcSEvan Bacon} else { 16026ad19fcSEvan Bacon LogBox = { 16126ad19fcSEvan Bacon install(): void {}, 16226ad19fcSEvan Bacon uninstall(): void {}, 16326ad19fcSEvan Bacon isInstalled(): boolean { 16426ad19fcSEvan Bacon return false; 16526ad19fcSEvan Bacon }, 16626ad19fcSEvan Bacon ignoreLogs(patterns: readonly IgnorePattern[]): void {}, 16726ad19fcSEvan Bacon ignoreAllLogs(value?: boolean): void {}, 16826ad19fcSEvan Bacon clearAllLogs(): void {}, 16926ad19fcSEvan Bacon addLog(log: LogData): void {}, 17026ad19fcSEvan Bacon addException(ex: ExtendedExceptionData): void {}, 17126ad19fcSEvan Bacon }; 17226ad19fcSEvan Bacon} 17326ad19fcSEvan Bacon 17426ad19fcSEvan Baconexport default LogBox; 175