@samitouri / QOS-React / commits / 50f00fd876

[Flight] Mark Errored Server Components (#31879)

This is similar to #31876 but for Server Components. It marks them as errored and puts the error message in the Summary properties. <img width="1511" alt="Screenshot 2024-12-20 at 5 05 35 PM" src="https://github.com/user-attachments/assets/92f11e42-0e23-41c7-bfd4-09effb25e024" /> This only looks at the current chunk for rejections. That means that there might still be promises deeper that rejected but it's only the immediate return value of the Server Component that's considered a rejection of the component itself.

Sebastian Markbåge committed Dec 28, 2024 at 02:02 UTC 50f00fd876b0b92b243cd8b54a222f9577446392
2 files changed +73 -8
packages/react-client/src/ReactFlightClient.js
+30 -8
@@ -73,6 +73,7 @@ import {
73 markAllTracksInOrder,
74 logComponentRender,
75 logDedupedComponentRender,
76 + logComponentErrored,
77 } from './ReactFlightPerformanceTrack';
78
79 import {
@@ -2876,6 +2877,7 @@ function flushComponentPerformance(
2877
2878 if (debugInfo) {
2879 let endTime = 0;
2880 + let isLastComponent = true;
2881 for (let i = debugInfo.length - 1; i >= 0; i--) {
2882 const info = debugInfo[i];
2883 if (typeof info.time === 'number') {
@@ -2890,17 +2892,37 @@ function flushComponentPerformance(
2892 const startTimeInfo = debugInfo[i - 1];
2893 if (typeof startTimeInfo.time === 'number') {
2894 const startTime = startTimeInfo.time;
2893 - logComponentRender(
2894 - componentInfo,
2895 - trackIdx,
2896 - startTime,
2897 - endTime,
2898 - childrenEndTime,
2899 - response._rootEnvironmentName,
2900 - );
2895 + if (
2896 + isLastComponent &&
2897 + root.status === ERRORED &&
2898 + root.reason !== response._closedReason
2899 + ) {
2900 + // If this is the last component to render before this chunk rejected, then conceptually
2901 + // this component errored. If this was a cancellation then it wasn't this component that
2902 + // errored.
2903 + logComponentErrored(
2904 + componentInfo,
2905 + trackIdx,
2906 + startTime,
2907 + endTime,
2908 + childrenEndTime,
2909 + response._rootEnvironmentName,
2910 + root.reason,
2911 + );
2912 + } else {
2913 + logComponentRender(
2914 + componentInfo,
2915 + trackIdx,
2916 + startTime,
2917 + endTime,
2918 + childrenEndTime,
2919 + response._rootEnvironmentName,
2920 + );
2921 + }
2922 // Track the root most component of the result for deduping logging.
2923 result.component = componentInfo;
2924 }
2925 + isLastComponent = false;
2926 }
2927 }
2928 }
packages/react-client/src/ReactFlightPerformanceTrack.js
+43
@@ -102,6 +102,49 @@ export function logComponentRender(
102 }
103 }
104
105 +export function logComponentErrored(
106 + componentInfo: ReactComponentInfo,
107 + trackIdx: number,
108 + startTime: number,
109 + endTime: number,
110 + childrenEndTime: number,
111 + rootEnv: string,
112 + error: mixed,
113 +): void {
114 + if (supportsUserTiming) {
115 + const properties = [];
116 + if (__DEV__) {
117 + const message =
118 + typeof error === 'object' &&
119 + error !== null &&
120 + typeof error.message === 'string'
121 + ? // eslint-disable-next-line react-internal/safe-string-coercion
122 + String(error.message)
123 + : // eslint-disable-next-line react-internal/safe-string-coercion
124 + String(error);
125 + properties.push(['Error', message]);
126 + }
127 + const env = componentInfo.env;
128 + const name = componentInfo.name;
129 + const isPrimaryEnv = env === rootEnv;
130 + const entryName =
131 + isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
132 + performance.measure(entryName, {
133 + start: startTime < 0 ? 0 : startTime,
134 + end: childrenEndTime,
135 + detail: {
136 + devtools: {
137 + color: 'error',
138 + track: trackNames[trackIdx],
139 + trackGroup: COMPONENTS_TRACK,
140 + tooltipText: entryName + ' Errored',
141 + properties,
142 + },
143 + },
144 + });
145 + }
146 +}
147 +
148 export function logDedupedComponentRender(
149 componentInfo: ReactComponentInfo,
150 trackIdx: number,