Skip to content

Commit 7118d66

Browse files
committed
feat(cloudflare): Capture request body via httpServerIntegration
1 parent 866958e commit 7118d66

26 files changed

Lines changed: 1042 additions & 13 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
}
6+
7+
export default Sentry.withSentry(
8+
(env: Env) => ({
9+
dsn: env.SENTRY_DSN,
10+
integrations: [Sentry.httpServerIntegration({ maxRequestBodySize: 'none' })],
11+
}),
12+
{
13+
async fetch(_request, _env, _ctx) {
14+
Sentry.captureMessage('POST with disabled body capture');
15+
return new Response('ok');
16+
},
17+
},
18+
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { expect, it } from 'vitest';
2+
import { createRunner } from '../../../runner';
3+
4+
it('Does not capture request body when maxRequestBodySize is none', async ({ signal }) => {
5+
const runner = createRunner(__dirname)
6+
.expect(envelope => {
7+
const event = envelope[1]?.[0]?.[1] as Record<string, unknown>;
8+
expect(event.message).toBe('POST with disabled body capture');
9+
expect(event.request).toEqual(
10+
expect.objectContaining({
11+
method: 'POST',
12+
url: expect.any(String),
13+
}),
14+
);
15+
// Body should NOT be captured
16+
expect((event.request as Record<string, unknown>).data).toBeUndefined();
17+
})
18+
.start(signal);
19+
20+
await runner.makeRequest('post', '/', {
21+
headers: { 'content-type': 'application/json' },
22+
data: JSON.stringify({ secret: 'should-not-be-captured' }),
23+
});
24+
25+
await runner.completed();
26+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "worker-name",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_compat"],
6+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
}
6+
7+
export default Sentry.withSentry(
8+
(env: Env) => ({
9+
dsn: env.SENTRY_DSN,
10+
integrations: integrations => integrations.filter(i => i.name !== 'HttpServer'),
11+
}),
12+
{
13+
async fetch(_request, _env, _ctx) {
14+
Sentry.captureMessage('POST with filtered integration');
15+
return new Response('ok');
16+
},
17+
},
18+
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { expect, it } from 'vitest';
2+
import { createRunner } from '../../../runner';
3+
4+
it('Does not capture request body when httpServerIntegration is filtered out', async ({ signal }) => {
5+
const runner = createRunner(__dirname)
6+
.expect(envelope => {
7+
const event = envelope[1]?.[0]?.[1] as Record<string, unknown>;
8+
expect(event.message).toBe('POST with filtered integration');
9+
expect(event.request).toEqual(
10+
expect.objectContaining({
11+
method: 'POST',
12+
url: expect.any(String),
13+
}),
14+
);
15+
// Body should NOT be captured when integration is filtered out
16+
expect((event.request as Record<string, unknown>).data).toBeUndefined();
17+
})
18+
.start(signal);
19+
20+
await runner.makeRequest('post', '/', {
21+
headers: { 'content-type': 'application/json' },
22+
data: JSON.stringify({ secret: 'should-not-be-captured' }),
23+
});
24+
25+
await runner.completed();
26+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "worker-name",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_compat"],
6+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
}
6+
7+
export default Sentry.withSentry(
8+
(env: Env) => ({
9+
dsn: env.SENTRY_DSN,
10+
integrations: [
11+
Sentry.httpServerIntegration({
12+
ignoreRequestBody: url => url.includes('/health') || url.includes('/upload'),
13+
}),
14+
],
15+
}),
16+
{
17+
async fetch(request, _env, _ctx) {
18+
const url = new URL(request.url);
19+
20+
if (url.pathname === '/health') {
21+
Sentry.captureMessage('Health check');
22+
return new Response('ok');
23+
}
24+
25+
if (url.pathname === '/upload') {
26+
Sentry.captureMessage('Upload request');
27+
return new Response('ok');
28+
}
29+
30+
if (url.pathname === '/api') {
31+
Sentry.captureMessage('API request');
32+
return new Response('ok');
33+
}
34+
35+
return new Response('Not found', { status: 404 });
36+
},
37+
},
38+
);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { expect, it } from 'vitest';
2+
import { createRunner } from '../../../runner';
3+
4+
it('Does not capture body for ignored URLs (health check)', async ({ signal }) => {
5+
const runner = createRunner(__dirname)
6+
.expect(envelope => {
7+
const event = envelope[1]?.[0]?.[1] as Record<string, unknown>;
8+
expect(event.message).toBe('Health check');
9+
// Body should NOT be captured because URL contains /health
10+
expect((event.request as Record<string, unknown>).data).toBeUndefined();
11+
})
12+
.start(signal);
13+
14+
await runner.makeRequest('post', '/health', {
15+
headers: { 'content-type': 'application/json' },
16+
data: JSON.stringify({ status: 'checking' }),
17+
});
18+
19+
await runner.completed();
20+
});
21+
22+
it('Does not capture body for ignored URLs (upload)', async ({ signal }) => {
23+
const runner = createRunner(__dirname)
24+
.expect(envelope => {
25+
const event = envelope[1]?.[0]?.[1] as Record<string, unknown>;
26+
expect(event.message).toBe('Upload request');
27+
// Body should NOT be captured because URL contains /upload
28+
expect((event.request as Record<string, unknown>).data).toBeUndefined();
29+
})
30+
.start(signal);
31+
32+
await runner.makeRequest('post', '/upload', {
33+
headers: { 'content-type': 'application/json' },
34+
data: JSON.stringify({ file: 'large-data' }),
35+
});
36+
37+
await runner.completed();
38+
});
39+
40+
it('Captures body for non-ignored URLs', async ({ signal }) => {
41+
const runner = createRunner(__dirname)
42+
.expect(envelope => {
43+
const event = envelope[1]?.[0]?.[1] as Record<string, unknown>;
44+
expect(event.message).toBe('API request');
45+
// Body SHOULD be captured because URL does not match ignore pattern
46+
expect((event.request as Record<string, unknown>).data).toBe('{"action":"submit"}');
47+
})
48+
.start(signal);
49+
50+
await runner.makeRequest('post', '/api', {
51+
headers: { 'content-type': 'application/json' },
52+
data: JSON.stringify({ action: 'submit' }),
53+
});
54+
55+
await runner.completed();
56+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "worker-name",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_compat"],
6+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
}
6+
7+
export default Sentry.withSentry(
8+
(env: Env) => ({
9+
dsn: env.SENTRY_DSN,
10+
integrations: [Sentry.httpServerIntegration({ maxRequestBodySize: 'small' })],
11+
}),
12+
{
13+
async fetch(request, _env, _ctx) {
14+
const url = new URL(request.url);
15+
16+
if (url.pathname === '/small-body') {
17+
Sentry.captureMessage('Small body request');
18+
return new Response('ok');
19+
}
20+
21+
if (url.pathname === '/large-body') {
22+
Sentry.captureMessage('Large body request');
23+
return new Response('ok');
24+
}
25+
26+
return new Response('Not found', { status: 404 });
27+
},
28+
},
29+
);

0 commit comments

Comments
 (0)