|
| 1 | +import { ATTR_URL_QUERY, SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions'; |
| 2 | +import { type Span, type SpanAttributes, GLOBAL_OBJ, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; |
| 3 | +import { isSentryRequestSpan } from '@sentry/opentelemetry'; |
| 4 | +import { ATTR_NEXT_SPAN_TYPE } from '../nextSpanAttributes'; |
| 5 | +import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../span-attributes-with-logic-attached'; |
| 6 | + |
| 7 | +const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { |
| 8 | + _sentryRewritesTunnelPath?: string; |
| 9 | +}; |
| 10 | + |
| 11 | +/** |
| 12 | + * Drops spans for tunnel requests from middleware or fetch instrumentation. |
| 13 | + * This catches both: |
| 14 | + * 1. Requests to the local tunnel route (before rewrite) |
| 15 | + * 2. Requests to Sentry ingest (after rewrite) |
| 16 | + */ |
| 17 | +export function dropMiddlewareTunnelRequests(span: Span, attrs: SpanAttributes | undefined): void { |
| 18 | + // Only filter middleware spans or HTTP fetch spans |
| 19 | + const isMiddleware = attrs?.[ATTR_NEXT_SPAN_TYPE] === 'Middleware.execute'; |
| 20 | + // The fetch span could be originating from rewrites re-writing a tunnel request |
| 21 | + // So we want to filter it out |
| 22 | + const isFetchSpan = attrs?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.node_fetch'; |
| 23 | + |
| 24 | + // If the span is not a middleware span or a fetch span, return |
| 25 | + if (!isMiddleware && !isFetchSpan) { |
| 26 | + return; |
| 27 | + } |
| 28 | + |
| 29 | + // Check if this is either a tunnel route request or a Sentry ingest request |
| 30 | + const isTunnel = isTunnelRouteSpan(attrs || {}); |
| 31 | + const isSentry = isSentryRequestSpan(span); |
| 32 | + |
| 33 | + if (isTunnel || isSentry) { |
| 34 | + // Mark the span to be dropped |
| 35 | + span.setAttribute(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true); |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +/** |
| 40 | + * Checks if a span's HTTP target matches the tunnel route. |
| 41 | + */ |
| 42 | +function isTunnelRouteSpan(spanAttributes: Record<string, unknown>): boolean { |
| 43 | + // Don't use process.env here because it will have a different value in the build and runtime |
| 44 | + // We want to use the one in build |
| 45 | + const tunnelPath = globalWithInjectedValues._sentryRewritesTunnelPath || process.env._sentryRewritesTunnelPath; |
| 46 | + if (!tunnelPath) { |
| 47 | + return false; |
| 48 | + } |
| 49 | + |
| 50 | + // Check both http.target (older) and url.query (newer) attributes |
| 51 | + // eslint-disable-next-line deprecation/deprecation |
| 52 | + const httpTarget = spanAttributes[SEMATTRS_HTTP_TARGET] || spanAttributes[ATTR_URL_QUERY]; |
| 53 | + |
| 54 | + if (typeof httpTarget === 'string') { |
| 55 | + // Extract pathname from the target (e.g., "/tunnel?o=123&p=456" -> "/tunnel") |
| 56 | + const pathname = httpTarget.split('?')[0] || ''; |
| 57 | + |
| 58 | + return pathname.startsWith(tunnelPath); |
| 59 | + } |
| 60 | + |
| 61 | + return false; |
| 62 | +} |
0 commit comments