diff --git a/desktop/flipper-ui-core/src/chrome/ConsoleLogs.tsx b/desktop/flipper-ui-core/src/chrome/ConsoleLogs.tsx index d3f9814a8..b1f345c16 100644 --- a/desktop/flipper-ui-core/src/chrome/ConsoleLogs.tsx +++ b/desktop/flipper-ui-core/src/chrome/ConsoleLogs.tsx @@ -9,7 +9,7 @@ import {useMemo} from 'react'; import React from 'react'; -import {Console, Hook} from 'console-feed'; +import {Console} from 'console-feed'; import type {Methods} from 'console-feed/lib/definitions/Methods'; import type {Styles} from 'console-feed/lib/definitions/Styles'; import {createState, useValue} from 'flipper-plugin'; @@ -20,33 +20,40 @@ import {Button, Dropdown, Menu, Checkbox} from 'antd'; import {DownOutlined} from '@ant-design/icons'; import {DeleteOutlined} from '@ant-design/icons'; import CBuffer from 'cbuffer'; +import {addLogTailer} from '../consoleLogTailer'; +import {v4} from 'uuid'; const MAX_DISPLAY_LOG_ITEMS = 1000; const MAX_EXPORT_LOG_ITEMS = 5000; // A list5 of log items meant to be used for exporting (and subsequent debugging) only -export const exportLogs = new CBuffer(MAX_EXPORT_LOG_ITEMS); -export const displayLogsAtom = createState([]); +export const exportLogs = new CBuffer( + MAX_EXPORT_LOG_ITEMS, +); +export const displayLogsAtom = createState([]); export const errorCounterAtom = createState(0); -export function enableConsoleHook() { - Hook( - window.console, - (log) => { - exportLogs.push(log); +type ConsoleFeedLogMessage = { + id: string; + method: Methods; + data: any[]; +}; - if (log.method === 'debug') { - return; // See below, skip debug messages which are generated very aggressively by Flipper - } - const newLogs = displayLogsAtom.get().slice(-MAX_DISPLAY_LOG_ITEMS); - newLogs.push(log); - displayLogsAtom.set(newLogs); - if (log.method === 'error' || log.method === 'assert') { - errorCounterAtom.set(errorCounterAtom.get() + 1); - } - }, - false, - ); +export function enableConsoleHook() { + addLogTailer((level, ...data) => { + const logMessage = {method: level, data: data, id: v4()}; + exportLogs.push(logMessage); + + if (level === 'debug') { + return; // See below, skip debug messages which are generated very aggressively by Flipper + } + const newLogs = displayLogsAtom.get().slice(-MAX_DISPLAY_LOG_ITEMS); + newLogs.push(logMessage); + displayLogsAtom.set(newLogs); + if (level === 'error') { + errorCounterAtom.set(errorCounterAtom.get() + 1); + } + }); } function clearLogs() { diff --git a/desktop/flipper-ui-core/src/consoleLogTailer.tsx b/desktop/flipper-ui-core/src/consoleLogTailer.tsx new file mode 100644 index 000000000..aff41ecb4 --- /dev/null +++ b/desktop/flipper-ui-core/src/consoleLogTailer.tsx @@ -0,0 +1,63 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {getStringFromErrorLike, LoggerTypes} from 'flipper-common'; + +const logLevels: LoggerTypes[] = ['debug', 'info', 'warn', 'error']; + +export type LogTailer = (level: LoggerTypes, ...data: Array) => void; + +const logTailers: LogTailer[] = []; + +export function addLogTailer(handler: LogTailer) { + logTailers.push(handler); +} + +export function initLogTailer() { + const originalConsole: {[key: string]: any} = console; + //store the raw console log functions here + const originalConsoleLogFunctions: {[key: string]: (...args: any) => void} = + {} as { + [key: string]: (...args: any) => void; + }; + // React Devtools also patches the console methods: + // https://github.com/facebook/react/blob/206d61f72214e8ae5b935f0bf8628491cb7f0797/packages/react-devtools-shared/src/backend/console.js#L141 + // Not using a proxy object here because it isn't compatible with their patching process. + // Instead replace the methods on the console itself. + // Doesn't matter who patches first, single thread means it will be consistent. + for (const level of logLevels) { + const originalConsoleLogFunction = originalConsole[level]; + originalConsoleLogFunctions[level] = originalConsoleLogFunction; + const overrideMethod = (...args: Array) => { + const message = getStringFromErrorLike(args); + const newLevel = transformLogLevel(level, message); + + const newConsoleLogFunction = originalConsoleLogFunctions[newLevel]; + const result = newConsoleLogFunction(...args); + logTailers.forEach((handler) => { + handler(newLevel, ...args); + }); + return result; + }; + + overrideMethod.__FLIPPER_ORIGINAL_METHOD__ = originalConsoleLogFunction; + originalConsole[level] = overrideMethod; + } +} + +function transformLogLevel(level: LoggerTypes, message: string) { + if ( + level === 'error' && + message.includes('ResizeObserver loop limit exceeded') + ) { + return 'warn'; + } + + return level; +} diff --git a/desktop/flipper-ui-core/src/startFlipperDesktop.tsx b/desktop/flipper-ui-core/src/startFlipperDesktop.tsx index f3048503c..04b241c9b 100644 --- a/desktop/flipper-ui-core/src/startFlipperDesktop.tsx +++ b/desktop/flipper-ui-core/src/startFlipperDesktop.tsx @@ -11,6 +11,7 @@ import {Provider} from 'react-redux'; import {createRoot} from 'react-dom/client'; import {init as initLogger} from './fb-stubs/Logger'; +import {initLogTailer} from './consoleLogTailer'; import {SandyApp} from './sandy-chrome/SandyApp'; import {Persistor, persistStore} from 'redux-persist'; import dispatcher from './dispatcher/index'; @@ -139,6 +140,7 @@ class AppFrame extends React.Component< function init(flipperServer: FlipperServer) { const settings = getRenderHostInstance().serverConfig.settings; const store = getStore(); + initLogTailer(); const logger = initLogger(store); setLoggerInstance(logger);