Skip to content

Outgoing HTTP spans intermittently suppressed in serverless (Vercel) — suppressTracing() context leaking / Prisma span noise & config gaps #18866

@jam3gw

Description

@jam3gw

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

10.34.0

Framework Version

No response

Link to Sentry event

No response

Reproduction Example/SDK Setup

Traces showing suppression (outgoing HTTP spans become Noop):

Clean traces (no suppression, for comparison):

Steps to Reproduce

  1. Deploy a Next.js app to Vercel serverless with @sentry/nextjs and tracesSampleRate: 1.0
  2. Create API routes that make outgoing HTTP requests (e.g., fetch() to external APIs, Twilio SDK calls)
  3. Enable debug logging: debug: true in Sentry config
  4. Send traffic to the API routes
  5. Observe debug logs showing Instrumentation suppressed, returning Noop Span for outgoing HTTP requests

Expected Result

All outgoing HTTP requests within a traced route handler should produce child spans, e.g.:

  • fetch() calls to external APIs
  • Twilio SDK HTTP requests
  • Any other instrumented HTTP client calls

Timing pattern observed:
1767803936358 | ✅ Starting sampled root span - POST /api/routes/upsert
1767803936360 | ✅ Starting span - executing api route
1767803936384 | ✅ prisma:client:operation starts
... | ✅ (many successful Prisma DB operations)
1767803936460 | ✅ Last Prisma operation completes
1767803936461 | ❌ Instrumentation suppressed, returning Noop Span
1767803936461 | ❌ [Tracing] Not injecting trace data for url
1767803936463 | ❌ Instrumentation suppressed (x2 more)
1767803936465 | ✅ Finishing span "executing api route"
1767803936466 | ✅ Finishing root span

The suppression consistently happens:

  1. AFTER all database/business logic completes
  2. BEFORE the response is sent
  3. Sometimes AFTER the root span finishes (likely during Sentry telemetry flush)

Actual Result

Outgoing HTTP requests are silently suppressed with:

Sentry Logger [log]: Instrumentation suppressed, returning Noop Span
Sentry Logger [log]: [Tracing] Not injecting trace data for url because tracing is suppressed.

Pattern:

  • ✅ Root span starts successfully
  • ✅ Child spans for route execution work fine
  • ❌ Outgoing HTTP requests (fetch calls) are suppressed → Noop spans
  • ✅ Root span finishes successfully

Scale of impact:

Route Traces Analyzed Avg Suppressions/Request
/api/routes/upsert 224 ~6.5
/api/routes/stops 140 ~6.6
/api/notifications/delay 27 ~7.3
/api/texts/initiate 16 ~6.4
/api/metrics 12 ~6.7
/api/calls/initiate 8 ~6.3

Some routes are completely clean (0 suppressions): /api/health, /api/cron/data-cleanup, /api/cron/eta-notifications.

Additional Context

Root Cause Analysis

After investigating the SDK source, we believe the issue is caused by suppressTracing() context leaking in serverless environments.

How suppressTracing() works:

typescript
// packages/opentelemetry/src/utils/suppressTracing.ts
export function suppressTracing<T>(callback: () => T): T {
const ctx = suppressTracingImpl(context.active());
return context.with(ctx, callback);
}

This is used by the SDK transport to prevent creating spans for Sentry's own HTTP requests (avoiding infinite recursion). However, in Vercel serverless, this suppression context appears to leak into subsequent async operations.

Evidence from logs:

Sentry Logger [log]: Flushing events...
Sentry Logger [log]: SpanExporter exported 0 spans, 1 spans are waiting for their parent spans to finish
Sentry Logger [log]: Flushing outcomes...
Sentry Logger [log]: No outcomes to send
Sentry Logger [log]: Instrumentation suppressed, returning Noop Span <-- Leak!
Sentry Logger [log]: [Tracing] Not injecting trace data for url because tracing is suppressed.

Hypotheses (ranked by likelihood):

  1. AsyncLocalStorage / async context propagation timing issue (HIGH): suppression context propagated to concurrent/subsequent ops when transport flushes
  2. Shared context in edge/serverless environments (MEDIUM): Vercel's runtime may not fully isolate async contexts (SDK already has IsolatedPromiseBuffer for edge, suggesting awareness of this)
  3. Promise chain context loss (MEDIUM): transport uses suppressTracing(() => fetch(...).then(...)) which may not restore context cleanly

Relevant code locations:

  • packages/opentelemetry/src/utils/suppressTracing.ts
  • packages/vercel-edge/src/transports/index.ts
  • packages/node-core/src/transports/http.ts

Environment

  • Platform: Vercel Serverless (Node.js runtime)
  • Framework: Next.js 15.4.9
  • SDK: @sentry/nextjs 10.27.0 (also reproduced on 10.34.0)
  • Database: Prisma 6.13.0
  • Sample Rate: tracesSampleRate: 1.0

Additional Context

No response

Priority

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.

Metadata

Metadata

Assignees

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions