Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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),
// A new (head-of-trace) TwP trace does not stamp a local transaction in its DSC; the DSC is
// resolved from the scope. Continued traces still carry the upstream transaction.
...(includeTransaction && { transaction: expect.any(String) }),
},
},
[[{ type: 'event' }, expectedEvent(event, { sdk })]],
Expand Down
40 changes: 22 additions & 18 deletions dev-packages/cloudflare-integration-tests/suites/basic/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ import { createRunner } from '../../runner';
it('Basic error in fetch handler', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(
eventEnvelope({
level: 'error',
exception: {
values: [
{
type: 'Error',
value: 'This is a test error from the Cloudflare integration tests',
stacktrace: {
frames: expect.any(Array),
eventEnvelope(
{
level: 'error',
exception: {
values: [
{
type: 'Error',
value: 'This is a test error from the Cloudflare integration tests',
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),
},
// A new (head-of-trace) TwP trace does not stamp a local transaction in its DSC.
},
request: {
headers: expect.any(Object),
method: 'GET',
url: expect.any(String),
},
}),
{ includeTransaction: false },
),
)
.start(signal);
await runner.makeRequest('get', '/', { expectError: true });
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(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Was this issue specifically related to Cloudflare? Just wondering why there is specifically a CF test for it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not CF specific but it tests a core behavior of getDynamicSamplingContextFromSpan that we didn't test before.

I wanted to make sure that a in a non TwP case the transaction is also scrubbed when the source is url.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: This test is also passing on the latest develop, so I'm not sure if this should have reproduced anything specific or if it should just check if the old behavior is the same.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

(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"],
}
Loading
Loading