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