Skip to content
Merged
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
11 changes: 10 additions & 1 deletion packages/cloudflare/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ClientOptions, Options, ServerRuntimeClientOptions } from '@sentry/core';
import { applySdkMetadata, debug, ServerRuntimeClient } from '@sentry/core';
import { applySdkMetadata, debug, ServerRuntimeClient, spanIsSampled } from '@sentry/core';
import { DEBUG_BUILD } from './debug-build';
import type { makeFlushLock } from './flush';
import type { CloudflareTransportOptions } from './transport';
Expand Down Expand Up @@ -44,6 +44,15 @@ export class CloudflareClient extends ServerRuntimeClient {
this._unsubscribeSpanStart = this.on('spanStart', span => {
const spanId = span.spanContext().spanId;
DEBUG_BUILD && debug.log('[CloudflareClient] Span started:', spanId);

// Negatively sampled spans never emit spanEnd,
// so tracking them would cause _pendingSpans to grow unboundedly.
// We should fix the inconsistent behavior for NonRecordingSpans in the future but
// for now, we just ignore them.
if (!spanIsSampled(span)) {
return;
}

this._pendingSpans.add(spanId);

if (!this._spanCompletionPromise) {
Expand Down
32 changes: 28 additions & 4 deletions packages/cloudflare/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { setAsyncLocalStorageAsyncContextStrategy } from '../src/async';
import { CloudflareClient, type CloudflareClientOptions } from '../src/client';
import { makeFlushLock } from '../src/flush';

const TRACE_FLAG_SAMPLED = 0x1;

const MOCK_CLIENT_OPTIONS: CloudflareClientOptions = {
dsn: 'https://public@dsn.ingest.sentry.io/1337',
stackParser: () => [],
Expand Down Expand Up @@ -232,7 +234,7 @@ describe('CloudflareClient', () => {

// Emit spanStart
const mockSpan = {
spanContext: () => ({ spanId: 'test-span-id' }),
spanContext: () => ({ spanId: 'test-span-id', traceFlags: TRACE_FLAG_SAMPLED }),
};
client.emit('spanStart', mockSpan as any);

Expand All @@ -249,7 +251,7 @@ describe('CloudflareClient', () => {
};

const mockSpan = {
spanContext: () => ({ spanId: 'test-span-id' }),
spanContext: () => ({ spanId: 'test-span-id', traceFlags: TRACE_FLAG_SAMPLED }),
};

// Start span
Expand All @@ -269,8 +271,12 @@ describe('CloudflareClient', () => {
_spanCompletionPromise: Promise<void> | null;
};

const mockSpan1 = { spanContext: () => ({ spanId: 'span-1' }) };
const mockSpan2 = { spanContext: () => ({ spanId: 'span-2' }) };
const mockSpan1 = {
spanContext: () => ({ spanId: 'span-1', traceFlags: TRACE_FLAG_SAMPLED }),
};
const mockSpan2 = {
spanContext: () => ({ spanId: 'span-2', traceFlags: TRACE_FLAG_SAMPLED }),
};

// Start both spans
client.emit('spanStart', mockSpan1 as any);
Expand All @@ -291,6 +297,24 @@ describe('CloudflareClient', () => {
await expect(completionPromise).resolves.toBeUndefined();
});

it('does not track negatively sampled spans', () => {
const client = new CloudflareClient(MOCK_CLIENT_OPTIONS);

const privateClient = client as unknown as {
_pendingSpans: Set<string>;
_spanCompletionPromise: Promise<void> | null;
};

const nonRecordingSpan = {
spanContext: () => ({ spanId: 'non-recording-span-id', traceFlags: 0 }),
};

client.emit('spanStart', nonRecordingSpan as any);

expect(privateClient._pendingSpans.has('non-recording-span-id')).toBe(false);
expect(privateClient._spanCompletionPromise).toBeNull();
});

it('does not track spans after dispose', () => {
const client = new CloudflareClient(MOCK_CLIENT_OPTIONS);

Expand Down
Loading