@samitouri / QOS-React-2 / commits / 5f34b051df

[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 }