UNPKG

@sentry/browser

Version:
222 lines (219 loc) 8.72 kB
import { addFetchInstrumentationHandler, instrumentFetchRequest, parseUrl, stripDataUrlContent, spanToJSON, hasSpanStreamingEnabled, timestampInSeconds, hasSpansEnabled, setHttpStatus, stripUrlQueryAndFragment, getClient, getActiveSpan, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, getLocationHref, stringMatchesSomePattern, getTraceData } from '@sentry/core/browser'; import { addXhrInstrumentationHandler, addPerformanceInstrumentationHandler, resourceTimingToSpanAttributes, SENTRY_XHR_DATA_KEY, parseXhrResponseHeaders } from '@sentry-internal/browser-utils'; import { getFullURL, createHeadersSafely, isPerformanceResourceTiming, baggageHeaderHasSentryValues } from './utils.js'; const defaultRequestInstrumentationOptions = { traceFetch: true, traceXHR: true, enableHTTPTimings: true, trackFetchStreamPerformance: false }; function instrumentOutgoingRequests(client, _options) { const { traceFetch, traceXHR, shouldCreateSpanForRequest, enableHTTPTimings, tracePropagationTargets, onRequestSpanStart, onRequestSpanEnd } = { ...defaultRequestInstrumentationOptions, ..._options }; const shouldCreateSpan = typeof shouldCreateSpanForRequest === "function" ? shouldCreateSpanForRequest : (_) => true; const shouldAttachHeadersWithTargets = (url) => shouldAttachHeaders(url, tracePropagationTargets); const spans = {}; const propagateTraceparent = client.getOptions().propagateTraceparent; if (traceFetch) { addFetchInstrumentationHandler((handlerData) => { const createdSpan = instrumentFetchRequest(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans, { propagateTraceparent, onRequestSpanEnd }); if (createdSpan) { const fullUrl = getFullURL(handlerData.fetchData.url); const host = fullUrl ? parseUrl(fullUrl).host : void 0; createdSpan.setAttributes({ "http.url": fullUrl ? stripDataUrlContent(fullUrl) : void 0, "server.address": host }); if (enableHTTPTimings) { addHTTPTimings(createdSpan, client); } onRequestSpanStart?.(createdSpan, { headers: handlerData.headers }); } }); } if (traceXHR) { addXhrInstrumentationHandler((handlerData) => { const createdSpan = xhrCallback( handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans, propagateTraceparent, onRequestSpanEnd ); if (createdSpan) { if (enableHTTPTimings) { addHTTPTimings(createdSpan, client); } onRequestSpanStart?.(createdSpan, { headers: createHeadersSafely(handlerData.xhr.__sentry_xhr_v3__?.request_headers) }); } }); } } const HTTP_TIMING_WAIT_MS = 300; function addHTTPTimings(span, client) { const { url } = spanToJSON(span).data; if (!url || typeof url !== "string") { return; } let onEntryFound = () => void setTimeout(unsubscribePerformanceObsever); if (hasSpanStreamingEnabled(client)) { const originalEnd = span.end.bind(span); span.end = (endTimestamp) => { const capturedEndTimestamp = endTimestamp ?? timestampInSeconds(); let isEnded = false; const endSpanAndCleanup = () => { if (isEnded) { return; } isEnded = true; setTimeout(unsubscribePerformanceObsever); originalEnd(capturedEndTimestamp); clearTimeout(fallbackTimeout); }; onEntryFound = endSpanAndCleanup; const fallbackTimeout = setTimeout(endSpanAndCleanup, HTTP_TIMING_WAIT_MS); }; } const unsubscribePerformanceObsever = addPerformanceInstrumentationHandler("resource", ({ entries }) => { entries.forEach((entry) => { if (isPerformanceResourceTiming(entry) && entry.name.endsWith(url)) { span.setAttributes(resourceTimingToSpanAttributes(entry)); onEntryFound(); } }); }); } function shouldAttachHeaders(targetUrl, tracePropagationTargets) { const href = getLocationHref(); if (!href) { const isRelativeSameOriginRequest = !!targetUrl.match(/^\/(?!\/)/); if (!tracePropagationTargets) { return isRelativeSameOriginRequest; } else { return stringMatchesSomePattern(targetUrl, tracePropagationTargets); } } else { let resolvedUrl; let currentOrigin; try { resolvedUrl = new URL(targetUrl, href); currentOrigin = new URL(href).origin; } catch { return false; } const isSameOriginRequest = resolvedUrl.origin === currentOrigin; if (!tracePropagationTargets) { return isSameOriginRequest; } else { return stringMatchesSomePattern(resolvedUrl.toString(), tracePropagationTargets) || isSameOriginRequest && stringMatchesSomePattern(resolvedUrl.pathname, tracePropagationTargets); } } } function xhrCallback(handlerData, shouldCreateSpan, shouldAttachHeaders2, spans, propagateTraceparent, onRequestSpanEnd) { const xhr = handlerData.xhr; const sentryXhrData = xhr?.[SENTRY_XHR_DATA_KEY]; if (!xhr || xhr.__sentry_own_request__ || !sentryXhrData) { return void 0; } const { url, method } = sentryXhrData; const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(url); if (handlerData.endTimestamp) { const spanId = xhr.__sentry_xhr_span_id__; if (!spanId) return; const span2 = spans[spanId]; if (span2) { if (shouldCreateSpanResult && sentryXhrData.status_code !== void 0) { setHttpStatus(span2, sentryXhrData.status_code); span2.end(); onRequestSpanEnd?.(span2, { headers: createHeadersSafely(parseXhrResponseHeaders(xhr)), error: handlerData.error }); } delete spans[spanId]; } return void 0; } const fullUrl = getFullURL(url); const parsedUrl = fullUrl ? parseUrl(fullUrl) : parseUrl(url); const urlForSpanName = stripDataUrlContent(stripUrlQueryAndFragment(url)); const client = getClient(); const hasParent = !!getActiveSpan(); const shouldEmitSpan = hasParent || !!client && hasSpanStreamingEnabled(client); const span = shouldCreateSpanResult && shouldEmitSpan ? startInactiveSpan({ name: `${method} ${urlForSpanName}`, attributes: { url: stripDataUrlContent(url), type: "xhr", "http.method": method, "http.url": fullUrl ? stripDataUrlContent(fullUrl) : void 0, "server.address": parsedUrl?.host, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.http.browser", [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "http.client", ...parsedUrl?.search && { "http.query": parsedUrl?.search }, ...parsedUrl?.hash && { "http.fragment": parsedUrl?.hash } } }) : new SentryNonRecordingSpan(); if (shouldCreateSpanResult && !shouldEmitSpan) { client?.recordDroppedEvent("no_parent_span", "span"); } xhr.__sentry_xhr_span_id__ = span.spanContext().spanId; spans[xhr.__sentry_xhr_span_id__] = span; if (shouldAttachHeaders2(url)) { addTracingHeadersToXhrRequest( xhr, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), // we do not want to use the span as base for the trace headers, // which means that the headers will be generated from the scope and the sampling decision is deferred hasSpansEnabled() && shouldEmitSpan ? span : void 0, propagateTraceparent ); } if (client) { client.emit("beforeOutgoingRequestSpan", span, handlerData); } return span; } function addTracingHeadersToXhrRequest(xhr, span, propagateTraceparent) { const { "sentry-trace": sentryTrace, baggage, traceparent } = getTraceData({ span, propagateTraceparent }); if (sentryTrace) { setHeaderOnXhr(xhr, sentryTrace, baggage, traceparent); } } function setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader, traceparentHeader) { const originalHeaders = xhr.__sentry_xhr_v3__?.request_headers; if (originalHeaders?.["sentry-trace"] || !xhr.setRequestHeader) { return; } try { xhr.setRequestHeader("sentry-trace", sentryTraceHeader); if (traceparentHeader && !originalHeaders?.["traceparent"]) { xhr.setRequestHeader("traceparent", traceparentHeader); } if (sentryBaggageHeader) { const originalBaggageHeader = originalHeaders?.["baggage"]; if (!originalBaggageHeader || !baggageHeaderHasSentryValues(originalBaggageHeader)) { xhr.setRequestHeader("baggage", sentryBaggageHeader); } } } catch { } } export { defaultRequestInstrumentationOptions, instrumentOutgoingRequests, shouldAttachHeaders }; //# sourceMappingURL=request.js.map