diff --git a/packages/internal-test-utils/consoleMock.js b/packages/internal-test-utils/consoleMock.js index 45a59d2b4cb29..328cf3d90d0c4 100644 --- a/packages/internal-test-utils/consoleMock.js +++ b/packages/internal-test-utils/consoleMock.js @@ -418,13 +418,18 @@ export function createLogAssertion( let argIndex = 0; // console.* could have been called with a non-string e.g. `console.error(new Error())` // eslint-disable-next-line react-internal/safe-string-coercion - String(format).replace(/%s/g, () => argIndex++); + String(format).replace(/%s|%c/g, () => argIndex++); if (argIndex !== args.length) { - logsMismatchingFormat.push({ - format, - args, - expectedArgCount: argIndex, - }); + if (format.includes('%c%s')) { + // We intentionally use mismatching formatting when printing badging because we don't know + // the best default to use for different types because the default varies by platform. + } else { + logsMismatchingFormat.push({ + format, + args, + expectedArgCount: argIndex, + }); + } } // Check for extra component stacks diff --git a/packages/internal-test-utils/shouldIgnoreConsoleError.js b/packages/internal-test-utils/shouldIgnoreConsoleError.js index 383650d25a0b7..0b5798d241a1d 100644 --- a/packages/internal-test-utils/shouldIgnoreConsoleError.js +++ b/packages/internal-test-utils/shouldIgnoreConsoleError.js @@ -3,6 +3,10 @@ module.exports = function shouldIgnoreConsoleError(format, args) { if (__DEV__) { if (typeof format === 'string') { + if (format.startsWith('%c%s')) { + // Looks like a badged error message + args.splice(0, 3); + } if ( args[0] != null && ((typeof args[0] === 'object' && diff --git a/packages/react-client/src/ReactFlightClientConsoleConfigBrowser.js b/packages/react-client/src/ReactClientConsoleConfigBrowser.js similarity index 82% rename from packages/react-client/src/ReactFlightClientConsoleConfigBrowser.js rename to packages/react-client/src/ReactClientConsoleConfigBrowser.js index cc934685c8f7b..da87324b6df27 100644 --- a/packages/react-client/src/ReactFlightClientConsoleConfigBrowser.js +++ b/packages/react-client/src/ReactClientConsoleConfigBrowser.js @@ -7,6 +7,8 @@ * @flow */ +import {warn, error} from 'shared/consoleWithStackDev'; + const badgeFormat = '%c%s%c '; // Same badge styling as DevTools. const badgeStyle = @@ -63,7 +65,12 @@ export function printToConsole( ); } - // eslint-disable-next-line react-internal/no-production-logging - console[methodName].apply(console, newArgs); - return; + if (methodName === 'error') { + error.apply(console, newArgs); + } else if (methodName === 'warn') { + warn.apply(console, newArgs); + } else { + // eslint-disable-next-line react-internal/no-production-logging + console[methodName].apply(console, newArgs); + } } diff --git a/packages/react-client/src/ReactFlightClientConsoleConfigPlain.js b/packages/react-client/src/ReactClientConsoleConfigPlain.js similarity index 76% rename from packages/react-client/src/ReactFlightClientConsoleConfigPlain.js rename to packages/react-client/src/ReactClientConsoleConfigPlain.js index 1dbdec54cd078..a4e7c3c6d7e3c 100644 --- a/packages/react-client/src/ReactFlightClientConsoleConfigPlain.js +++ b/packages/react-client/src/ReactClientConsoleConfigPlain.js @@ -7,6 +7,8 @@ * @flow */ +import {warn, error} from 'shared/consoleWithStackDev'; + const badgeFormat = '[%s] '; const pad = ' '; @@ -44,7 +46,12 @@ export function printToConsole( newArgs.splice(offset, 0, badgeFormat, pad + badgeName + pad); } - // eslint-disable-next-line react-internal/no-production-logging - console[methodName].apply(console, newArgs); - return; + if (methodName === 'error') { + error.apply(console, newArgs); + } else if (methodName === 'warn') { + warn.apply(console, newArgs); + } else { + // eslint-disable-next-line react-internal/no-production-logging + console[methodName].apply(console, newArgs); + } } diff --git a/packages/react-client/src/ReactFlightClientConsoleConfigServer.js b/packages/react-client/src/ReactClientConsoleConfigServer.js similarity index 83% rename from packages/react-client/src/ReactFlightClientConsoleConfigServer.js rename to packages/react-client/src/ReactClientConsoleConfigServer.js index 7567483245fa6..f6ecad92f3eef 100644 --- a/packages/react-client/src/ReactFlightClientConsoleConfigServer.js +++ b/packages/react-client/src/ReactClientConsoleConfigServer.js @@ -7,6 +7,8 @@ * @flow */ +import {warn, error} from 'shared/consoleWithStackDev'; + // This flips color using ANSI, then sets a color styling, then resets. const badgeFormat = '\x1b[0m\x1b[7m%c%s\x1b[0m%c '; // Same badge styling as DevTools. @@ -64,7 +66,12 @@ export function printToConsole( ); } - // eslint-disable-next-line react-internal/no-production-logging - console[methodName].apply(console, newArgs); - return; + if (methodName === 'error') { + error.apply(console, newArgs); + } else if (methodName === 'warn') { + warn.apply(console, newArgs); + } else { + // eslint-disable-next-line react-internal/no-production-logging + console[methodName].apply(console, newArgs); + } } diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 866805b5578c1..7d421f0422b56 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -1730,6 +1730,7 @@ function resolveErrorDev( digest: string, message: string, stack: string, + env: string, ): void { if (!__DEV__) { // These errors should never make it into a build so we don't need to encode them in codes.json @@ -1769,6 +1770,7 @@ function resolveErrorDev( } (error: any).digest = digest; + (error: any).environmentName = env; const errorWithDigest: ErrorWithDigest = (error: any); const chunks = response._chunks; const chunk = chunks.get(id); @@ -2056,6 +2058,8 @@ function resolveConsoleEntry( task.run(callStack); return; } + // TODO: Set the current owner so that consoleWithStackDev adds the component + // stack during the replay - if needed. } const rootTask = response._debugRootTask; if (rootTask != null) { @@ -2198,6 +2202,7 @@ function processFullRow( errorInfo.digest, errorInfo.message, errorInfo.stack, + errorInfo.env, ); } else { resolveErrorProd(response, id, errorInfo.digest); diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index bd2d98736addd..8a652dab4aaa8 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -127,6 +127,7 @@ describe('ReactFlight', () => { this.props.expectedMessage, ); expect(this.state.error.digest).toBe('a dev digest'); + expect(this.state.error.environmentName).toBe('Server'); } else { expect(this.state.error.message).toBe( 'An error occurred in the Server Components render. The specific message is omitted in production' + @@ -143,6 +144,7 @@ describe('ReactFlight', () => { expectedDigest = '[]'; } expect(this.state.error.digest).toContain(expectedDigest); + expect(this.state.error.environmentName).toBe(undefined); expect(this.state.error.stack).toBe( 'Error: ' + this.state.error.message, ); diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js index 87d87ea523e59..7ae8d5f5cdc7d 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; -export * from 'react-client/src/ReactFlightClientConsoleConfigBrowser'; +export * from 'react-client/src/ReactClientConsoleConfigBrowser'; export * from 'react-server-dom-esm/src/ReactFlightClientConfigBundlerESM'; export * from 'react-server-dom-esm/src/ReactFlightClientConfigTargetESMBrowser'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js index 97b4afd13a835..28e2489cf22bb 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; -export * from 'react-client/src/ReactFlightClientConsoleConfigBrowser'; +export * from 'react-client/src/ReactClientConsoleConfigBrowser'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackBrowser'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackBrowser'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js index 51c832bff43a3..3a5ec6800fd57 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; -export * from 'react-client/src/ReactFlightClientConsoleConfigBrowser'; +export * from 'react-client/src/ReactClientConsoleConfigBrowser'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackBrowser'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js index 50713ae8e8e68..461996a2e0887 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; -export * from 'react-client/src/ReactFlightClientConsoleConfigPlain'; +export * from 'react-client/src/ReactClientConsoleConfigPlain'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; export type Response = any; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js index 269f8ec0c2313..c08af0a6531de 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; -export * from 'react-client/src/ReactFlightClientConsoleConfigServer'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js index cafa02b686214..db8da42686215 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; -export * from 'react-client/src/ReactFlightClientConsoleConfigServer'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js index 017dc33081d5f..ddf6440a20e48 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; -export * from 'react-client/src/ReactFlightClientConsoleConfigBrowser'; +export * from 'react-client/src/ReactClientConsoleConfigBrowser'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; export type Response = any; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js index 6c68ae163bb51..bf2071d6fc864 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigNode'; -export * from 'react-client/src/ReactFlightClientConsoleConfigServer'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-esm/src/ReactFlightClientConfigBundlerESM'; export * from 'react-server-dom-esm/src/ReactFlightClientConfigTargetESMServer'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack-bundled.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack-bundled.js index f1e7d66ee8117..16f649249dd37 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack-bundled.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack-bundled.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigNode'; -export * from 'react-client/src/ReactFlightClientConsoleConfigServer'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js index c6da80ef6060f..68047af97b9f8 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigNode'; -export * from 'react-client/src/ReactFlightClientConsoleConfigServer'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerNode'; export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js index 95fd1590ab5c6..37a5322140a8a 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigNode'; -export * from 'react-client/src/ReactFlightClientConsoleConfigServer'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js index 41c7e8e1d4706..867612c0ac2d7 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js @@ -8,7 +8,7 @@ */ export * from 'react-client/src/ReactFlightClientStreamConfigNode'; -export * from 'react-client/src/ReactFlightClientConsoleConfigServer'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerNode'; export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index e512fba13fc5d..a7cfc94e00a15 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -635,6 +635,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { NotPendingTransition: (null: TransitionStatus), resetFormInstance(form: Instance) {}, + + printToConsole(methodName, args, badgeName) { + // eslint-disable-next-line react-internal/no-production-logging + console[methodName].apply(console, args); + }, }; const hostConfig = useMutation diff --git a/packages/react-reconciler/src/ReactFiberErrorLogger.js b/packages/react-reconciler/src/ReactFiberErrorLogger.js index a948e5c79b694..addb0aea43dcd 100644 --- a/packages/react-reconciler/src/ReactFiberErrorLogger.js +++ b/packages/react-reconciler/src/ReactFiberErrorLogger.js @@ -20,6 +20,8 @@ import ReactSharedInternals from 'shared/ReactSharedInternals'; import {enableOwnerStacks} from 'shared/ReactFeatureFlags'; +import {printToConsole} from './ReactFiberConfig'; + // Side-channel since I'm not sure we want to make this part of the public API let componentName: null | string = null; let errorBoundaryName: null | string = null; @@ -94,13 +96,33 @@ export function defaultOnCaughtError( }.`; if (enableOwnerStacks) { - console.error( - '%o\n\n%s\n\n%s\n', - error, - componentNameMessage, - recreateMessage, - // We let our consoleWithStackDev wrapper add the component stack to the end. - ); + if ( + typeof error === 'object' && + error !== null && + typeof error.environmentName === 'string' + ) { + // This was a Server error. We print the environment name in a badge just like we do with + // replays of console logs to indicate that the source of this throw as actually the Server. + printToConsole( + 'error', + [ + '%o\n\n%s\n\n%s\n', + error, + componentNameMessage, + recreateMessage, + // We let our consoleWithStackDev wrapper add the component stack to the end. + ], + error.environmentName, + ); + } else { + console.error( + '%o\n\n%s\n\n%s\n', + error, + componentNameMessage, + recreateMessage, + // We let our consoleWithStackDev wrapper add the component stack to the end. + ); + } } else { // The current Fiber is disconnected at this point which means that console printing // cannot add a component stack since it terminates at the deletion node. This is not diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.art.js b/packages/react-reconciler/src/forks/ReactFiberConfig.art.js index 867ca996ec5af..1fb43b5ade8d3 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.art.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.art.js @@ -8,3 +8,4 @@ */ export * from 'react-art/src/ReactFiberConfigART'; +export * from 'react-client/src/ReactClientConsoleConfigBrowser'; diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js index 0bfe93a008cf1..24c80469c72a5 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js @@ -80,6 +80,7 @@ export const suspendInstance = $$$config.suspendInstance; export const waitForCommitToBeReady = $$$config.waitForCommitToBeReady; export const NotPendingTransition = $$$config.NotPendingTransition; export const resetFormInstance = $$$config.resetFormInstance; +export const printToConsole = $$$config.printToConsole; // ------------------- // Microtasks diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.dom.js b/packages/react-reconciler/src/forks/ReactFiberConfig.dom.js index 4932b1a787bb9..fa7dbe3123284 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.dom.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.dom.js @@ -8,3 +8,4 @@ */ export * from 'react-dom-bindings/src/client/ReactFiberConfigDOM'; +export * from 'react-client/src/ReactClientConsoleConfigBrowser'; diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.fabric.js b/packages/react-reconciler/src/forks/ReactFiberConfig.fabric.js index f1787a68e845b..2fb8768972da4 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.fabric.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.fabric.js @@ -8,3 +8,4 @@ */ export * from 'react-native-renderer/src/ReactFiberConfigFabric'; +export * from 'react-client/src/ReactClientConsoleConfigPlain'; diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.native.js b/packages/react-reconciler/src/forks/ReactFiberConfig.native.js index 3f8a28688b716..3e06abc660ef7 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.native.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.native.js @@ -8,3 +8,4 @@ */ export * from 'react-native-renderer/src/ReactFiberConfigNative'; +export * from 'react-client/src/ReactClientConsoleConfigPlain'; diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.test.js b/packages/react-reconciler/src/forks/ReactFiberConfig.test.js index 85020417c2c7d..238434a50df8c 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.test.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.test.js @@ -8,3 +8,4 @@ */ export * from 'react-test-renderer/src/ReactFiberConfigTestHost'; +export * from 'react-client/src/ReactClientConsoleConfigPlain'; diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index eedae9a46f1b0..75b195e1e25fb 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -78,6 +78,7 @@ import { resetResumableState, completeResumableState, emitEarlyPreloads, + printToConsole, } from './ReactFizzConfig'; import { constructClassInstance, @@ -363,7 +364,17 @@ export opaque type Request = { const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800; function defaultErrorHandler(error: mixed) { - console['error'](error); // Don't transform to our wrapper + if ( + typeof error === 'object' && + error !== null && + typeof error.environmentName === 'string' + ) { + // This was a Server error. We print the environment name in a badge just like we do with + // replays of console logs to indicate that the source of this throw as actually the Server. + printToConsole('error', [error], error.environmentName); + } else { + console['error'](error); // Don't transform to our wrapper + } return null; } diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index cb138fd30a710..02847f204bc3e 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -2774,11 +2774,18 @@ function emitErrorChunk( if (__DEV__) { let message; let stack = ''; + let env = request.environmentName(); try { if (error instanceof Error) { // eslint-disable-next-line react-internal/safe-string-coercion message = String(error.message); stack = getStack(error); + const errorEnv = (error: any).environmentName; + if (typeof errorEnv === 'string') { + // This probably came from another FlightClient as a pass through. + // Keep the environment name. + env = errorEnv; + } } else if (typeof error === 'object' && error !== null) { message = describeObjectForErrorMessage(error); } else { @@ -2788,7 +2795,7 @@ function emitErrorChunk( } catch (x) { message = 'An error occurred but serializing the error message failed.'; } - errorInfo = {digest, message, stack}; + errorInfo = {digest, message, stack, env}; } else { errorInfo = {digest}; } diff --git a/packages/react-server/src/forks/ReactFizzConfig.custom.js b/packages/react-server/src/forks/ReactFizzConfig.custom.js index 07ebb3295f32f..c7964f187d42c 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.custom.js +++ b/packages/react-server/src/forks/ReactFizzConfig.custom.js @@ -40,6 +40,8 @@ export const isPrimaryRenderer = false; export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); +export const printToConsole = $$$config.printToConsole; + export const resetResumableState = $$$config.resetResumableState; export const completeResumableState = $$$config.completeResumableState; export const getChildFormatContext = $$$config.getChildFormatContext; diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js b/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js index 7c5ba9bce7e27..244202002edd7 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom-edge.js @@ -10,6 +10,8 @@ import type {Request} from 'react-server/src/ReactFizzServer'; export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; + // For now, we get this from the global scope, but this will likely move to a module. export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; export const requestStorage: AsyncLocalStorage = diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js b/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js index 84d49396efcdf..5695669839f81 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js @@ -10,5 +10,7 @@ import type {Request} from 'react-server/src/ReactFizzServer'; export * from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy'; +export * from 'react-client/src/ReactClientConsoleConfigPlain'; + export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-node.js b/packages/react-server/src/forks/ReactFizzConfig.dom-node.js index 8c9718e8234c3..5ee4566ad09ba 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom-node.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom-node.js @@ -13,6 +13,8 @@ import type {Request} from 'react-server/src/ReactFizzServer'; export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; +export * from 'react-client/src/ReactClientConsoleConfigServer'; + export const supportsRequestStorage = true; export const requestStorage: AsyncLocalStorage = new AsyncLocalStorage(); diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom.js b/packages/react-server/src/forks/ReactFizzConfig.dom.js index 2bf9be13273d6..17ddc166a7922 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.dom.js +++ b/packages/react-server/src/forks/ReactFizzConfig.dom.js @@ -10,5 +10,7 @@ import type {Request} from 'react-server/src/ReactFizzServer'; export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; +export * from 'react-client/src/ReactClientConsoleConfigBrowser'; + export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any);