Files
klz-cables.com/.pnpm-store/v10/files/a8/41e3ee12a33baef1c31651b5e6b718fc11d348d3348586f8a1a44ae89107aefb4a6b23f9124bfa66dca514629b00866839e8dc9b66cbd2c8ead2c6f975099c
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

336 lines
12 KiB
Plaintext

import { errorMonitor } from 'node:events';
import { SpanKind, context, trace } from '@opentelemetry/api';
import { RPCType, setRPCMetadata, isTracingSuppressed, getRPCMetadata } from '@opentelemetry/core';
import { SEMATTRS_NET_HOST_IP, SEMATTRS_NET_HOST_PORT, SEMATTRS_NET_PEER_IP, SEMATTRS_HTTP_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions';
import { debug, parseStringToURLObject, stripUrlQueryAndFragment, httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP, getSpanStatusFromHttpCode, SPAN_STATUS_ERROR, getIsolationScope } from '@sentry/core';
import { DEBUG_BUILD } from '../../debug-build.js';
import { addStartSpanCallback } from './httpServerIntegration.js';
const INTEGRATION_NAME = 'Http.ServerSpans';
// Tree-shakable guard to remove all code related to tracing
const _httpServerSpansIntegration = ((options = {}) => {
const ignoreStaticAssets = options.ignoreStaticAssets ?? true;
const ignoreIncomingRequests = options.ignoreIncomingRequests;
const ignoreStatusCodes = options.ignoreStatusCodes ?? [
[401, 404],
// 300 and 304 are possibly valid status codes we do not want to filter
[301, 303],
[305, 399],
];
const { onSpanCreated } = options;
// eslint-disable-next-line deprecation/deprecation
const { requestHook, responseHook, applyCustomAttributesOnSpan } = options.instrumentation ?? {};
return {
name: INTEGRATION_NAME,
setup(client) {
// If no tracing, we can just skip everything here
if (typeof __SENTRY_TRACING__ !== 'undefined' && !__SENTRY_TRACING__) {
return;
}
client.on('httpServerRequest', (_request, _response, normalizedRequest) => {
// Type-casting this here because we do not want to put the node types into core
const request = _request ;
const response = _response ;
const startSpan = (next) => {
if (
shouldIgnoreSpansForIncomingRequest(request, {
ignoreStaticAssets,
ignoreIncomingRequests,
})
) {
DEBUG_BUILD && debug.log(INTEGRATION_NAME, 'Skipping span creation for incoming request', request.url);
return next();
}
const fullUrl = normalizedRequest.url || request.url || '/';
const urlObj = parseStringToURLObject(fullUrl);
const headers = request.headers;
const userAgent = headers['user-agent'];
const ips = headers['x-forwarded-for'];
const httpVersion = request.httpVersion;
const host = headers.host;
const hostname = host?.replace(/^(.*)(:[0-9]{1,5})/, '$1') || 'localhost';
const tracer = client.tracer;
const scheme = fullUrl.startsWith('https') ? 'https' : 'http';
const method = normalizedRequest.method || request.method?.toUpperCase() || 'GET';
const httpTargetWithoutQueryFragment = urlObj ? urlObj.pathname : stripUrlQueryAndFragment(fullUrl);
const bestEffortTransactionName = `${method} ${httpTargetWithoutQueryFragment}`;
// We use the plain tracer.startSpan here so we can pass the span kind
const span = tracer.startSpan(bestEffortTransactionName, {
kind: SpanKind.SERVER,
attributes: {
// Sentry specific attributes
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.http',
'sentry.http.prefetch': isKnownPrefetchRequest(request) || undefined,
// Old Semantic Conventions attributes - added for compatibility with what `@opentelemetry/instrumentation-http` output before
'http.url': fullUrl,
'http.method': normalizedRequest.method,
'http.target': urlObj ? `${urlObj.pathname}${urlObj.search}` : httpTargetWithoutQueryFragment,
'http.host': host,
'net.host.name': hostname,
'http.client_ip': typeof ips === 'string' ? ips.split(',')[0] : undefined,
'http.user_agent': userAgent,
'http.scheme': scheme,
'http.flavor': httpVersion,
'net.transport': httpVersion?.toUpperCase() === 'QUIC' ? 'ip_udp' : 'ip_tcp',
...getRequestContentLengthAttribute(request),
...httpHeadersToSpanAttributes(
normalizedRequest.headers || {},
client.getOptions().sendDefaultPii ?? false,
),
},
});
// TODO v11: Remove the following three hooks, only onSpanCreated should remain
requestHook?.(span, request);
responseHook?.(span, response);
applyCustomAttributesOnSpan?.(span, request, response);
onSpanCreated?.(span, request, response);
const rpcMetadata = {
type: RPCType.HTTP,
span,
};
return context.with(setRPCMetadata(trace.setSpan(context.active(), span), rpcMetadata), () => {
context.bind(context.active(), request);
context.bind(context.active(), response);
// Ensure we only end the span once
// E.g. error can be emitted before close is emitted
let isEnded = false;
function endSpan(status) {
if (isEnded) {
return;
}
isEnded = true;
const newAttributes = getIncomingRequestAttributesOnResponse(request, response);
span.setAttributes(newAttributes);
span.setStatus(status);
span.end();
// Update the transaction name if the route has changed
const route = newAttributes['http.route'];
if (route) {
getIsolationScope().setTransactionName(`${request.method?.toUpperCase() || 'GET'} ${route}`);
}
}
response.on('close', () => {
endSpan(getSpanStatusFromHttpCode(response.statusCode));
});
response.on(errorMonitor, () => {
const httpStatus = getSpanStatusFromHttpCode(response.statusCode);
// Ensure we def. have an error status here
endSpan(httpStatus.code === SPAN_STATUS_ERROR ? httpStatus : { code: SPAN_STATUS_ERROR });
});
return next();
});
};
addStartSpanCallback(request, startSpan);
});
},
processEvent(event) {
// Drop transaction if it has a status code that should be ignored
if (event.type === 'transaction') {
const statusCode = event.contexts?.trace?.data?.['http.response.status_code'];
if (typeof statusCode === 'number') {
const shouldDrop = shouldFilterStatusCode(statusCode, ignoreStatusCodes);
if (shouldDrop) {
DEBUG_BUILD && debug.log('Dropping transaction due to status code', statusCode);
return null;
}
}
}
return event;
},
afterAllSetup(client) {
if (!DEBUG_BUILD) {
return;
}
if (client.getIntegrationByName('Http')) {
debug.warn(
'It seems that you have manually added `httpServerSpansIntergation` while `httpIntegration` is also present. Make sure to remove `httpIntegration` when adding `httpServerSpansIntegration`.',
);
}
if (!client.getIntegrationByName('Http.Server')) {
debug.error(
'It seems that you have manually added `httpServerSpansIntergation` without adding `httpServerIntegration`. This is a requiement for spans to be created - please add the `httpServerIntegration` integration.',
);
}
},
};
}) ;
/**
* This integration emits spans for incoming requests handled via the node `http` module.
* It requires the `httpServerIntegration` to be present.
*/
const httpServerSpansIntegration = _httpServerSpansIntegration
;
function isKnownPrefetchRequest(req) {
// Currently only handles Next.js prefetch requests but may check other frameworks in the future.
return req.headers['next-router-prefetch'] === '1';
}
/**
* Check if a request is for a common static asset that should be ignored by default.
*
* Only exported for tests.
*/
function isStaticAssetRequest(urlPath) {
const path = stripUrlQueryAndFragment(urlPath);
// Common static file extensions
if (path.match(/\.(ico|png|jpg|jpeg|gif|svg|css|js|woff|woff2|ttf|eot|webp|avif)$/)) {
return true;
}
// Common metadata files
if (path.match(/^\/(robots\.txt|sitemap\.xml|manifest\.json|browserconfig\.xml)$/)) {
return true;
}
return false;
}
function shouldIgnoreSpansForIncomingRequest(
request,
{
ignoreStaticAssets,
ignoreIncomingRequests,
}
,
) {
if (isTracingSuppressed(context.active())) {
return true;
}
// request.url is the only property that holds any information about the url
// it only consists of the URL path and query string (if any)
const urlPath = request.url;
const method = request.method?.toUpperCase();
// We do not capture OPTIONS/HEAD requests as spans
if (method === 'OPTIONS' || method === 'HEAD' || !urlPath) {
return true;
}
// Default static asset filtering
if (ignoreStaticAssets && method === 'GET' && isStaticAssetRequest(urlPath)) {
return true;
}
if (ignoreIncomingRequests?.(urlPath, request)) {
return true;
}
return false;
}
function getRequestContentLengthAttribute(request) {
const length = getContentLength(request.headers);
if (length == null) {
return {};
}
if (isCompressed(request.headers)) {
return {
['http.request_content_length']: length,
};
} else {
return {
['http.request_content_length_uncompressed']: length,
};
}
}
function getContentLength(headers) {
const contentLengthHeader = headers['content-length'];
if (contentLengthHeader === undefined) return null;
const contentLength = parseInt(contentLengthHeader, 10);
if (isNaN(contentLength)) return null;
return contentLength;
}
function isCompressed(headers) {
const encoding = headers['content-encoding'];
return !!encoding && encoding !== 'identity';
}
function getIncomingRequestAttributesOnResponse(request, response) {
// take socket from the request,
// since it may be detached from the response object in keep-alive mode
const { socket } = request;
const { statusCode, statusMessage } = response;
const newAttributes = {
[ATTR_HTTP_RESPONSE_STATUS_CODE]: statusCode,
// eslint-disable-next-line deprecation/deprecation
[SEMATTRS_HTTP_STATUS_CODE]: statusCode,
'http.status_text': statusMessage?.toUpperCase(),
};
const rpcMetadata = getRPCMetadata(context.active());
if (socket) {
const { localAddress, localPort, remoteAddress, remotePort } = socket;
// eslint-disable-next-line deprecation/deprecation
newAttributes[SEMATTRS_NET_HOST_IP] = localAddress;
// eslint-disable-next-line deprecation/deprecation
newAttributes[SEMATTRS_NET_HOST_PORT] = localPort;
// eslint-disable-next-line deprecation/deprecation
newAttributes[SEMATTRS_NET_PEER_IP] = remoteAddress;
newAttributes['net.peer.port'] = remotePort;
}
// eslint-disable-next-line deprecation/deprecation
newAttributes[SEMATTRS_HTTP_STATUS_CODE] = statusCode;
newAttributes['http.status_text'] = (statusMessage || '').toUpperCase();
if (rpcMetadata?.type === RPCType.HTTP && rpcMetadata.route !== undefined) {
const routeName = rpcMetadata.route;
newAttributes[ATTR_HTTP_ROUTE] = routeName;
}
return newAttributes;
}
/**
* If the given status code should be filtered for the given list of status codes/ranges.
*/
function shouldFilterStatusCode(statusCode, dropForStatusCodes) {
return dropForStatusCodes.some(code => {
if (typeof code === 'number') {
return code === statusCode;
}
const [min, max] = code;
return statusCode >= min && statusCode <= max;
});
}
export { httpServerSpansIntegration, isStaticAssetRequest };
//# sourceMappingURL=httpServerSpansIntegration.js.map