Skip to content

Commit 60f8f65

Browse files
committed
add flushing
1 parent 5652282 commit 60f8f65

File tree

2 files changed

+70
-9
lines changed

2 files changed

+70
-9
lines changed

packages/react-router/src/server/wrapSentryHandleRequest.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { context } from '@opentelemetry/api';
22
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
33
import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
44
import {
5+
flush,
56
getActiveSpan,
67
getRootSpan,
78
getTraceMetaTags,
89
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
910
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
11+
vercelWaitUntil,
1012
} from '@sentry/core';
1113
import type { AppLoadContext, EntryContext } from 'react-router';
1214
import type { PassThrough } from 'stream';
@@ -58,11 +60,16 @@ export function wrapSentryHandleRequest(originalHandle: OriginalHandleRequest):
5860
});
5961
}
6062

61-
return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext);
63+
try {
64+
return await originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext);
65+
} finally {
66+
vercelWaitUntil(flush());
67+
}
6268
};
6369
}
6470

6571
/** @deprecated Use `wrapSentryHandleRequest` instead. */
72+
// todo(v11): remove this
6673
export const sentryHandleRequest = wrapSentryHandleRequest;
6774

6875
/**

packages/react-router/test/server/wrapSentryHandleRequest.test.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { RPCType } from '@opentelemetry/core';
22
import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
33
import {
4+
flush,
45
getActiveSpan,
56
getRootSpan,
67
getTraceMetaTags,
78
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
89
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
10+
vercelWaitUntil,
911
} from '@sentry/core';
1012
import { PassThrough } from 'stream';
1113
import { beforeEach, describe, expect, test, vi } from 'vitest';
@@ -22,13 +24,66 @@ vi.mock('@sentry/core', () => ({
2224
getActiveSpan: vi.fn(),
2325
getRootSpan: vi.fn(),
2426
getTraceMetaTags: vi.fn(),
27+
flush: vi.fn(),
28+
vercelWaitUntil: vi.fn(),
2529
}));
2630

2731
describe('wrapSentryHandleRequest', () => {
2832
beforeEach(() => {
2933
vi.clearAllMocks();
3034
});
3135

36+
test('should call flush on successful execution', async () => {
37+
const originalHandler = vi.fn().mockResolvedValue('success response');
38+
const wrappedHandler = wrapSentryHandleRequest(originalHandler);
39+
40+
const request = new Request('https://example.com');
41+
const responseStatusCode = 200;
42+
const responseHeaders = new Headers();
43+
const routerContext = { staticHandlerContext: { matches: [] } } as any;
44+
const loadContext = {} as any;
45+
46+
await wrappedHandler(request, responseStatusCode, responseHeaders, routerContext, loadContext);
47+
48+
expect(vercelWaitUntil).toHaveBeenCalledWith(flush());
49+
expect(flush).toHaveBeenCalled();
50+
});
51+
52+
test('should call flush even when original handler throws an error', async () => {
53+
const mockError = new Error('Handler failed');
54+
const originalHandler = vi.fn().mockRejectedValue(mockError);
55+
const wrappedHandler = wrapSentryHandleRequest(originalHandler);
56+
57+
const request = new Request('https://example.com');
58+
const responseStatusCode = 200;
59+
const responseHeaders = new Headers();
60+
const routerContext = { staticHandlerContext: { matches: [] } } as any;
61+
const loadContext = {} as any;
62+
63+
await expect(
64+
wrappedHandler(request, responseStatusCode, responseHeaders, routerContext, loadContext),
65+
).rejects.toThrow('Handler failed');
66+
67+
expect(vercelWaitUntil).toHaveBeenCalledWith(flush());
68+
expect(flush).toHaveBeenCalled();
69+
});
70+
71+
test('should propagate errors from original handler', async () => {
72+
const mockError = new Error('Test error');
73+
const originalHandler = vi.fn().mockRejectedValue(mockError);
74+
const wrappedHandler = wrapSentryHandleRequest(originalHandler);
75+
76+
const request = new Request('https://example.com');
77+
const responseStatusCode = 500;
78+
const responseHeaders = new Headers();
79+
const routerContext = { staticHandlerContext: { matches: [] } } as any;
80+
const loadContext = {} as any;
81+
82+
await expect(wrappedHandler(request, responseStatusCode, responseHeaders, routerContext, loadContext)).rejects.toBe(
83+
mockError,
84+
);
85+
});
86+
3287
test('should call original handler with same parameters', async () => {
3388
const originalHandler = vi.fn().mockResolvedValue('original response');
3489
const wrappedHandler = wrapSentryHandleRequest(originalHandler);
@@ -62,7 +117,8 @@ describe('wrapSentryHandleRequest', () => {
62117
(getActiveSpan as unknown as ReturnType<typeof vi.fn>).mockReturnValue(mockActiveSpan);
63118
(getRootSpan as unknown as ReturnType<typeof vi.fn>).mockReturnValue(mockRootSpan);
64119
const getRPCMetadata = vi.fn().mockReturnValue(mockRpcMetadata);
65-
vi.mocked(vi.importActual('@opentelemetry/core')).getRPCMetadata = getRPCMetadata;
120+
(vi.importActual('@opentelemetry/core') as unknown as { getRPCMetadata: typeof getRPCMetadata }).getRPCMetadata =
121+
getRPCMetadata;
66122

67123
const routerContext = {
68124
staticHandlerContext: {
@@ -110,7 +166,8 @@ describe('wrapSentryHandleRequest', () => {
110166
(getActiveSpan as unknown as ReturnType<typeof vi.fn>).mockReturnValue(null);
111167

112168
const getRPCMetadata = vi.fn().mockReturnValue(mockRpcMetadata);
113-
vi.mocked(vi.importActual('@opentelemetry/core')).getRPCMetadata = getRPCMetadata;
169+
(vi.importActual('@opentelemetry/core') as unknown as { getRPCMetadata: typeof getRPCMetadata }).getRPCMetadata =
170+
getRPCMetadata;
114171

115172
const routerContext = {
116173
staticHandlerContext: {
@@ -132,7 +189,7 @@ describe('getMetaTagTransformer', () => {
132189
);
133190
});
134191

135-
test('should inject meta tags before closing head tag', done => {
192+
test('should inject meta tags before closing head tag', () => {
136193
const outputStream = new PassThrough();
137194
const bodyStream = new PassThrough();
138195
const transformer = getMetaTagTransformer(bodyStream);
@@ -145,7 +202,6 @@ describe('getMetaTagTransformer', () => {
145202
outputStream.on('end', () => {
146203
expect(outputData).toContain('<meta name="sentry-trace" content="test-trace-id"></head>');
147204
expect(outputData).not.toContain('</head></head>');
148-
done();
149205
});
150206

151207
transformer.pipe(outputStream);
@@ -154,7 +210,7 @@ describe('getMetaTagTransformer', () => {
154210
bodyStream.end();
155211
});
156212

157-
test('should not modify chunks without head closing tag', done => {
213+
test('should not modify chunks without head closing tag', () => {
158214
const outputStream = new PassThrough();
159215
const bodyStream = new PassThrough();
160216
const transformer = getMetaTagTransformer(bodyStream);
@@ -167,7 +223,6 @@ describe('getMetaTagTransformer', () => {
167223
outputStream.on('end', () => {
168224
expect(outputData).toBe('<html><body>Test</body></html>');
169225
expect(getTraceMetaTags).toHaveBeenCalled();
170-
done();
171226
});
172227

173228
transformer.pipe(outputStream);
@@ -176,7 +231,7 @@ describe('getMetaTagTransformer', () => {
176231
bodyStream.end();
177232
});
178233

179-
test('should handle buffer input', done => {
234+
test('should handle buffer input', () => {
180235
const outputStream = new PassThrough();
181236
const bodyStream = new PassThrough();
182237
const transformer = getMetaTagTransformer(bodyStream);
@@ -188,7 +243,6 @@ describe('getMetaTagTransformer', () => {
188243

189244
outputStream.on('end', () => {
190245
expect(outputData).toContain('<meta name="sentry-trace" content="test-trace-id"></head>');
191-
done();
192246
});
193247

194248
transformer.pipe(outputStream);

0 commit comments

Comments
 (0)