Skip to content

Commit 0621b79

Browse files
Lms24cursoragent
andcommitted
test(cloudflare): Cover continued traces across ignored spans
Mirror the Node and browser propagation scenarios so Cloudflare regressions across ignored streamed spans are caught by integration tests. Refs #22262 Co-Authored-By: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent bddcb83 commit 0621b79

9 files changed

Lines changed: 222 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
SERVER_URL: string;
6+
}
7+
8+
export default Sentry.withSentry(
9+
(env: Env) => ({
10+
dsn: env.SENTRY_DSN,
11+
tracesSampleRate: 0,
12+
traceLifecycle: 'stream',
13+
ignoreSpans: ['ignored-child'],
14+
tracePropagationTargets: [env.SERVER_URL],
15+
}),
16+
{
17+
async fetch(_request, env, _ctx) {
18+
await Sentry.startSpan({ name: 'ignored-child' }, async () => {
19+
await fetch(`${env.SERVER_URL}/outgoing`);
20+
});
21+
22+
return Response.json({ status: 'ok' });
23+
},
24+
},
25+
);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Envelope, SerializedStreamedSpanContainer } from '@sentry/core';
2+
import { SENTRY_OP } from '@sentry/conventions/attributes';
3+
import { createTestServer } from '@sentry-internal/test-utils';
4+
import { expect, it } from 'vitest';
5+
import { createRunner } from '../../../../runner';
6+
7+
it('preserves a positive sampling decision across an ignored child span', async ({ signal }) => {
8+
const [serverUrl, closeTestServer] = await createTestServer()
9+
.get('/outgoing', headers => {
10+
expect(headers['sentry-trace']).toMatch(/^12345678901234567890123456789012-[\da-f]{16}-1$/);
11+
expect(headers['baggage']).toBe(
12+
'sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=1,sentry-sampled=true,sentry-public_key=public,sentry-sample_rand=0.5',
13+
);
14+
})
15+
.start();
16+
17+
const runner = createRunner(__dirname)
18+
.withServerUrl(serverUrl)
19+
.expect(envelope => {
20+
const container = getSpanContainer(envelope);
21+
const serverSpan = container.items.find(item => item.attributes[SENTRY_OP]?.value === 'http.server');
22+
const fetchSpan = container.items.find(item => item.attributes[SENTRY_OP]?.value === 'http.client');
23+
24+
expect(serverSpan?.is_segment).toBe(true);
25+
expect(serverSpan?.trace_id).toBe('12345678901234567890123456789012');
26+
expect(fetchSpan?.parent_span_id).toBe(serverSpan?.span_id);
27+
expect(container.items.some(item => item.name === 'ignored-child')).toBe(false);
28+
})
29+
.start(signal);
30+
31+
try {
32+
const response = await runner.makeRequest<{ status: string }>('get', '/', {
33+
headers: {
34+
'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
35+
baggage:
36+
'sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=1,sentry-sampled=true,sentry-public_key=public,sentry-sample_rand=0.5',
37+
},
38+
});
39+
40+
expect(response?.status).toBe('ok');
41+
await runner.completed();
42+
} finally {
43+
closeTestServer();
44+
}
45+
});
46+
47+
function getSpanContainer(envelope: Envelope): SerializedStreamedSpanContainer {
48+
const spanItem = envelope[1].find(item => item[0].type === 'span');
49+
expect(spanItem).toBeDefined();
50+
return spanItem![1] as SerializedStreamedSpanContainer;
51+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "ignore-spans-streamed-continued-trace-child",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_als"],
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
SERVER_URL: string;
6+
}
7+
8+
export default Sentry.withSentry(
9+
(env: Env) => ({
10+
dsn: env.SENTRY_DSN,
11+
tracesSampleRate: 0,
12+
traceLifecycle: 'stream',
13+
ignoreSpans: [{ attributes: { 'sentry.op': 'http.client' } }],
14+
tracePropagationTargets: [env.SERVER_URL],
15+
}),
16+
{
17+
async fetch(_request, env, _ctx) {
18+
await fetch(`${env.SERVER_URL}/outgoing`);
19+
return Response.json({ status: 'ok' });
20+
},
21+
},
22+
);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Envelope, SerializedStreamedSpanContainer } from '@sentry/core';
2+
import { SENTRY_OP } from '@sentry/conventions/attributes';
3+
import { createTestServer } from '@sentry-internal/test-utils';
4+
import { expect, it } from 'vitest';
5+
import { createRunner } from '../../../../runner';
6+
7+
it('preserves a positive sampling decision when the outgoing fetch span is ignored', async ({ signal }) => {
8+
let outgoingSentryTrace: string | string[] | undefined;
9+
10+
const [serverUrl, closeTestServer] = await createTestServer()
11+
.get('/outgoing', headers => {
12+
outgoingSentryTrace = headers['sentry-trace'];
13+
expect(headers['baggage']).toBe(
14+
'sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=1,sentry-sampled=true,sentry-public_key=public,sentry-sample_rand=0.5',
15+
);
16+
})
17+
.start();
18+
19+
const runner = createRunner(__dirname)
20+
.withServerUrl(serverUrl)
21+
.expect(envelope => {
22+
const container = getSpanContainer(envelope);
23+
const serverSpan = container.items.find(item => item.attributes[SENTRY_OP]?.value === 'http.server');
24+
25+
expect(serverSpan?.is_segment).toBe(true);
26+
expect(serverSpan?.trace_id).toBe('12345678901234567890123456789012');
27+
expect(outgoingSentryTrace).toBe(`12345678901234567890123456789012-${serverSpan?.span_id}-1`);
28+
expect(container.items.some(item => item.attributes[SENTRY_OP]?.value === 'http.client')).toBe(false);
29+
})
30+
.start(signal);
31+
32+
try {
33+
const response = await runner.makeRequest<{ status: string }>('get', '/', {
34+
headers: {
35+
'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
36+
baggage:
37+
'sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=1,sentry-sampled=true,sentry-public_key=public,sentry-sample_rand=0.5',
38+
},
39+
});
40+
41+
expect(response?.status).toBe('ok');
42+
await runner.completed();
43+
} finally {
44+
closeTestServer();
45+
}
46+
});
47+
48+
function getSpanContainer(envelope: Envelope): SerializedStreamedSpanContainer {
49+
const spanItem = envelope[1].find(item => item[0].type === 'span');
50+
expect(spanItem).toBeDefined();
51+
return spanItem![1] as SerializedStreamedSpanContainer;
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "ignore-spans-streamed-continued-trace-http-client",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_als"],
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
SERVER_URL: string;
6+
}
7+
8+
export default Sentry.withSentry(
9+
(env: Env) => ({
10+
dsn: env.SENTRY_DSN,
11+
tracesSampleRate: 0,
12+
traceLifecycle: 'stream',
13+
ignoreSpans: [{ op: 'http.server' }],
14+
tracePropagationTargets: [env.SERVER_URL],
15+
}),
16+
{
17+
async fetch(_request, env, _ctx) {
18+
await fetch(`${env.SERVER_URL}/outgoing`);
19+
return Response.json({ status: 'ok' });
20+
},
21+
},
22+
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { createTestServer } from '@sentry-internal/test-utils';
2+
import { expect, it } from 'vitest';
3+
import { createRunner } from '../../../../runner';
4+
5+
it('propagates a negative sampling decision when the continued server segment is ignored', async ({ signal }) => {
6+
expect.assertions(3);
7+
8+
const [serverUrl, closeTestServer] = await createTestServer()
9+
.get('/outgoing', headers => {
10+
expect(headers['sentry-trace']).toMatch(/^12345678901234567890123456789012-[\da-f]{16}-0$/);
11+
expect(headers['baggage']).toBe(
12+
'sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=1,sentry-sampled=false,sentry-public_key=public,sentry-sample_rand=0.5',
13+
);
14+
})
15+
.start();
16+
17+
const runner = createRunner(__dirname).withServerUrl(serverUrl).start(signal);
18+
19+
try {
20+
const response = await runner.makeRequest<{ status: string }>('get', '/', {
21+
headers: {
22+
'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
23+
baggage:
24+
'sentry-trace_id=12345678901234567890123456789012,sentry-sample_rate=1,sentry-sampled=true,sentry-public_key=public,sentry-sample_rand=0.5',
25+
},
26+
});
27+
28+
expect(response?.status).toBe('ok');
29+
} finally {
30+
closeTestServer();
31+
}
32+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "ignore-spans-streamed-continued-trace-segment",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_als"],
6+
}

0 commit comments

Comments
 (0)