Introduced first class console to help users debugging issues (#1479)

Summary:
Handling issues typically start with: did you look at the Electron logs? Since Flipper is such an extensible tool, running in varying environments I think the console should be support as first class concept. Many errors are currently not shown to the user. This PR is a first attempt to fix that.

The implementation is based on https://github.com/samdenty/console-feed, which is used by quite some web based IDE like tools (like codesandbox), and offers a lot of goodies out of the box, like collapsing errors, objects, etc.

Edit: also added a counter keeping track of the amount of errors

N.B. no need to immediately review this diff, I'll import it to phabricator as soon as I can :)

## Changelog

changelog: Introduce 'Debug Logs' section to help users to troubleshoot issues or to provide more accurate reports.

Pull Request resolved: https://github.com/facebook/flipper/pull/1479

Test Plan: ![Screenshot from 2020-08-18 15-29-55](https://user-images.githubusercontent.com/1820292/90526011-c9b22d80-e167-11ea-88cf-7b4e07918a96.png)

Reviewed By: jknoxville

Differential Revision: D23198103

Pulled By: passy

fbshipit-source-id: a2505f9fa59e10676a44ffa33312efe83c7be55d
This commit is contained in:
Michel Weststrate
2020-08-20 13:26:39 -07:00
committed by Facebook GitHub Bot
parent baa29d0b49
commit dd15cffa64
8 changed files with 185 additions and 7 deletions

View File

@@ -23,6 +23,7 @@
"archiver": "^5.0.0",
"async-mutex": "^0.1.3",
"axios": "^0.19.2",
"console-feed": "^3.0.1",
"deep-equal": "^2.0.1",
"emotion": "^10.0.23",
"expand-tilde": "^2.0.2",

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) Facebook, Inc. and its 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 {useMemo} from 'react';
import {Button, Toolbar, ButtonGroup, Layout} from '../ui';
import React from 'react';
import {Console, Hook} from 'console-feed';
import type {Methods} from 'console-feed/lib/definitions/Methods';
import {createState, useValue} from 'flipper-plugin';
import {useLocalStorage} from '../utils/useLocalStorage';
const logsAtom = createState<any[]>([]);
export const errorCounterAtom = createState(0);
export function enableConsoleHook() {
console.log('enabling hooks');
Hook(
window.console,
(log) => {
logsAtom.set([...logsAtom.get(), log]);
if (log.method === 'error' || log.method === 'assert') {
errorCounterAtom.set(errorCounterAtom.get() + 1);
}
},
false,
);
}
function clearLogs() {
logsAtom.set([]);
errorCounterAtom.set(0);
}
const allLogLevels: Methods[] = [
'log',
'debug',
'info',
'warn',
'error',
'table',
'clear',
'time',
'timeEnd',
'count',
'assert',
];
const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert'];
export function ConsoleLogs() {
const logs = useValue(logsAtom);
const [logLevels, setLogLevels] = useLocalStorage<Methods[]>(
'console-logs-loglevels',
defaultLogLevels,
);
const dropdown = useMemo(() => {
return allLogLevels.map(
(l): Electron.MenuItemConstructorOptions => ({
label: l,
checked: logLevels.includes(l),
type: 'checkbox',
click() {
setLogLevels((state) =>
state.includes(l)
? state.filter((level) => level !== l)
: [l, ...state],
);
},
}),
);
}, [logLevels, setLogLevels]);
return (
<Layout.Top scrollable>
<Toolbar>
<ButtonGroup>
<Button onClick={clearLogs} icon="trash">
Clear Logs
</Button>
<Button dropdown={dropdown}>Log Levels</Button>
</ButtonGroup>
</Toolbar>
<Console logs={logs} filter={logLevels} variant="light" />
</Layout.Top>
);
}

View File

@@ -29,6 +29,8 @@ import {
} from './sidebarUtils';
import {Group} from '../../reducers/supportForm';
import {getInstance} from '../../fb-stubs/Logger';
import {ConsoleLogs, errorCounterAtom} from '../ConsoleLogs';
import {useValue} from 'flipper-plugin';
type OwnProps = {};
@@ -103,6 +105,7 @@ function MainSidebarUtilsSection({
/>
Manage Plugins
</ListItem>
<DebugLogsEntry staticView={staticView} setStaticView={setStaticView} />
{config.showLogin && <UserAccount />}
</div>
);
@@ -171,3 +174,26 @@ const RenderNotificationsEntry = connect<
</ListItem>
);
});
function DebugLogsEntry({
staticView,
setStaticView,
}: {
staticView: StaticView;
setStaticView: (payload: StaticView) => void;
}) {
const active = isStaticViewActive(staticView, ConsoleLogs);
const errorCount = useValue(errorCounterAtom);
return (
<ListItem onClick={() => setStaticView(ConsoleLogs)} active={active}>
<PluginIcon
name="caution-octagon"
color={colors.light50}
isActive={active}
/>
<PluginName count={errorCount} isActive={active}>
Debug Logs
</PluginName>
</ListItem>
);
}

View File

@@ -85,6 +85,7 @@ const PluginShape = styled(FlexBox)<{
export const PluginName = styled(Text)<{isActive?: boolean; count?: number}>(
(props) => ({
cursor: 'default',
minWidth: 0,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',

View File

@@ -38,6 +38,7 @@ import os from 'os';
import QuickPerformanceLogger, {FLIPPER_QPL_EVENTS} from './fb-stubs/QPL';
import {PopoverProvider} from './ui/components/PopoverProvider';
import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
import {enableConsoleHook} from './chrome/ConsoleLogs';
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use
@@ -119,6 +120,7 @@ function init() {
const sessionId = store.getState().application.sessionId;
initCrashReporter(sessionId || '');
registerRecordingHooks(store);
enableConsoleHook();
window.flipperGlobalStoreDispatch = store.dispatch;
}

View File

@@ -27,13 +27,15 @@ import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils';
import {deconstructClientId} from '../utils/clientUtils';
import {PluginDefinition} from '../plugin';
import {RegisterPluginAction} from './plugins';
import {ConsoleLogs} from '../chrome/ConsoleLogs';
export type StaticView =
| null
| typeof WelcomeScreen
| typeof NotificationScreen
| typeof SupportRequestFormV2
| typeof SupportRequestDetails;
| typeof SupportRequestDetails
| typeof ConsoleLogs;
export type FlipperError = {
occurrences?: number;