Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8486e32
feat(opentelemetry): Add SentryTraceProvider
andreiborza May 18, 2026
30b73f9
Add useSentryTraceProvider flag
andreiborza May 18, 2026
b6376f4
Fix HTTP propagation with SentryTraceProvider
andreiborza May 18, 2026
b369fa6
Add SentryTraceProvider test coverage
andreiborza May 18, 2026
4250c2d
Add e2e tests
andreiborza May 18, 2026
df9f187
Noop recordException, use core's getActiveSpan in setupEventContextTrace
andreiborza Jun 3, 2026
306367e
Allow non-recording spans to carry explicit sampling decisions
andreiborza Jun 6, 2026
2b70730
WIP
andreiborza Jun 7, 2026
2bb6a3c
Make sure getSamplingDecision reads sampled flag from spancontext
andreiborza Jun 8, 2026
bbbed4e
Fix cf integration tests
andreiborza Jun 8, 2026
cd032d6
Handle parent_span_id
andreiborza Jun 8, 2026
87ed1f0
Fix scope leaking
andreiborza Jun 8, 2026
29edad8
Revert idle span DSC changes — not needed for SentryTraceProvider
andreiborza Jun 9, 2026
fbe96b2
fix(core): Preserve continued-trace DSC transaction for TwP root spans
andreiborza Jun 9, 2026
8949df5
docs(core): Document the SentryNonRecordingSpan parentSpanId getter
andreiborza Jun 9, 2026
732606c
fix(opentelemetry): Link suppressed-parent non-recording spans to the…
andreiborza Jun 9, 2026
ec84d60
fix(core): Capture scopes on non-recording spans from createChildOrRo…
andreiborza Jun 9, 2026
70d5480
fix(opentelemetry): Mark SentryTraceProvider as set up only after reg…
andreiborza Jun 9, 2026
5969f40
fix(core): Freeze a continued trace's DSC as-is in TwP mode
andreiborza Jun 9, 2026
3a31d37
fix(opentelemetry): Capture scopes on suppressed non-recording spans
andreiborza Jun 9, 2026
d642cfc
fix(core): Capture scopes on the non-recording idle span placeholder
andreiborza Jun 9, 2026
5917bab
fix(core): Pass parent span id to unsampled and ignored non-recording…
andreiborza Jun 9, 2026
d34ae34
fix(core): Don't add url-source span names to the TwP DSC
andreiborza Jun 9, 2026
79e9919
fix(node): Let span-derived response status_code win in TwP preproces…
andreiborza Jun 9, 2026
0506397
test(cloudflare): Cover url-source DSC omission in integration tests
andreiborza Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions dev-packages/cloudflare-integration-tests/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,17 @@ export function expectedEvent(event: Event, { sdk }: { sdk: 'cloudflare' | 'hono

export function eventEnvelope(
event: Event,
{ includeSampleRand = false, sdk = 'cloudflare' }: { includeSampleRand?: boolean; sdk?: 'cloudflare' | 'hono' } = {},
{
includeSamplingFields = false,
includeSampleRand = false,
includeTransaction = true,
sdk = 'cloudflare',
}: {
includeSamplingFields?: boolean;
includeSampleRand?: boolean;
includeTransaction?: boolean;
sdk?: 'cloudflare' | 'hono';
} = {},
): Envelope {
return [
{
Expand All @@ -72,10 +82,11 @@ export function eventEnvelope(
environment: event.environment || 'production',
public_key: 'public',
trace_id: UUID_MATCHER,
sample_rate: expect.any(String),
...(includeSamplingFields && { sample_rate: expect.any(String), sampled: expect.any(String) }),
...(includeSampleRand && { sample_rand: expect.stringMatching(/^[01](\.\d+)?$/) }),
sampled: expect.any(String),
transaction: expect.any(String),
// In TwP mode the span name is omitted from the DSC when the span source is `url`
// (raw URLs may contain PII), mirroring `getDynamicSamplingContextFromSpan`.
...(includeTransaction && { transaction: expect.any(String) }),
},
},
[[{ type: 'event' }, expectedEvent(event, { sdk })]],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ import { createRunner } from '../../runner';
it('Only sends one error event when withSentry is called twice', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(
eventEnvelope({
level: 'error',
exception: {
values: [
{
type: 'Error',
value: 'Test error from double-instrumented worker',
stacktrace: {
frames: expect.any(Array),
eventEnvelope(
{
level: 'error',
exception: {
values: [
{
type: 'Error',
value: 'Test error from double-instrumented worker',
stacktrace: {
frames: expect.any(Array),
},
mechanism: { type: 'auto.http.cloudflare', handled: false },
},
mechanism: { type: 'auto.http.cloudflare', handled: false },
},
],
],
},
request: {
headers: expect.any(Object),
method: 'GET',
url: expect.any(String),
},
},
request: {
headers: expect.any(Object),
method: 'GET',
url: expect.any(String),
},
}),
// `/error` resolves to a raw URL span (source `url`), so the TwP DSC omits the span name.
{ includeTransaction: false },
),
)
// The http.server span produces a transaction envelope that is sent in parallel with the
// error event. Either can arrive first at the mock server, so ignore it here to keep the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ it('Hono app captures errors', async ({ signal }) => {
url: expect.any(String),
},
},
{ includeSampleRand: true },
{ includeSamplingFields: true, includeSampleRand: true },
),
)
// Second envelope: transaction event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ it('Hono app captures parametrized errors (Hono SDK)', async ({ signal }) => {
},
],
},
{ includeSampleRand: true, sdk: 'hono' },
{ includeSamplingFields: true, includeSampleRand: true, sdk: 'hono' },
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import { createRunner } from '../../../runner';
it('Captures JSON request body', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(
eventEnvelope({
level: 'info',
message: 'POST JSON request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-json'),
data: '{"username":"test","action":"login"}',
eventEnvelope(
{
level: 'info',
message: 'POST JSON request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-json'),
data: '{"username":"test","action":"login"}',
},
},
}),
// Raw URL span (source `url`), so the TwP DSC omits the span name.
{ includeTransaction: false },
),
)
.start(signal);

