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