Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class MyDurableObjectBase extends DurableObject<Env> {
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
}),
MyDurableObjectBase,
Expand All @@ -25,7 +24,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
}),
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,206 +1,119 @@
import { expect, it } from 'vitest';
import type { Event } from '@sentry/core';
import type { SerializedStreamedSpan } from '@sentry/core';
import { createRunner } from '../../../../runner';
import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils';

it('propagates trace from worker to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({
signal,
}) => {
let workerTraceId: string | undefined;
let workerSpanId: string | undefined;
let doTraceId: string | undefined;
let doParentSpanId: string | undefined;
let workerSpan: SerializedStreamedSpan | undefined;
let doSpan: SerializedStreamedSpan | undefined;

const runner = createRunner(__dirname)
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;

expect(transactionEvent).toEqual(
expect.objectContaining({
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
data: expect.objectContaining({
'sentry.origin': 'auto.http.cloudflare',
}),
origin: 'auto.http.cloudflare',
}),
}),
transaction: 'GET /hello',
}),
);
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);

expect(getSpanOp(segmentSpan!)).toBe('http.server');
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
// `/hello` is a raw URL, so the streamed segment name keeps the method only.
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' });
doSpan = segmentSpan;
})
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;

expect(transactionEvent).toEqual(
expect.objectContaining({
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
data: expect.objectContaining({
'sentry.origin': 'auto.http.cloudflare',
}),
origin: 'auto.http.cloudflare',
}),
}),
transaction: 'GET /',
}),
);
workerTraceId = transactionEvent.contexts?.trace?.trace_id as string;
workerSpanId = transactionEvent.contexts?.trace?.span_id as string;
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);

expect(getSpanOp(segmentSpan!)).toBe('http.server');
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
expect(segmentSpan?.name).toBe('GET /');
workerSpan = segmentSpan;
})
.unordered()
.start(signal);
await runner.makeRequest('get', '/');
await runner.completed();

expect(workerTraceId).toBeDefined();
expect(doTraceId).toBe(workerTraceId);

expect(workerSpanId).toBeDefined();
expect(doParentSpanId).toBe(workerSpanId);
expect(workerSpan?.trace_id).toBeDefined();
expect(doSpan?.trace_id).toBe(workerSpan?.trace_id);
expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id);
});

it('propagates trace from queue handler to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({
signal,
}) => {
let queueTraceId: string | undefined;
let queueSpanId: string | undefined;
let doTraceId: string | undefined;
let doParentSpanId: string | undefined;
let queueSpan: SerializedStreamedSpan | undefined;
let doSpan: SerializedStreamedSpan | undefined;

const runner = createRunner(__dirname)
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;

expect(transactionEvent).toEqual(
expect.objectContaining({
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
data: expect.objectContaining({
'sentry.origin': 'auto.http.cloudflare',
}),
origin: 'auto.http.cloudflare',
}),
}),
transaction: 'GET /hello',
}),
);
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);

expect(getSpanOp(segmentSpan!)).toBe('http.server');
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' });
doSpan = segmentSpan;
})
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;

expect(transactionEvent).toEqual(
expect.objectContaining({
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'queue.process',
data: expect.objectContaining({
'sentry.origin': 'auto.faas.cloudflare.queue',
}),
origin: 'auto.faas.cloudflare.queue',
}),
}),
transaction: 'process my-queue',
}),
);
queueTraceId = transactionEvent.contexts?.trace?.trace_id as string;
queueSpanId = transactionEvent.contexts?.trace?.span_id as string;
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);

expect(segmentSpan?.name).toBe('process my-queue');
expect(getSpanOp(segmentSpan!)).toBe('queue.process');
expect(segmentSpan?.attributes['sentry.origin']).toEqual({
type: 'string',
value: 'auto.faas.cloudflare.queue',
});
queueSpan = segmentSpan;
})
// Also expect the fetch transaction from the /queue/send request
// Also expect the fetch span from the /queue/send request
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;

expect(transactionEvent).toEqual(
expect.objectContaining({
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
data: expect.objectContaining({
'sentry.origin': 'auto.http.cloudflare',
}),
origin: 'auto.http.cloudflare',
}),
}),
transaction: 'GET /queue/send',
}),
);
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);

expect(getSpanOp(segmentSpan!)).toBe('http.server');
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/queue/send' });
})
.unordered()
.start(signal);
// The fetch handler sends a message to the queue, which triggers the queue consumer
await runner.makeRequest('get', '/queue/send');
await runner.completed();

expect(queueTraceId).toBeDefined();
expect(doTraceId).toBe(queueTraceId);

expect(queueSpanId).toBeDefined();
expect(doParentSpanId).toBe(queueSpanId);
expect(queueSpan?.trace_id).toBeDefined();
expect(doSpan?.trace_id).toBe(queueSpan?.trace_id);
expect(doSpan?.parent_span_id).toBe(queueSpan?.span_id);
});

