UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

175 lines (172 loc) • 6.15 kB
import { URL_FRAGMENT, URL_QUERY, HTTP_REQUEST_METHOD } from '@sentry/conventions/attributes'; import { getClient, shouldPropagateTraceForUrl, spanIsIgnored, getActiveSpan, withActiveSpan, getTraceData, mergeBaggageHeaders, getBreadcrumbLogLevelFromHttpStatusCode, addBreadcrumb, debug, parseUrl, getUrlFragment, filterCollectedUrlQuery, getUrlQuery, getSanitizedUrlString } from '@sentry/core'; const SENTRY_TRACE_HEADER = "sentry-trace"; const SENTRY_BAGGAGE_HEADER = "baggage"; const W3C_TRACEPARENT_HEADER = "traceparent"; function addTracePropagationHeadersToFetchRequest(request, propagationDecisionMap, span) { const url = getAbsoluteUrl(request.origin, request.path); const { tracePropagationTargets, propagateTraceparent } = getClient()?.getOptions() || {}; if (!shouldPropagateTraceForUrl(url, tracePropagationTargets, propagationDecisionMap)) { return; } const spanForTraceHeaders = span && spanIsIgnored(span) && getActiveSpan() ? void 0 : span; const addedHeaders = spanForTraceHeaders ? withActiveSpan(spanForTraceHeaders, () => getTraceData({ propagateTraceparent })) : getTraceData({ propagateTraceparent }); if (!addedHeaders) { return; } const { "sentry-trace": sentryTrace, baggage, traceparent } = addedHeaders; const requestHeaders = Array.isArray(request.headers) ? normalizeUndiciHeaderPairs(request.headers) : stringToArrayHeaders(request.headers); _deduplicateArrayHeader(requestHeaders, SENTRY_TRACE_HEADER); _deduplicateArrayHeader(requestHeaders, SENTRY_BAGGAGE_HEADER); if (propagateTraceparent) { _deduplicateArrayHeader(requestHeaders, W3C_TRACEPARENT_HEADER); } const hasExistingSentryTraceHeader = _findExistingHeaderIndex(requestHeaders, SENTRY_TRACE_HEADER) !== -1; if (!hasExistingSentryTraceHeader) { if (sentryTrace) { requestHeaders.push(SENTRY_TRACE_HEADER, sentryTrace); } if (traceparent && _findExistingHeaderIndex(requestHeaders, "traceparent") === -1) { requestHeaders.push("traceparent", traceparent); } const existingBaggageIndex = _findExistingHeaderIndex(requestHeaders, SENTRY_BAGGAGE_HEADER); if (baggage && existingBaggageIndex === -1) { requestHeaders.push(SENTRY_BAGGAGE_HEADER, baggage); } else if (baggage) { const existingBaggageValue = requestHeaders[existingBaggageIndex + 1]; const merged = mergeBaggageHeaders(existingBaggageValue, baggage); if (merged) { requestHeaders[existingBaggageIndex + 1] = merged; } } } if (Array.isArray(request.headers)) { request.headers.splice(0, request.headers.length, ...requestHeaders); } else { request.headers = arrayToStringHeaders(requestHeaders); } } function normalizeUndiciHeaderPairs(headers) { const out = []; for (let i = 0; i < headers.length; i++) { const entry = headers[i]; if (i % 2 === 0) { out.push(typeof entry === "string" ? entry : String(entry)); } else { out.push(Array.isArray(entry) ? entry.join(", ") : entry ?? ""); } } return out; } function stringToArrayHeaders(requestHeaders) { const headersArray = requestHeaders.split("\r\n"); const headers = []; for (const header of headersArray) { try { const colonIndex = header.indexOf(":"); if (colonIndex === -1) { continue; } const key = header.slice(0, colonIndex).trim(); const value = header.slice(colonIndex + 1).trim(); if (key) { headers.push(key, value); } } catch { debug.warn(`Failed to convert string request header to array header: ${header}`); } } return headers; } function arrayToStringHeaders(headers) { const headerPairs = []; for (let i = 0; i < headers.length; i += 2) { const key = headers[i]; const value = headers[i + 1]; if (!key || value == null) { continue; } headerPairs.push(`${key}: ${value}`); } if (!headerPairs.length) { return ""; } return headerPairs.join("\r\n").concat("\r\n"); } function _deduplicateArrayHeader(headers, headerName) { let firstIndex = -1; for (let i = 0; i < headers.length; i += 2) { if (headers[i] !== headerName) { continue; } if (firstIndex === -1) { firstIndex = i; continue; } const firstHeaderValue = headers[firstIndex + 1]; if (headerName === SENTRY_BAGGAGE_HEADER && firstHeaderValue) { const merged = mergeBaggageHeaders(headers[i + 1], firstHeaderValue); if (merged) { headers[firstIndex + 1] = merged; } } headers.splice(i, 2); i -= 2; } } function _findExistingHeaderIndex(headers, name) { return headers.findIndex((header, i) => i % 2 === 0 && header === name); } function addFetchRequestBreadcrumb(request, response) { const data = getBreadcrumbData(request); const statusCode = response.statusCode; const level = getBreadcrumbLogLevelFromHttpStatusCode(statusCode); addBreadcrumb( { category: "http", data: { status_code: statusCode, ...data }, type: "http", level }, { event: "response", request, response } ); } function getBreadcrumbData(request) { try { const url = getAbsoluteUrl(request.origin, request.path); const parsedUrl = parseUrl(url); return { url: getSanitizedUrlString(parsedUrl), // eslint-disable-next-line typescript/no-deprecated [HTTP_REQUEST_METHOD]: request.method || "GET", [URL_QUERY]: filterCollectedUrlQuery(getUrlQuery(parsedUrl.search)), [URL_FRAGMENT]: getUrlFragment(parsedUrl.hash) }; } catch { return {}; } } function getAbsoluteUrl(origin, path = "/") { try { const url = new URL(path, origin); return url.toString(); } catch { const url = `${origin}`; if (url.endsWith("/") && path.startsWith("/")) { return `${url}${path.slice(1)}`; } if (!url.endsWith("/") && !path.startsWith("/")) { return `${url}/${path}`; } return `${url}${path}`; } } export { addFetchRequestBreadcrumb, addTracePropagationHeadersToFetchRequest, getAbsoluteUrl }; //# sourceMappingURL=outgoingFetchRequest.js.map