@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
203 lines (166 loc) • 6.87 kB
JavaScript
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