1/** 2 * Copyright (c) 650 Industries. 3 * Copyright (c) Meta Platforms, Inc. and affiliates. 4 * 5 * This source code is licensed under the MIT license found in the 6 * LICENSE file in the root directory of this source tree. 7 */ 8 9import { IgnorePattern, LogData } from './Data/LogBoxData'; 10import { ExtendedExceptionData } from './Data/parseLogBoxLog'; 11 12export { LogData, ExtendedExceptionData, IgnorePattern }; 13 14let LogBox: ILogBox; 15 16interface ILogBox { 17 install(): void; 18 uninstall(): void; 19 isInstalled(): boolean; 20 ignoreLogs(patterns: readonly IgnorePattern[]): void; 21 ignoreAllLogs(ignore?: boolean): void; 22 clearAllLogs(): void; 23 addLog(log: LogData): void; 24 addException(error: ExtendedExceptionData): void; 25} 26 27/** 28 * LogBox displays logs in the app. 29 */ 30if (__DEV__) { 31 const LogBoxData = require('./Data/LogBoxData'); 32 const { parseLogBoxLog, parseInterpolation } = 33 require('./Data/parseLogBoxLog') as typeof import('./Data/parseLogBoxLog'); 34 35 let originalConsoleError: typeof console.error | undefined; 36 let consoleErrorImpl: typeof console.error | undefined; 37 38 let isLogBoxInstalled: boolean = false; 39 40 LogBox = { 41 install(): void { 42 if (isLogBoxInstalled) { 43 return; 44 } 45 46 isLogBoxInstalled = true; 47 48 // Trigger lazy initialization of module. 49 // require("../NativeModules/specs/NativeLogBox"); 50 51 // IMPORTANT: we only overwrite `console.error` and `console.warn` once. 52 // When we uninstall we keep the same reference and only change its 53 // internal implementation 54 const isFirstInstall = originalConsoleError == null; 55 if (isFirstInstall) { 56 originalConsoleError = console.error.bind(console); 57 58 console.error = (...args) => { 59 consoleErrorImpl?.(...args); 60 }; 61 } 62 63 consoleErrorImpl = registerError; 64 65 if (process.env.NODE_ENV === 'test') { 66 LogBoxData.setDisabled(true); 67 } 68 }, 69 70 uninstall(): void { 71 if (!isLogBoxInstalled) { 72 return; 73 } 74 75 isLogBoxInstalled = false; 76 77 // IMPORTANT: we don't re-assign to `console` in case the method has been 78 // decorated again after installing LogBox. E.g.: 79 // Before uninstalling: original > LogBox > OtherErrorHandler 80 // After uninstalling: original > LogBox (noop) > OtherErrorHandler 81 consoleErrorImpl = originalConsoleError; 82 delete (console as any).disableLogBox; 83 }, 84 85 isInstalled(): boolean { 86 return isLogBoxInstalled; 87 }, 88 89 ignoreLogs(patterns: readonly IgnorePattern[]): void { 90 LogBoxData.addIgnorePatterns(patterns); 91 }, 92 93 ignoreAllLogs(value?: boolean): void { 94 LogBoxData.setDisabled(value == null ? true : value); 95 }, 96 97 clearAllLogs(): void { 98 LogBoxData.clear(); 99 }, 100 101 addLog(log: LogData): void { 102 if (isLogBoxInstalled) { 103 LogBoxData.addLog(log); 104 } 105 }, 106 107 addException(error: ExtendedExceptionData): void { 108 if (isLogBoxInstalled) { 109 LogBoxData.addException(error); 110 } 111 }, 112 }; 113 114 const isWarningModuleWarning = (...args: any) => { 115 return typeof args[0] === 'string' && args[0].startsWith('Warning: '); 116 }; 117 118 const registerError = (...args: Parameters<typeof console.error>): void => { 119 // Let errors within LogBox itself fall through. 120 if (LogBoxData.isLogBoxErrorMessage(args[0])) { 121 originalConsoleError?.(...args); 122 return; 123 } 124 125 try { 126 if (!isWarningModuleWarning(...args)) { 127 // Only show LogBox for the 'warning' module, otherwise pass through. 128 // By passing through, this will get picked up by the React console override, 129 // potentially adding the component stack. React then passes it back to the 130 // React Native ExceptionsManager, which reports it to LogBox as an error. 131 // 132 // The 'warning' module needs to be handled here because React internally calls 133 // `console.error('Warning: ')` with the component stack already included. 134 originalConsoleError?.(...args); 135 return; 136 } 137 138 const { category, message, componentStack } = parseLogBoxLog(args); 139 140 if (!LogBoxData.isMessageIgnored(message.content)) { 141 // Interpolate the message so they are formatted for adb and other CLIs. 142 // This is different than the message.content above because it includes component stacks. 143 const interpolated = parseInterpolation(args); 144 originalConsoleError?.(interpolated.message.content); 145 146 LogBoxData.addLog({ 147 // Always show the static rendering issues as full screen since they 148 // are too confusing otherwise. 149 level: /did not match\. Server:/.test(message.content) ? 'fatal' : 'error', 150 category, 151 message, 152 componentStack, 153 }); 154 } 155 } catch (err) { 156 LogBoxData.reportUnexpectedLogBoxError(err); 157 } 158 }; 159} else { 160 LogBox = { 161 install(): void {}, 162 uninstall(): void {}, 163 isInstalled(): boolean { 164 return false; 165 }, 166 ignoreLogs(patterns: readonly IgnorePattern[]): void {}, 167 ignoreAllLogs(value?: boolean): void {}, 168 clearAllLogs(): void {}, 169 addLog(log: LogData): void {}, 170 addException(ex: ExtendedExceptionData): void {}, 171 }; 172} 173 174export default LogBox; 175