Skip to content

Commit fc512e5

Browse files
committed
fix: ensure server errors are logged before sanitizing
1 parent 33cc4c0 commit fc512e5

File tree

7 files changed

+59
-6
lines changed

7 files changed

+59
-6
lines changed

.changeset/log-server-errors.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/server-runtime": patch
3+
---
4+
5+
Ensure un-sanitized server errors are logged on the server during document requests

integration/error-sanitization-test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,12 @@ const routeFiles = {
128128
test.describe("Error Sanitization", () => {
129129
let fixture: Fixture;
130130
let oldConsoleError: () => void;
131+
let errorLogs: any[] = [];
131132

132133
test.beforeEach(() => {
133134
oldConsoleError = console.error;
134-
console.error = () => {};
135+
errorLogs = [];
136+
console.error = (...args) => errorLogs.push(args);
135137
});
136138

137139
test.afterEach(() => {
@@ -167,6 +169,9 @@ test.describe("Error Sanitization", () => {
167169
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
168170
);
169171
expect(html).not.toMatch(/stack/i);
172+
expect(errorLogs.length).toBe(1);
173+
expect(errorLogs[0][0].message).toMatch("Loader Error");
174+
expect(errorLogs[0][0].stack).toMatch(" at ");
170175
});
171176

172177
test("sanitizes render errors in document requests", async () => {
@@ -178,6 +183,9 @@ test.describe("Error Sanitization", () => {
178183
'{"routes/index":{"message":"Unexpected Server Error","__type":"Error"}}'
179184
);
180185
expect(html).not.toMatch(/stack/i);
186+
expect(errorLogs.length).toBe(1);
187+
expect(errorLogs[0][0].message).toMatch("Render Error");
188+
expect(errorLogs[0][0].stack).toMatch(" at ");
181189
});
182190

183191
test("renders deferred document without errors", async () => {
@@ -200,6 +208,9 @@ test.describe("Error Sanitization", () => {
200208
// Defer errors are not not part of the JSON blob but rather rejected
201209
// against a pending promise and therefore are inlined JS.
202210
expect(html).toMatch("x.stack=undefined;");
211+
// defer errors are not logged to the server console since the request
212+
// has "succeeded"
213+
expect(errorLogs.length).toBe(0);
203214
});
204215

205216
test("returns data without errors", async () => {
@@ -214,6 +225,9 @@ test.describe("Error Sanitization", () => {
214225
let response = await fixture.requestData("/?loader", "routes/index");
215226
let text = await response.text();
216227
expect(text).toBe('{"message":"Unexpected Server Error"}');
228+
expect(errorLogs.length).toBe(1);
229+
expect(errorLogs[0][0].message).toMatch("Loader Error");
230+
expect(errorLogs[0][0].stack).toMatch(" at ");
217231
});
218232

219233
test("returns deferred data without errors", async () => {
@@ -231,6 +245,9 @@ test.describe("Error Sanitization", () => {
231245
'{"lazy":"__deferred_promise:lazy"}\n\n' +
232246
'error:{"lazy":{"message":"Unexpected Server Error"}}\n\n'
233247
);
248+
// defer errors are not logged to the server console since the request
249+
// has "succeeded"
250+
expect(errorLogs.length).toBe(0);
234251
});
235252

236253
test("sanitizes loader errors in resource requests", async () => {
@@ -240,12 +257,20 @@ test.describe("Error Sanitization", () => {
240257
);
241258
let text = await response.text();
242259
expect(text).toBe('{"message":"Unexpected Server Error"}');
260+
expect(errorLogs.length).toBe(1);
261+
expect(errorLogs[0][0].message).toMatch("Loader Error");
262+
expect(errorLogs[0][0].stack).toMatch(" at ");
243263
});
244264

245265
test("sanitizes mismatched route errors in data requests", async () => {
246266
let response = await fixture.requestData("/", "not-a-route");
247267
let text = await response.text();
248268
expect(text).toBe('{"message":"Unexpected Server Error"}');
269+
expect(errorLogs.length).toBe(1);
270+
expect(errorLogs[0][0].message).toMatch(
271+
'Route "not-a-route" does not match URL "/"'
272+
);
273+
expect(errorLogs[0][0].stack).toMatch(" at ");
249274
});
250275
});
251276

@@ -286,6 +311,9 @@ test.describe("Error Sanitization", () => {
286311
expect(html).toMatch(
287312
'errors":{"routes/index":{"message":"Loader Error","stack":"Error: Loader Error\\n'
288313
);
314+
expect(errorLogs.length).toBe(1);
315+
expect(errorLogs[0][0].message).toMatch("Loader Error");
316+
expect(errorLogs[0][0].stack).toMatch(" at ");
289317
});
290318

291319
test("does not sanitize render errors in document requests", async () => {
@@ -297,6 +325,9 @@ test.describe("Error Sanitization", () => {
297325
expect(html).toMatch(
298326
'errors":{"routes/index":{"message":"Render Error","stack":"Error: Render Error\\n'
299327
);
328+
expect(errorLogs.length).toBe(1);
329+
expect(errorLogs[0][0].message).toMatch("Render Error");
330+
expect(errorLogs[0][0].stack).toMatch(" at ");
300331
});
301332

302333
test("renders deferred document without errors", async () => {
@@ -316,6 +347,9 @@ test.describe("Error Sanitization", () => {
316347
// Defer errors are not not part of the JSON blob but rather rejected
317348
// against a pending promise and therefore are inlined JS.
318349
expect(html).toMatch("x.stack=e.stack;");
350+
// defer errors are not logged to the server console since the request
351+
// has "succeeded"
352+
expect(errorLogs.length).toBe(0);
319353
});
320354

321355
test("returns data without errors", async () => {
@@ -332,6 +366,9 @@ test.describe("Error Sanitization", () => {
332366
expect(text).toMatch(
333367
'{"message":"Loader Error","stack":"Error: Loader Error'
334368
);
369+
expect(errorLogs.length).toBe(1);
370+
expect(errorLogs[0][0].message).toMatch("Loader Error");
371+
expect(errorLogs[0][0].stack).toMatch(" at ");
335372
});
336373

337374
test("returns deferred data without errors", async () => {
@@ -348,6 +385,9 @@ test.describe("Error Sanitization", () => {
348385
expect(text).toMatch(
349386
'error:{"lazy":{"message":"REJECTED","stack":"Error: REJECTED'
350387
);
388+
// defer errors are not logged to the server console since the request
389+
// has "succeeded"
390+
expect(errorLogs.length).toBe(0);
351391
});
352392

353393
test("does not sanitize loader errors in resource requests", async () => {
@@ -359,6 +399,9 @@ test.describe("Error Sanitization", () => {
359399
expect(text).toMatch(
360400
'{"message":"Loader Error","stack":"Error: Loader Error'
361401
);
402+
expect(errorLogs.length).toBe(1);
403+
expect(errorLogs[0][0].message).toMatch("Loader Error");
404+
expect(errorLogs[0][0].stack).toMatch(" at ");
362405
});
363406

364407
test("sanitizes mismatched route errors in data requests", async () => {
@@ -367,6 +410,11 @@ test.describe("Error Sanitization", () => {
367410
expect(text).toMatch(
368411
'{"message":"Route \\"not-a-route\\" does not match URL \\"/\\"","stack":"Error: Route \\"not-a-route\\" does not match URL \\"/\\"'
369412
);
413+
expect(errorLogs.length).toBe(1);
414+
expect(errorLogs[0][0].message).toMatch(
415+
'Route "not-a-route" does not match URL "/"'
416+
);
417+
expect(errorLogs[0][0].stack).toMatch(" at ");
370418
});
371419
});
372420
});

packages/remix-dev/config/defaults/cloudflare/entry.server.react-stream.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export default async function handleRequest(
1414
{
1515
signal: request.signal,
1616
onError(error: unknown) {
17-
console.error(error);
1817
responseStatusCode = 500;
1918
},
2019
}

packages/remix-dev/config/defaults/deno/entry.server.react-stream.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export default async function handleRequest(
1414
{
1515
signal: request.signal,
1616
onError(error: unknown) {
17-
console.error(error);
1817
responseStatusCode = 500;
1918
},
2019
}

packages/remix-dev/config/defaults/node/entry.server.react-stream.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ function handleBotRequest(
6262
},
6363
onError(error: unknown) {
6464
responseStatusCode = 500;
65-
console.error(error);
6665
},
6766
}
6867
);
@@ -103,7 +102,6 @@ function handleBrowserRequest(
103102
reject(error);
104103
},
105104
onError(error: unknown) {
106-
console.error(error);
107105
responseStatusCode = 500;
108106
},
109107
}

packages/remix-react/errorBoundaries.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ export class RemixErrorBoundary extends React.Component<
7070
* When app's don't provide a root level ErrorBoundary, we default to this.
7171
*/
7272
export function RemixRootDefaultErrorBoundary({ error }: { error: Error }) {
73-
console.error(error);
7473
return (
7574
<html lang="en">
7675
<head>

packages/remix-server-runtime/server.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ async function handleDocumentRequestRR(
248248

249249
// Sanitize errors outside of development environments
250250
if (context.errors) {
251+
Object.values(context.errors).forEach((err) =>
252+
logServerErrorIfNotAborted(err, request, serverMode)
253+
);
251254
context.errors = sanitizeErrors(context.errors, serverMode);
252255
}
253256

@@ -284,6 +287,8 @@ async function handleDocumentRequestRR(
284287
loadContext
285288
);
286289
} catch (error: unknown) {
290+
logServerErrorIfNotAborted(error, request, serverMode);
291+
287292
// Get a new StaticHandlerContext that contains the error at the right boundary
288293
context = getStaticContextFromError(
289294
staticHandler.dataRoutes,

0 commit comments

Comments
 (0)