Skip to content

Commit

Permalink
Restore Metro log streaming via CLI flag (#49356)
Browse files Browse the repository at this point in the history
Summary:
This change adds an opt-in to restore JavaScript log streaming via the Metro dev server, [removed from React Native core in 0.77](https://reactnative.dev/blog/2025/01/21/version-0.77#removal-of-consolelog-streaming-in-metro).

Users can opt into this legacy behaviour by adding the `--client-logs` flag to `npx react-native-community/cli start`.

- The default experience remains without streamed JS logs.
- The existing "JavaScript logs have moved! ..." notice is printed in all cases, and we do not advertise the new flag for new users.
- Under non-Community CLI dev servers (i.e. Expo), log streaming is restored implicitly.

We will clean up this functionality again when we eventually remove JS log streaming over `HMRClient`, tasked in T214991636.

**Implementation notes**

- Logs are always sent over `HMRClient` (previous status quo), even with log streaming off in the dev server. This is a necessary evil to be able to flag this functionality in a user-accessible place, and to move fast for 0.78.
- Necessarily, emitting `fusebox_console_notice` moves to the dev server itself, on first device (Fusebox) connection.

Changelog:
[General][Added] - Add opt in for legacy Metro log streaming via `--client-logs` flag
  • Loading branch information
huntie authored Feb 12, 2025
1 parent 475f797 commit 969eb3f
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 38 deletions.
5 changes: 3 additions & 2 deletions packages/community-cli-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Start the React Native development server.
#### Usage

```sh
npx react-native start [options]
npx @react-native-community/cli start [options]
```

#### Options
Expand All @@ -37,6 +37,7 @@ npx react-native start [options]
| `--cert <path>` | Specify path to a custom SSL cert. |
| `--config <string>` | Path to the CLI configuration file. |
| `--no-interactive` | Disable interactive mode. |
| `--client-logs` | **[Deprecated]** Enable plain text JavaScript log streaming for all connected apps. |

### `bundle`

Expand All @@ -45,7 +46,7 @@ Build the bundle for the provided JavaScript entry file.
#### Usage

```sh
npx react-native bundle --entry-file <path> [options]
npx @react-native-community/cli bundle --entry-file <path> [options]
```

#### Options
Expand Down
8 changes: 8 additions & 0 deletions packages/community-cli-plugin/src/commands/start/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ const startCommand: Command = {
name: '--no-interactive',
description: 'Disables interactive mode',
},
{
name: '--client-logs',
description:
'[Deprecated] Enable plain text JavaScript log streaming for all ' +
'connected apps. This feature is deprecated and will be removed in ' +
'future.',
default: false,
},
],
};

Expand Down
6 changes: 6 additions & 0 deletions packages/community-cli-plugin/src/commands/start/runServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type StartCommandArgs = {
config?: string,
projectRoot?: string,
interactive: boolean,
clientLogs: boolean,
};

async function runServer(
Expand Down Expand Up @@ -96,6 +97,11 @@ async function runServer(
require.resolve(plugin),
);
}
// TODO(T214991636): Remove legacy Metro log forwarding
if (!args.clientLogs) {
// $FlowIgnore[cannot-write] Assigning to readonly property
metroConfig.server.forwardClientLogs = false;
}

let reportEvent: (event: TerminalReportableEvent) => void;
const terminal = new Terminal(process.stdout);
Expand Down
39 changes: 36 additions & 3 deletions packages/dev-middleware/src/createDevMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import type {CreateCustomMessageHandlerFn} from './inspector-proxy/CustomMessageHandler';
import type {BrowserLauncher} from './types/BrowserLauncher';
import type {EventReporter} from './types/EventReporter';
import type {EventReporter, ReportableEvent} from './types/EventReporter';
import type {Experiments, ExperimentsConfig} from './types/Experiments';
import type {Logger} from './types/Logger';
import type {NextHandleFunction} from 'connect';
Expand Down Expand Up @@ -81,11 +81,15 @@ export default function createDevMiddleware({
unstable_customInspectorMessageHandler,
}: Options): DevMiddlewareAPI {
const experiments = getExperiments(experimentConfig);
const eventReporter = createWrappedEventReporter(
unstable_eventReporter,
logger,
);

const inspectorProxy = new InspectorProxy(
projectRoot,
serverBaseUrl,
unstable_eventReporter,
eventReporter,
experiments,
unstable_customInspectorMessageHandler,
);
Expand All @@ -97,7 +101,7 @@ export default function createDevMiddleware({
serverBaseUrl,
inspectorProxy,
browserLauncher: unstable_browserLauncher,
eventReporter: unstable_eventReporter,
eventReporter,
experiments,
logger,
}),
Expand Down Expand Up @@ -129,3 +133,32 @@ function getExperiments(config: ExperimentsConfig): Experiments {
enableNetworkInspector: config.enableNetworkInspector ?? false,
};
}

