@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
224 lines (221 loc) • 10.1 kB
JavaScript
import { HTTP_RESPONSE_BODY_SIZE, URL_FULL, SERVER_ADDRESS, SERVER_PORT, URL_QUERY, URL_FRAGMENT, URL_DOMAIN, SENTRY_OP, HTTP_REQUEST_METHOD } from '@sentry/conventions/attributes';
import { HTTP_CLIENT } from '@sentry/conventions/op';
import { getClient } from './currentScopes.js';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes.js';
import { getActiveSpan } from './utils/spanUtils.js';
import { setHttpStatus, SPAN_STATUS_ERROR } from './tracing/spanstatus.js';
import { isRequest, isInstanceOf } from './utils/is.js';
import { hasSpansEnabled } from './utils/hasSpansEnabled.js';
import { SENTRY_BAGGAGE_KEY_PREFIX } from './utils/baggage.js';
import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan.js';
import { hasSpanStreamingEnabled } from './tracing/spans/hasSpanStreamingEnabled.js';
import { startInactiveSpan, spanIsIgnored } from './tracing/trace.js';
import { filterCollectedUrl, filterCollectedUrlQuery } from './utils/data-collection/filterCollectedUrl.js';
import { getTraceData } from './utils/traceData.js';
import { getUrlDomain, stripDataUrlContent, parseStringToURLObject, getSanitizedUrlStringFromUrlObject, isURLObjectRelative, getUrlQuery, getUrlFragment } from './utils/url.js';
function instrumentFetchRequest(handlerData, shouldCreateSpan, shouldAttachHeaders, spans, instrumentFetchRequestOptions) {
if (!handlerData.fetchData) {
return void 0;
}
const { method, url } = handlerData.fetchData;
const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(url);
if (handlerData.endTimestamp) {
const spanId = handlerData.fetchData.__span;
if (!spanId) return;
const span2 = spans[spanId];
if (span2) {
if (shouldCreateSpanResult) {
endSpan(span2, handlerData);
_callOnRequestSpanEnd(span2, handlerData, instrumentFetchRequestOptions);
}
delete spans[spanId];
}
return void 0;
}
const {
spanOrigin = "auto.http.browser",
propagateTraceparent = false,
urlBase
} = instrumentFetchRequestOptions ?? {};
const client = getClient();
const hasParent = !!getActiveSpan();
const shouldEmitSpan = hasParent || !!client && hasSpanStreamingEnabled(client);
const span = shouldCreateSpanResult && shouldEmitSpan ? startInactiveSpan(getSpanStartOptions(url, method, spanOrigin, client, urlBase)) : new SentryNonRecordingSpan();
const spanForTraceHeaders = spanIsIgnored(span) && hasParent ? void 0 : span;
if (shouldCreateSpanResult && !shouldEmitSpan) {
client?.recordDroppedEvent("no_parent_span", "span");
}
handlerData.fetchData.__span = span.spanContext().spanId;
spans[span.spanContext().spanId] = span;
if (shouldAttachHeaders(handlerData.fetchData.url)) {
const request = handlerData.args[0];
const options = { ...handlerData.args[1] || {} };
const headers = _INTERNAL_getTracingHeadersForFetchRequest(
request,
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
hasSpansEnabled() && shouldEmitSpan ? spanForTraceHeaders : void 0,
propagateTraceparent
);
if (headers) {
handlerData.args[1] = options;
options.headers = headers;
}
}
if (client) {
const fetchHint = {
input: handlerData.args,
response: handlerData.response,
startTimestamp: handlerData.startTimestamp,
endTimestamp: handlerData.endTimestamp
};
client.emit("beforeOutgoingRequestSpan", span, fetchHint);
}
return span;
}
function _callOnRequestSpanEnd(span, handlerData, instrumentFetchRequestOptions) {
instrumentFetchRequestOptions?.onRequestSpanEnd?.(span, {
headers: handlerData.response?.headers,
error: handlerData.error
});
}
function _INTERNAL_getTracingHeadersForFetchRequest(request, fetchOptionsObj, span, propagateTraceparent) {
const traceHeaders = getTraceData({ span, propagateTraceparent });
const sentryTrace = traceHeaders["sentry-trace"];
const baggage = traceHeaders.baggage;
const traceparent = traceHeaders.traceparent;
if (!sentryTrace) {
return void 0;
}
const originalHeaders = fetchOptionsObj.headers || (isRequest(request) ? request.headers : void 0);
if (!originalHeaders) {
return {
"sentry-trace": sentryTrace,
...baggage && { baggage },
...traceparent && { traceparent }
};
} else if (isHeaders(originalHeaders)) {
const newHeaders = new Headers(originalHeaders);
if (!newHeaders.get("sentry-trace")) {
newHeaders.set("sentry-trace", sentryTrace);
}
if (propagateTraceparent && traceparent && !newHeaders.get("traceparent")) {
newHeaders.set("traceparent", traceparent);
}
if (baggage) {
const prevBaggageHeader = newHeaders.get("baggage");
if (!prevBaggageHeader) {
newHeaders.set("baggage", baggage);
} else if (!baggageHeaderHasSentryBaggageValues(prevBaggageHeader)) {
newHeaders.set("baggage", `${prevBaggageHeader},${baggage}`);
}
}
return newHeaders;
} else if (isHeadersInitTupleArray(originalHeaders)) {
const newHeaders = [...originalHeaders];
if (!newHeaders.find((header) => header[0] === "sentry-trace")) {
newHeaders.push(["sentry-trace", sentryTrace]);
}
if (propagateTraceparent && traceparent && !newHeaders.find((header) => header[0] === "traceparent")) {
newHeaders.push(["traceparent", traceparent]);
}
const prevBaggageHeaderWithSentryValues = originalHeaders.find(
(header) => header[0] === "baggage" && typeof header[1] === "string" && baggageHeaderHasSentryBaggageValues(header[1])
);
if (baggage && !prevBaggageHeaderWithSentryValues) {
newHeaders.push(["baggage", baggage]);
}
return newHeaders;
} else {
const existingSentryTraceHeader = "sentry-trace" in originalHeaders ? originalHeaders["sentry-trace"] : void 0;
const existingTraceparentHeader = "traceparent" in originalHeaders ? originalHeaders.traceparent : void 0;
const existingBaggageHeader = "baggage" in originalHeaders ? originalHeaders.baggage : void 0;
const newBaggageHeaders = existingBaggageHeader ? Array.isArray(existingBaggageHeader) ? [...existingBaggageHeader] : [existingBaggageHeader] : [];
const prevBaggageHeaderWithSentryValues = existingBaggageHeader && (Array.isArray(existingBaggageHeader) ? existingBaggageHeader.find((headerItem) => baggageHeaderHasSentryBaggageValues(headerItem)) : baggageHeaderHasSentryBaggageValues(existingBaggageHeader));
if (baggage && !prevBaggageHeaderWithSentryValues) {
newBaggageHeaders.push(baggage);
}
const newHeaders = Object.assign({}, originalHeaders, {
"sentry-trace": existingSentryTraceHeader ?? sentryTrace,
...newBaggageHeaders.length > 0 && { baggage: newBaggageHeaders.join(",") }
});
if (propagateTraceparent && traceparent && !existingTraceparentHeader) {
newHeaders.traceparent = traceparent;
}
return newHeaders;
}
}
function endSpan(span, handlerData) {
if (handlerData.response) {
setHttpStatus(span, handlerData.response.status);
const contentLength = handlerData.response?.headers?.get("content-length");
if (contentLength) {
const contentLengthNum = parseInt(contentLength);
if (contentLengthNum > 0) {
span.setAttribute(HTTP_RESPONSE_BODY_SIZE, contentLengthNum);
}
}
} else if (handlerData.error) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" });
}
span.end();
}
function baggageHeaderHasSentryBaggageValues(baggageHeader) {
if (typeof baggageHeader !== "string") {
return false;
}
return baggageHeader.split(",").some((baggageEntry) => baggageEntry.trim().startsWith(SENTRY_BAGGAGE_KEY_PREFIX));
}
function isHeaders(headers) {
return typeof Headers !== "undefined" && isInstanceOf(headers, Headers);
}
function isHeadersInitTupleArray(headers) {
if (!Array.isArray(headers)) {
return false;
}
return headers.every(
(item) => Array.isArray(item) && item.length === 2 && typeof item[0] === "string"
);
}
function getSpanStartOptions(url, method, spanOrigin, client, urlBase) {
const isStreamed = !!client && hasSpanStreamingEnabled(client);
const domain = getUrlDomain(url, urlBase);
if (url.startsWith("data:")) {
const sanitizedUrl2 = stripDataUrlContent(url);
return {
name: isStreamed ? method : `${method} ${sanitizedUrl2}`,
attributes: getFetchSpanAttributes(url, void 0, method, spanOrigin, client, domain)
};
}
const parsedUrl = parseStringToURLObject(url);
const sanitizedUrl = parsedUrl ? getSanitizedUrlStringFromUrlObject(parsedUrl) : url;
return {
name: isStreamed ? domain ? `${method} ${domain}` : method : `${method} ${sanitizedUrl}`,
attributes: getFetchSpanAttributes(url, parsedUrl, method, spanOrigin, client, domain)
};
}
function getFetchSpanAttributes(url, parsedUrl, method, spanOrigin, client, domain) {
const attributes = {
[URL_FULL]: filterCollectedUrl(stripDataUrlContent(url), client),
type: "fetch",
// oxlint-disable-next-line typescript/no-deprecated
[HTTP_REQUEST_METHOD]: method,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin,
[SENTRY_OP]: HTTP_CLIENT,
[URL_DOMAIN]: domain
};
if (parsedUrl) {
if (!isURLObjectRelative(parsedUrl)) {
attributes[URL_FULL] = filterCollectedUrl(stripDataUrlContent(parsedUrl.href), client);
attributes[SERVER_ADDRESS] = parsedUrl.hostname;
attributes[SERVER_PORT] = parsedUrl.port ? Number(parsedUrl.port) : void 0;
}
attributes[URL_QUERY] = filterCollectedUrlQuery(getUrlQuery(parsedUrl.search), client);
attributes[URL_FRAGMENT] = getUrlFragment(parsedUrl.hash);
}
return attributes;
}
export { _INTERNAL_getTracingHeadersForFetchRequest, _callOnRequestSpanEnd, instrumentFetchRequest };
//# sourceMappingURL=fetch.js.map