Skip to content

Commit 585e5dc

Browse files
add support for console replay of console logs happen in async operations
get console replay messages after promise resolves or rejects use isPromise to check if the result is a promise make consoleReplay function accept the list of logs to be added to the script make consoleHistory argument of consoleReplay optional add comments add comments update comment remove FlowFixMe comment
1 parent ee382bc commit 585e5dc

File tree

2 files changed

+25
-19
lines changed

2 files changed

+25
-19
lines changed

node_package/src/buildConsoleReplay.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ declare global {
99
}
1010
}
1111

12-
export function consoleReplay(): string {
12+
export function consoleReplay(customConsoleHistory: typeof console['history'] | undefined = undefined): string {
1313
// console.history is a global polyfill used in server rendering.
14-
if (!(console.history instanceof Array)) {
14+
const consoleHistory = customConsoleHistory ?? console.history;
15+
16+
if (!(Array.isArray(consoleHistory))) {
1517
return '';
1618
}
1719

18-
const lines = console.history.map(msg => {
20+
const lines = consoleHistory.map(msg => {
1921
const stringifiedList = msg.arguments.map(arg => {
2022
let val: string;
2123
try {
@@ -42,6 +44,6 @@ export function consoleReplay(): string {
4244
return lines.join('\n');
4345
}
4446

45-
export default function buildConsoleReplay(): string {
46-
return RenderUtils.wrapInScriptTags('consoleReplayLog', consoleReplay());
47+
export default function buildConsoleReplay(customConsoleHistory: typeof console['history'] | undefined = undefined): string {
48+
return RenderUtils.wrapInScriptTags('consoleReplayLog', consoleReplay(customConsoleHistory));
4749
}

node_package/src/serverRenderReactComponent.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,22 @@ function createResultObject(html: string | null, consoleReplayScript: string, re
105105

106106
async function createPromiseResult(
107107
renderState: RenderState & { result: Promise<string> },
108-
consoleReplayScript: string,
109108
componentName: string,
110109
throwJsErrors: boolean
111110
): Promise<RenderResult> {
111+
// Capture console history before awaiting the promise
112+
// Node renderer will reset the global console.history after executing the synchronous part of the request.
113+
// It resets it only if replayServerAsyncOperationLogs renderer config is set to false.
114+
// In both cases, we need to keep a reference to console.history to avoid losing console logs in case of reset.
115+
const consoleHistory = console.history;
112116
try {
113117
const html = await renderState.result;
118+
const consoleReplayScript = buildConsoleReplay(consoleHistory);
114119
return createResultObject(html, consoleReplayScript, renderState);
115120
} catch (e: unknown) {
116121
const errorRenderState = handleRenderingError(e, { componentName, throwJsErrors });
117-
return createResultObject(errorRenderState.result, consoleReplayScript, errorRenderState);
122+
const consoleReplayScript = buildConsoleReplay(consoleHistory);
123+
return createResultObject(errorRenderState.result, consoleReplayScript, renderState);
118124
}
119125
}
120126

@@ -123,20 +129,12 @@ function createFinalResult(
123129
componentName: string,
124130
throwJsErrors: boolean
125131
): null | string | Promise<RenderResult> {
126-
// Console history is stored globally in `console.history`.
127-
// If node renderer is handling a render request that returns a promise,
128-
// It can handle another request while awaiting the promise.
129-
// To prevent cross-request console logs leakage between these requests,
130-
// we build the consoleReplayScript before awaiting any promises.
131-
// The console history is reset after the synchronous part of each request.
132-
// This causes console logs happening during async operations to not be captured.
133-
const consoleReplayScript = buildConsoleReplay();
134-
135132
const { result } = renderState;
136133
if (isPromise(result)) {
137-
return createPromiseResult({ ...renderState, result }, consoleReplayScript, componentName, throwJsErrors);
134+
return createPromiseResult({ ...renderState, result }, componentName, throwJsErrors);
138135
}
139136

137+
const consoleReplayScript = buildConsoleReplay();
140138
return JSON.stringify(createResultObject(result, consoleReplayScript, renderState));
141139
}
142140

@@ -183,13 +181,19 @@ function serverRenderReactComponentInternal(options: RenderParams): null | strin
183181
}
184182

185183
const serverRenderReactComponent: typeof serverRenderReactComponentInternal = (options) => {
184+
let result: string | Promise<RenderResult> | null = null;
186185
try {
187-
return serverRenderReactComponentInternal(options);
186+
result = serverRenderReactComponentInternal(options);
188187
} finally {
189188
// Reset console history after each render.
190189
// See `RubyEmbeddedJavaScript.console_polyfill` for initialization.
191-
console.history = [];
190+
// We don't need to clear the console history if the result is a promise
191+
// Promises only supported in node renderer and node renderer takes care of cleanining console history
192+
if (typeof result === 'string') {
193+
console.history = [];
194+
}
192195
}
196+
return result;
193197
};
194198

195199
export default serverRenderReactComponent;

0 commit comments

Comments
 (0)