Expand All @@ -29,16 +33,20 @@ it('Captures JSON request body', async ({ signal }) => {
it('Captures form-urlencoded request body', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(
eventEnvelope({
level: 'info',
message: 'POST form request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-form'),
data: 'username=test&password=secret',
eventEnvelope(
{
level: 'info',
message: 'POST form request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-form'),
data: 'username=test&password=secret',
},
},
}),
// Raw URL span (source `url`), so the TwP DSC omits the span name.
{ includeTransaction: false },
),
)
.start(signal);

Expand All @@ -53,16 +61,20 @@ it('Captures form-urlencoded request body', async ({ signal }) => {
it('Captures plain text request body', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(
eventEnvelope({
level: 'info',
message: 'POST text request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-text'),
data: 'This is plain text content',
eventEnvelope(
{
level: 'info',
message: 'POST text request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-text'),
data: 'This is plain text content',
},
},
}),
// Raw URL span (source `url`), so the TwP DSC omits the span name.
{ includeTransaction: false },
),
)
.start(signal);

Expand All @@ -77,15 +89,19 @@ it('Captures plain text request body', async ({ signal }) => {
it('Does not capture body for POST without content', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(
eventEnvelope({
level: 'info',
message: 'POST no body request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-no-body'),
eventEnvelope(
{
level: 'info',
message: 'POST no body request',
request: {
headers: expect.any(Object),
method: 'POST',
url: expect.stringContaining('/post-no-body'),
},
},
}),
// Raw URL span (source `url`), so the TwP DSC omits the span name.
{ includeTransaction: false },
),
)
.start(signal);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as Sentry from '@sentry/cloudflare';

interface Env {
SENTRY_DSN: string;
}

// Tracing is enabled (not TwP), but the route is a raw, non-parametrized URL so the
// http.server span source is `url`. The span name must therefore be omitted from the
// DSC (raw URLs may contain PII), even though a real transaction is recorded.
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
{
async fetch(_request, _env, _ctx) {
throw new Error('Test error from URL-source worker');
},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expect, it } from 'vitest';
import { eventEnvelope } from '../../../expect';
import { createRunner } from '../../../runner';

it('omits the span name from the DSC for url-source spans when tracing is enabled', async ({ signal }) => {
const runner = createRunner(__dirname)
// Error event: because tracing is enabled, the DSC carries the sampling fields. But the span
// source is `url`, so the span name is omitted from the DSC (raw URLs may contain PII).
.expect(
eventEnvelope(
{
level: 'error',
exception: {
values: [
{
type: 'Error',
value: 'Test error from URL-source worker',
stacktrace: {
frames: expect.any(Array),
},
mechanism: { type: 'auto.http.cloudflare', handled: false },
},
],
},
request: {
headers: expect.any(Object),
method: 'GET',
url: expect.any(String),
},
},
{ includeSamplingFields: true, includeSampleRand: true, includeTransaction: false },
),
)
// Transaction event: proves we are NOT in TwP — the span is recorded with a `url` source and
// carries the name on the event itself, even though it is intentionally absent from the DSC.
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1];
expect(transactionEvent).toEqual(
expect.objectContaining({
type: 'transaction',
transaction: 'GET /error',
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
data: expect.objectContaining({ 'sentry.source': 'url' }),
}),
}),
}),
);
})
.unordered()
.start(signal);
await runner.makeRequest('get', '/error', { expectError: true });
await runner.completed();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "worker-name",
"compatibility_date": "2025-06-17",
"main": "index.ts",
"compatibility_flags": ["nodejs_compat"],
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ it('Tracing headers', async ({ signal }) => {
const [SERVER_URL, closeTestServer] = await createTestServer()
.get('/', headers => {
expect(headers['baggage']).toEqual(expect.any(String));
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})-0$/));
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-0');
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})$/));
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000');
expect(headers['traceparent']).toEqual(expect.stringMatching(/^00-([a-f\d]{32})-([a-f\d]{16})-00$/));
})
.start();
Expand Down
9 changes: 9 additions & 0 deletions dev-packages/e2e-tests/test-applications/astro-4/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,14 @@
},
"volta": {
"extends": "../../package.json"
},
"sentryTest": {
"variants": [
{
"build-command": "env E2E_USE_SENTRY_TRACE_PROVIDER=1 pnpm test:build",
"assert-command": "env E2E_USE_SENTRY_TRACE_PROVIDER=1 pnpm test:assert",
"label": "astro-4 (sentry-trace-provider)"
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Sentry.init({
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
...(process.env.E2E_USE_SENTRY_TRACE_PROVIDER === '1'
? {
_experiments: {
useSentryTraceProvider: true,
},
}
: {}),
spotlight: true,
tunnel: 'http://localhost:3031/', // proxy server
});
9 changes: 9 additions & 0 deletions dev-packages/e2e-tests/test-applications/astro-5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,14 @@
},
"volta": {
"extends": "../../package.json"
},
"sentryTest": {
"variants": [
{
"build-command": "env E2E_USE_SENTRY_TRACE_PROVIDER=1 pnpm test:build",
"assert-command": "env E2E_USE_SENTRY_TRACE_PROVIDER=1 pnpm test:assert",
"label": "astro-5 (sentry-trace-provider)"
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,12 @@ Sentry.init({
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
...(process.env.E2E_USE_SENTRY_TRACE_PROVIDER === '1'
? {
_experiments: {
useSentryTraceProvider: true,
},
}
: {}),
tunnel: 'http://localhost:3031/', // proxy server
});
13 changes: 12 additions & 1 deletion dev-packages/e2e-tests/test-applications/astro-6/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"astro": "astro",
"start": "node ./dist/server/entry.mjs",
"test:build": "pnpm install && pnpm build",
"test:assert": "TEST_ENV=production playwright test"
"test:build:sentry-trace-provider": "E2E_USE_SENTRY_TRACE_PROVIDER=1 pnpm test:build",
"test:assert": "TEST_ENV=production playwright test",
"test:assert:sentry-trace-provider": "E2E_USE_SENTRY_TRACE_PROVIDER=1 pnpm test:assert"
},
"dependencies": {
"@astrojs/node": "^10.0.0",
Expand All @@ -21,5 +23,14 @@
"volta": {
"node": "22.22.0",
"extends": "../../package.json"
},
"sentryTest": {
"variants": [
{
"build-command": "pnpm test:build:sentry-trace-provider",
"assert-command": "pnpm test:assert:sentry-trace-provider",
"label": "astro-6 (sentry-trace-provider)"
}
]
}
}
Loading
Loading