[Devtools] add logs for profiler tab switch & settings change (#24966)
* [devtools] add logs for profiler tab switch & settings change * prettier * remove unnecessary console.log * better naming: logEvent -> loggerEvent * use same object for event and metadata
Mengdi Chen committed
Jul 25, 2022 at 11:53 UTC
5f34b051dfb708ef6e8dc8e55d5e1bbd209381e8
8 files changed
+96
-20
packages/react-devtools-shared/src/Logger.js
+24
-4
@@ -9,7 +9,7 @@
9
10
import {enableLogger} from 'react-devtools-feature-flags';
11
12
-export type LogEvent =
12
+export type LoggerEvent =
13
| {|
14
+event_name: 'loaded-dev-tools',
15
|}
@@ -34,22 +34,42 @@ export type LogEvent =
34
|}
35
| {|
36
+event_name: 'select-element',
37
+ +metadata: {
38
+ +source: string,
39
+ },
40
|}
41
| {|
42
+event_name: 'inspect-element-button-clicked',
43
|}
44
| {|
45
+event_name: 'profiling-start',
46
+ +metadata: {
47
+ +current_tab: string,
48
+ },
49
+ |}
50
+ | {|
51
+ +event_name: 'profiler-tab-changed',
52
+ +metadata: {
53
+ +tabId: string,
54
+ },
55
+ |}
56
+ | {|
57
+ +event_name: 'settings-changed',
58
+ +metadata: {
59
+ +key: string,
60
+ +value: any,
61
+ ...
62
+ },
63
|};
64
45
-export type LogFunction = (LogEvent, ?Object) => void | Promise<void>;
65
+export type LogFunction = LoggerEvent => void | Promise<void>;
66
67
let logFunctions: Array<LogFunction> = [];
68
export const logEvent: LogFunction =
69
enableLogger === true
50
- ? function logEvent(event: LogEvent, metadata: ?Object): void {
70
+ ? function logEvent(event: LoggerEvent): void {
71
logFunctions.forEach(log => {
52
- log(event, metadata);
72
+ log(event);
73
});
74
}
75
: function logEvent() {};
packages/react-devtools-shared/src/devtools/views/Components/Element.js
+4
-1
@@ -77,7 +77,10 @@ export default function Element({data, index, style}: Props) {
77
78
const handleClick = ({metaKey}) => {
79
if (id !== null) {
80
- logEvent({event_name: 'select-element'}, {source: 'click-element'});
80
+ logEvent({
81
+ event_name: 'select-element',
82
+ metadata: {source: 'click-element'},
83
+ });
84
dispatch({
85
type: 'SELECT_ELEMENT_BY_ID',
86
payload: metaKey ? null : id,
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js
+4
-1
@@ -303,7 +303,10 @@ function OwnerView({
303
} = useHighlightNativeElement();
304
305
const handleClick = useCallback(() => {
306
- logEvent({event_name: 'select-element'}, {source: 'owner-view'});
306
+ logEvent({
307
+ event_name: 'select-element',
308
+ metadata: {source: 'owner-view'},
309
+ });
310
dispatch({
311
type: 'SELECT_ELEMENT_BY_ID',
312
payload: id,
packages/react-devtools-shared/src/devtools/views/Components/Tree.js
+4
-1
@@ -104,7 +104,10 @@ export default function Tree(props: Props) {
104
function handleStopInspectingNative(didSelectNode) {
105
if (didSelectNode && focusTargetRef.current !== null) {
106
focusTargetRef.current.focus();
107
- logEvent({event_name: 'select-element'}, {source: 'inspector'});
107
+ logEvent({
108
+ event_name: 'select-element',
109
+ metadata: {source: 'inspector'},
110
+ });
111
}
112
}
113
bridge.addListener('stopInspectingNative', handleStopInspectingNative);
packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+12
-1
@@ -207,10 +207,21 @@ function ProfilerContextController({children}: Props) {
207
const [selectedTabID, selectTab] = useLocalStorage<TabID>(
208
'React::DevTools::Profiler::defaultTab',
209
'flame-chart',
210
+ value => {
211
+ logEvent({
212
+ event_name: 'profiler-tab-changed',
213
+ metadata: {
214
+ tabId: value,
215
+ },
216
+ });
217
+ },
218
);
219
220
const startProfiling = useCallback(() => {
213
- logEvent({event_name: 'profiling-start'}, {current_tab: selectedTabID});
221
+ logEvent({
222
+ event_name: 'profiling-start',
223
+ metadata: {current_tab: selectedTabID},
224
+ });
225
store.profilerStore.startProfiling();
226
}, [store, selectedTabID]);
227
const stopProfiling = useCallback(() => store.profilerStore.stopProfiling(), [
packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
+34
-8
@@ -27,6 +27,7 @@ import {
27
} from 'react-devtools-shared/src/constants';
28
import {useLocalStorage} from '../hooks';
29
import {BridgeContext} from '../context';
30
+import {logEvent} from 'react-devtools-shared/src/Logger';
31
32
import type {BrowserTheme} from '../DevTools';
33
@@ -68,6 +69,22 @@ type Context = {|
69
const SettingsContext = createContext<Context>(((null: any): Context));
70
SettingsContext.displayName = 'SettingsContext';
71
72
+function useLocalStorageWithLog<T>(
73
+ key: string,
74
+ initialValue: T | (() => T),
75
+): [T, (value: T | (() => T)) => void] {
76
+ return useLocalStorage<T>(key, initialValue, (v, k) => {
77
+ logEvent({
78
+ event_name: 'settings-changed',
79
+ metadata: {
80
+ source: 'localStorage setter',
81
+ key: k,
82
+ value: v,
83
+ },
84
+ });
85
+ });
86
+}
87
+
88
type DocumentElements = Array<HTMLElement>;
89
90
type Props = {|
@@ -85,47 +102,56 @@ function SettingsContextController({
102
}: Props) {
103
const bridge = useContext(BridgeContext);
104
88
- const [displayDensity, setDisplayDensity] = useLocalStorage<DisplayDensity>(
105
+ const [
106
+ displayDensity,
107
+ setDisplayDensity,
108
+ ] = useLocalStorageWithLog<DisplayDensity>(
109
'React::DevTools::displayDensity',
110
'compact',
111
);
92
- const [theme, setTheme] = useLocalStorage<Theme>(
112
+ const [theme, setTheme] = useLocalStorageWithLog<Theme>(
113
'React::DevTools::theme',
114
'auto',
115
);
116
const [
117
appendComponentStack,
118
setAppendComponentStack,
99
- ] = useLocalStorage<boolean>(LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY, true);
119
+ ] = useLocalStorageWithLog<boolean>(
120
+ LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
121
+ true,
122
+ );
123
const [
124
breakOnConsoleErrors,
125
setBreakOnConsoleErrors,
103
- ] = useLocalStorage<boolean>(
126
+ ] = useLocalStorageWithLog<boolean>(
127
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
128
false,
129
);
107
- const [parseHookNames, setParseHookNames] = useLocalStorage<boolean>(
130
+ const [parseHookNames, setParseHookNames] = useLocalStorageWithLog<boolean>(
131
LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
132
false,
133
);
134
const [
135
hideConsoleLogsInStrictMode,
136
sethideConsoleLogsInStrictMode,
114
- ] = useLocalStorage<boolean>(
137
+ ] = useLocalStorageWithLog<boolean>(
138
LOCAL_STORAGE_HIDE_CONSOLE_LOGS_IN_STRICT_MODE,
139
false,
140
);
141
const [
142
showInlineWarningsAndErrors,
143
setShowInlineWarningsAndErrors,
121
- ] = useLocalStorage<boolean>(
144
+ ] = useLocalStorageWithLog<boolean>(
145
LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
146
true,
147
);
148
const [
149
traceUpdatesEnabled,
150
setTraceUpdatesEnabled,
128
- ] = useLocalStorage<boolean>(LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY, false);
151
+ ] = useLocalStorageWithLog<boolean>(
152
+ LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
153
+ false,
154
+ );
155
156
const documentElements = useMemo<DocumentElements>(() => {
157
const array: Array<HTMLElement> = [
packages/react-devtools-shared/src/devtools/views/hooks.js
+5
@@ -144,6 +144,7 @@ export function useIsOverflowing(
144
export function useLocalStorage<T>(
145
key: string,
146
initialValue: T | (() => T),
147
+ onValueSet?: (any, string) => void,
148
): [T, (value: T | (() => T)) => void] {
149
const getValueFromLocalStorage = useCallback(() => {
150
try {
@@ -173,6 +174,10 @@ export function useLocalStorage<T>(
174
175
// Notify listeners that this setting has changed.
176
window.dispatchEvent(new Event(key));
177
+
178
+ if (onValueSet != null) {
179
+ onValueSet(valueToStore, key);
180
+ }
181
} catch (error) {
182
console.log(error);
183
}
packages/react-devtools-shared/src/registerDevToolsEventLogger.js
+9
-4
@@ -7,7 +7,7 @@
7
* @flow strict-local
8
*/
9
10
-import type {LogEvent} from 'react-devtools-shared/src/Logger';
10
+import type {LoggerEvent} from 'react-devtools-shared/src/Logger';
11
12
import {registerEventLogger} from 'react-devtools-shared/src/Logger';
13
import {enableLogger} from 'react-devtools-feature-flags';
@@ -25,9 +25,14 @@ export function registerDevToolsEventLogger(
25
| LoggerContext
26
| ?(() => Promise<LoggerContext>),
27
): void {
28
- async function logEvent(event: LogEvent, metadata: ?Object) {
28
+ async function logEvent(event: LoggerEvent) {
29
if (enableLogger) {
30
if (loggingIFrame != null) {
31
+ let metadata = null;
32
+ if (event.metadata != null) {
33
+ metadata = event.metadata;
34
+ delete event.metadata;
35
+ }
36
loggingIFrame.contentWindow.postMessage(
37
{
38
source: 'react-devtools-logging',
@@ -35,7 +40,7 @@ export function registerDevToolsEventLogger(
40
context: {
41
surface,
42
version: process.env.DEVTOOLS_VERSION,
38
- metadata: metadata != null ? JSON.stringify(metadata) : '',
43
+ metadata: metadata !== null ? JSON.stringify(metadata) : '',
44
...(fetchAdditionalContext != null
45
? await fetchAdditionalContext()
46
: {}),
@@ -56,7 +61,7 @@ export function registerDevToolsEventLogger(
61
62
loggingIFrame = iframe;
63
if (missedEvents.length > 0) {
59
- missedEvents.forEach(logEvent);
64
+ missedEvents.forEach(event => logEvent(event));
65
missedEvents = [];
66
}
67
}