/**
* Creates a wrapped EventReporter that locally intercepts events to
* log to the terminal.
*/
function createWrappedEventReporter(
reporter: ?EventReporter,
logger: ?Logger,
): EventReporter {
return {
logEvent(event: ReportableEvent) {
switch (event.type) {
case 'fusebox_console_notice':
logger?.info(
'\n' +
'\u001B[7m' +
' \u001B[1m💡 JavaScript logs have moved!\u001B[22m They can now be ' +
'viewed in React Native DevTools. Tip: Type \u001B[1mj\u001B[22m in ' +
'the terminal to open (requires Google Chrome or Microsoft Edge).' +
'\u001B[27m' +
'\n',
);
break;
}

reporter?.logEvent(event);
},
};
}
13 changes: 13 additions & 0 deletions packages/dev-middleware/src/inspector-proxy/Device.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const PAGES_POLLING_INTERVAL = 1000;
// more details.
const FILE_PREFIX = 'file://';

let fuseboxConsoleNoticeLogged = false;

type DebuggerConnection = {
// Debugger web socket connection
socket: WS,
Expand Down Expand Up @@ -513,6 +515,7 @@ export default class Device {
// created instead of manually checking this on every getPages result.
for (const page of this.#pages.values()) {
if (this.#pageHasCapability(page, 'nativePageReloads')) {
this.#logFuseboxConsoleNotice();
continue;
}

Expand Down Expand Up @@ -1067,4 +1070,14 @@ export default class Device {
dangerouslyGetSocket(): WS {
return this.#deviceSocket;
}

// TODO(T214991636): Remove notice
#logFuseboxConsoleNotice() {
if (fuseboxConsoleNoticeLogged) {
return;
}

this.#deviceEventReporter?.logFuseboxConsoleNotice();
fuseboxConsoleNoticeLogged = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ class DeviceEventReporter {
});
}

logFuseboxConsoleNotice(): void {
this.#eventReporter.logEvent({
type: 'fusebox_console_notice',
});
}

#logExpiredCommand(pendingCommand: PendingCommand): void {
this.#eventReporter.logEvent({
type: 'debugger_command',
Expand Down
3 changes: 3 additions & 0 deletions packages/dev-middleware/src/types/EventReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export type ReportableEvent =
| 'PROTOCOL_ERROR',
>,
}
| {
type: 'fusebox_console_notice',
}
| {
type: 'proxy_error',
status: 'error',
Expand Down
5 changes: 2 additions & 3 deletions packages/react-native/Libraries/Core/setUpDeveloperTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ if (__DEV__) {
if (!Platform.isTesting) {
const HMRClient = require('../Utilities/HMRClient');

if (global.__FUSEBOX_HAS_FULL_CONSOLE_SUPPORT__) {
HMRClient.unstable_notifyFuseboxConsoleEnabled();
} else if (console._isPolyfilled) {
// TODO(T214991636): Remove legacy Metro log forwarding
if (console._isPolyfilled) {
// We assume full control over the console and send JavaScript logs to Metro.
[
'trace',
Expand Down
28 changes: 0 additions & 28 deletions packages/react-native/Libraries/Utilities/HMRClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ let hmrUnavailableReason: string | null = null;
let currentCompileErrorMessage: string | null = null;
let didConnect: boolean = false;
let pendingLogs: Array<[LogLevel, $ReadOnlyArray<mixed>]> = [];
let pendingFuseboxConsoleNotification = false;

type LogLevel =
| 'trace'
Expand All @@ -52,7 +51,6 @@ export type HMRClientNativeInterface = {|
isEnabled: boolean,
scheme?: string,
): void,
unstable_notifyFuseboxConsoleEnabled(): void,
|};

/**
Expand Down Expand Up @@ -142,29 +140,6 @@ const HMRClient: HMRClientNativeInterface = {
}
},

unstable_notifyFuseboxConsoleEnabled() {
if (!hmrClient) {
pendingFuseboxConsoleNotification = true;
return;
}
hmrClient.send(
JSON.stringify({
type: 'log',
level: 'info',
data: [
'\n' +
'\u001B[7m' +
' \u001B[1m💡 JavaScript logs have moved!\u001B[22m They can now be ' +
'viewed in React Native DevTools. Tip: Type \u001B[1mj\u001B[22m in ' +
'the terminal to open (requires Google Chrome or Microsoft Edge).' +
'\u001B[27m' +
'\n',
],
}),
);
pendingFuseboxConsoleNotification = false;
},

// Called once by the bridge on startup, even if Fast Refresh is off.
// It creates the HMR client but doesn't actually set up the socket yet.
setup(
Expand Down Expand Up @@ -341,9 +316,6 @@ function flushEarlyLogs(client: MetroHMRClient) {
pendingLogs.forEach(([level, data]) => {
HMRClient.log(level, data);
});
if (pendingFuseboxConsoleNotification) {
HMRClient.unstable_notifyFuseboxConsoleEnabled();
}
} finally {
pendingLogs.length = 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const HMRClientProdShim: HMRClientNativeInterface = {
disable() {},
registerBundle() {},
log() {},
unstable_notifyFuseboxConsoleEnabled() {},
};

module.exports = HMRClientProdShim;
Original file line number Diff line number Diff line change
Expand Up @@ -8942,7 +8942,6 @@ export type HMRClientNativeInterface = {|
isEnabled: boolean,
scheme?: string
): void,
unstable_notifyFuseboxConsoleEnabled(): void,
|};
declare const HMRClient: HMRClientNativeInterface;
declare module.exports: HMRClient;
Expand Down

0 comments on commit 969eb3f

Please sign in to comment.