it('propagates trace from scheduled handler to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({
signal,
}) => {
let scheduledTraceId: string | undefined;
let scheduledSpanId: string | undefined;
let doTraceId: string | undefined;
let doParentSpanId: string | undefined;
let scheduledSpan: SerializedStreamedSpan | undefined;
let doSpan: SerializedStreamedSpan | undefined;

const runner = createRunner(__dirname)
.withWranglerArgs('--test-scheduled')
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;

expect(transactionEvent).toEqual(
expect.objectContaining({
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
data: expect.objectContaining({
'sentry.origin': 'auto.http.cloudflare',
}),
origin: 'auto.http.cloudflare',
}),
}),
transaction: 'GET /hello',
}),
);
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);

expect(getSpanOp(segmentSpan!)).toBe('http.server');
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' });
doSpan = segmentSpan;
})
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;

expect(transactionEvent).toEqual(
expect.objectContaining({
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'function',
data: expect.objectContaining({
'sentry.origin': 'auto.faas.cloudflare.scheduled',
}),
origin: 'auto.faas.cloudflare.scheduled',
}),
}),
}),
);
scheduledTraceId = transactionEvent.contexts?.trace?.trace_id as string;
scheduledSpanId = transactionEvent.contexts?.trace?.span_id as string;
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);

expect(getSpanOp(segmentSpan!)).toBe('function');
expect(segmentSpan?.attributes['sentry.origin']).toEqual({
type: 'string',
value: 'auto.faas.cloudflare.scheduled',
});
scheduledSpan = segmentSpan;
})
.unordered()
.start(signal);
await runner.makeRequest('get', '/__scheduled?cron=*+*+*+*+*');
await runner.completed();

expect(scheduledTraceId).toBeDefined();
expect(doTraceId).toBe(scheduledTraceId);

expect(scheduledSpanId).toBeDefined();
expect(doParentSpanId).toBe(scheduledSpanId);
expect(scheduledSpan?.trace_id).toBeDefined();
expect(doSpan?.trace_id).toBe(scheduledSpan?.trace_id);
expect(doSpan?.parent_span_id).toBe(scheduledSpan?.span_id);
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class MyDurableObjectBase extends DurableObject<Env> {
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
}),
MyDurableObjectBase,
Expand All @@ -34,7 +33,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
// Both targets are written in a casing the bindings do not use, and the regex carries the `g`
// flag, which makes `test()` stateful unless the SDK normalizes it away.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { expect, it } from 'vitest';
import type { Envelope, Event } from '@sentry/core';
import type { Envelope, SerializedStreamedSpan } from '@sentry/core';
import { createRunner } from '../../../../runner';
import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils';

it('propagates trace over RPC when the binding casing differs from rpcTracePropagationBindings', async ({ signal }) => {
const transactionsByName = new Map<string, Event>();
const segmentSpansByName = new Map<string, SerializedStreamedSpan>();

const collect = (envelope: Envelope): void => {
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
transactionsByName.set(transactionEvent.transaction as string, transactionEvent);
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
expect(segmentSpan).toBeDefined();
segmentSpansByName.set(segmentSpan!.name, segmentSpan!);
};

const runner = createRunner(__dirname)
Expand All @@ -23,16 +25,18 @@ it('propagates trace over RPC when the binding casing differs from rpcTracePropa

await runner.completed();

const worker = transactionsByName.get('GET /rpc/all');
expect(worker?.contexts?.trace?.op).toBe('http.server');
// `/rpc/all` is a raw URL, so the streamed segment name keeps the method only.
const worker = segmentSpansByName.get('GET');
expect(getSpanOp(worker!)).toBe('http.server');
expect(worker?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/all' });

// `sayHello` comes from the string target, `alpha` and `beta` from the regex target. `beta` is the
// one a stateful `g` regex would miss, because `alpha` already advanced its `lastIndex`.
for (const methodName of ['sayHello', 'alpha', 'beta']) {
const durableObject = transactionsByName.get(methodName);
const durableObject = segmentSpansByName.get(methodName);

expect(durableObject?.contexts?.trace?.op).toBe('rpc');
expect(durableObject?.contexts?.trace?.trace_id).toBe(worker?.contexts?.trace?.trace_id);
expect(durableObject?.contexts?.trace?.parent_span_id).toBe(worker?.contexts?.trace?.span_id);
expect(getSpanOp(durableObject!)).toBe('rpc');
expect(durableObject?.trace_id).toBe(worker?.trace_id);
expect(durableObject?.parent_span_id).toBe(worker?.span_id);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class MyDurableObjectBase extends DurableObject<Env> {
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
}),
MyDurableObjectBase,
Expand All @@ -24,7 +23,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
}),
{
Expand Down
Loading
Loading