Skip to content

Commit 229d595

Browse files
authored
Merge pull request #538 from wesbos/claude/ignore-render-disconnects
Stop reporting dropped client connections as render errors
2 parents 785bf43 + e70f3d4 commit 229d595

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

‎src/server/captureRenderErrors.test.ts‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ import * as Sentry from '@sentry/cloudflare';
22
import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest';
33
import { captureRenderErrors } from './captureRenderErrors';
44

5-
vi.mock('@sentry/cloudflare', () => ({ captureException: vi.fn() }));
5+
vi.mock('@sentry/cloudflare', () => ({ captureException: vi.fn(), getIsolationScope: vi.fn() }));
6+
7+
function setRequestInScope(url?: string) {
8+
vi.mocked(Sentry.getIsolationScope).mockReturnValue({
9+
getScopeData: () => ({ sdkProcessingMetadata: url ? { normalizedRequest: { url } } : {} }),
10+
} as unknown as ReturnType<typeof Sentry.getIsolationScope>);
11+
}
612

713
const originalConsoleError = console.error;
814

915
describe('captureRenderErrors', () => {
1016
beforeEach(() => {
1117
vi.mocked(Sentry.captureException).mockClear();
18+
setRequestInScope('https://wesbos.com/blog');
1219
console.error = vi.fn();
1320
captureRenderErrors();
1421
});
@@ -32,6 +39,17 @@ describe('captureRenderErrors', () => {
3239
expect(Sentry.captureException).not.toHaveBeenCalled();
3340
});
3441

42+
test('ignores a lost connection from outside a request, because the visitor left', () => {
43+
setRequestInScope();
44+
console.error('Error during rendering:', 'Error: Network connection lost.');
45+
expect(Sentry.captureException).not.toHaveBeenCalled();
46+
});
47+
48+
test('reports a lost connection from inside a request, because a subrequest failed', () => {
49+
console.error('Error during rendering:', 'Error: Network connection lost.');
50+
expect(Sentry.captureException).toHaveBeenCalledOnce();
51+
});
52+
3553
test('ignores other console errors', () => {
3654
console.error('Cache read error:', 'nope');
3755
expect(Sentry.captureException).not.toHaveBeenCalled();

‎src/server/captureRenderErrors.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ import * as Sentry from '@sentry/cloudflare';
33
const WAKU_RENDER_ERROR = 'Error during rendering:';
44
// React aborts a render when the visitor leaves mid-stream, which isn't a bug
55
const RENDER_ABORTED = 'The render was aborted by the server';
6+
const CONNECTION_LOST = 'Network connection lost';
7+
8+
/**
9+
* A dropped client connection reports as "Network connection lost" too, from outside the request.
10+
* The same message from inside a request is a failed subrequest, like D1 or a fetch, so keep those.
11+
*/
12+
function isClientDisconnect(details: string) {
13+
if (details.includes(RENDER_ABORTED)) return true;
14+
const { normalizedRequest } = Sentry.getIsolationScope().getScopeData().sdkProcessingMetadata;
15+
return details.includes(CONNECTION_LOST) && !normalizedRequest;
16+
}
617

718
/**
819
* Waku has no error hook for renders. An error thrown while streaming, like one inside <Suspense>,
@@ -15,7 +26,7 @@ export function captureRenderErrors() {
1526
consoleError(...args);
1627
const [label, details] = args;
1728
if (label !== WAKU_RENDER_ERROR || typeof details !== 'string') return;
18-
if (details.includes(RENDER_ABORTED)) return;
29+
if (isClientDisconnect(details)) return;
1930
const stack = details.replaceAll('\\n', '\n');
2031
const error = new Error(stack.split('\n')[0]?.replace(/^\w*Error: /, ''));
2132
error.stack = stack;

0 commit comments

Comments
 (0)