UNPKG

@sentry/core

Version:
203 lines (166 loc) 6.87 kB
import { parseUrl, generateSentryTraceHeader, dynamicSamplingContextToSentryBaggageHeader, isInstanceOf, BAGGAGE_HEADER_NAME } from '@sentry/utils'; import { getCurrentScope, getClient, getIsolationScope } from './currentScopes.js'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP } from './semanticAttributes.js'; import './tracing/errors.js'; import './debug-build.js'; import { hasTracingEnabled } from './utils/hasTracingEnabled.js'; import { getActiveSpan, spanToTraceHeader } from './utils/spanUtils.js'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan.js'; import { setHttpStatus, SPAN_STATUS_ERROR } from './tracing/spanstatus.js'; import { startInactiveSpan } from './tracing/trace.js'; import { getDynamicSamplingContextFromSpan, getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext.js'; /** * Create and track fetch request spans for usage in combination with `addFetchInstrumentationHandler`. * * @returns Span if a span was created, otherwise void. */ function instrumentFetchRequest( handlerData, shouldCreateSpan, shouldAttachHeaders, spans, spanOrigin = 'auto.http.browser', ) { if (!handlerData.fetchData) { return undefined; } const shouldCreateSpanResult = hasTracingEnabled() && shouldCreateSpan(handlerData.fetchData.url); if (handlerData.endTimestamp && shouldCreateSpanResult) { const spanId = handlerData.fetchData.__span; if (!spanId) return; const span = spans[spanId]; if (span) { endSpan(span, handlerData); // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete spans[spanId]; } return undefined; } const scope = getCurrentScope(); const client = getClient(); const { method, url } = handlerData.fetchData; const fullUrl = getFullURL(url); const host = fullUrl ? parseUrl(fullUrl).host : undefined; const hasParent = !!getActiveSpan(); const span = shouldCreateSpanResult && hasParent ? startInactiveSpan({ name: `${method} ${url}`, attributes: { url, type: 'fetch', 'http.method': method, 'http.url': fullUrl, 'server.address': host, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', }, }) : new SentryNonRecordingSpan(); handlerData.fetchData.__span = span.spanContext().spanId; spans[span.spanContext().spanId] = span; if (shouldAttachHeaders(handlerData.fetchData.url) && client) { const request = handlerData.args[0]; // In case the user hasn't set the second argument of a fetch call we default it to `{}`. handlerData.args[1] = handlerData.args[1] || {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any const options = handlerData.args[1]; options.headers = addTracingHeadersToFetchRequest( request, client, scope, options, // 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 hasTracingEnabled() && hasParent ? span : undefined, ); } return span; } /** * Adds sentry-trace and baggage headers to the various forms of fetch headers */ function addTracingHeadersToFetchRequest( request, // unknown is actually type Request but we can't export DOM types from this package, client, scope, options , span, ) { const isolationScope = getIsolationScope(); const { traceId, spanId, sampled, dsc } = { ...isolationScope.getPropagationContext(), ...scope.getPropagationContext(), }; const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), ); const headers = options.headers || (typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request ).headers : undefined); if (!headers) { return { 'sentry-trace': sentryTraceHeader, baggage: sentryBaggageHeader }; } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { const newHeaders = new Headers(headers ); newHeaders.append('sentry-trace', sentryTraceHeader); if (sentryBaggageHeader) { // If the same header is appended multiple times the browser will merge the values into a single request header. // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. newHeaders.append(BAGGAGE_HEADER_NAME, sentryBaggageHeader); } return newHeaders ; } else if (Array.isArray(headers)) { const newHeaders = [...headers, ['sentry-trace', sentryTraceHeader]]; if (sentryBaggageHeader) { // If there are multiple entries with the same key, the browser will merge the values into a single request header. // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. newHeaders.push([BAGGAGE_HEADER_NAME, sentryBaggageHeader]); } return newHeaders ; } else { const existingBaggageHeader = 'baggage' in headers ? headers.baggage : undefined; const newBaggageHeaders = []; if (Array.isArray(existingBaggageHeader)) { newBaggageHeaders.push(...existingBaggageHeader); } else if (existingBaggageHeader) { newBaggageHeaders.push(existingBaggageHeader); } if (sentryBaggageHeader) { newBaggageHeaders.push(sentryBaggageHeader); } return { ...(headers ), 'sentry-trace': sentryTraceHeader, baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined, }; } } function getFullURL(url) { try { const parsed = new URL(url); return parsed.href; } catch (e) { return undefined; } } function endSpan(span, handlerData) { if (handlerData.response) { setHttpStatus(span, handlerData.response.status); const contentLength = handlerData.response && handlerData.response.headers && handlerData.response.headers.get('content-length'); if (contentLength) { const contentLengthNum = parseInt(contentLength); if (contentLengthNum > 0) { span.setAttribute('http.response_content_length', contentLengthNum); } } } else if (handlerData.error) { span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); } span.end(); } export { addTracingHeadersToFetchRequest, instrumentFetchRequest }; //# sourceMappingURL=fetch.js.map