UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

183 lines (180 loc) • 8.78 kB
import { errorMonitor } from 'node:events'; import { NETWORK_TRANSPORT, NETWORK_PROTOCOL_VERSION, NETWORK_PROTOCOL_NAME, SERVER_ADDRESS, URL_SCHEME, USER_AGENT_ORIGINAL, HTTP_REQUEST_METHOD, URL_FRAGMENT, URL_QUERY, URL_PATH, URL_FULL, SENTRY_HTTP_PREFETCH, SENTRY_ORIGIN, SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_KIND, CLIENT_ADDRESS, SERVER_PORT, NETWORK_LOCAL_ADDRESS, NETWORK_LOCAL_PORT, CLIENT_PORT, NETWORK_PEER_ADDRESS, NETWORK_PEER_PORT, HTTP_RESPONSE_STATUS_CODE } from '@sentry/conventions/attributes'; import { HTTP_SERVER } from '@sentry/conventions/op'; import { DEFAULT_IGNORE_STATUS_CODES, processHttpServerTransactionEvent, getClientIPAddress } from '@sentry/core/server'; import { debug, parseStringToURLObject, stripUrlQueryAndFragment, getUrlQuery, getUrlFragment, startInactiveSpan, HTTP_SPAN_NAME_FALLBACK, httpHeadersToSpanAttributes, getContentLengthFromHeaders, filterCollectedUrlQuery, filterCollectedUrl, hasSpanStreamingEnabled, withActiveSpan, bindScopeToEmitter, getSpanStatusFromHttpCode, SPAN_STATUS_ERROR, isTracingSuppressed } from '@sentry/core'; import { DEBUG_BUILD } from '../../debug-build.js'; import { addStartSpanCallback } from './httpServerIntegration.js'; const INTEGRATION_NAME = "Http.ServerSpans"; const _httpServerSpansIntegration = ((options = {}) => { const ignoreStaticAssets = options.ignoreStaticAssets ?? true; const ignoreIncomingRequests = options.ignoreIncomingRequests; const ignoreStatusCodes = options.ignoreStatusCodes ?? DEFAULT_IGNORE_STATUS_CODES; const { onSpanCreated } = options; return { name: INTEGRATION_NAME, setup(client) { if (typeof __SENTRY_TRACING__ !== "undefined" && !__SENTRY_TRACING__) { return; } client.on("httpServerRequest", (_request, _response, normalizedRequest) => { 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 httpVersion = request.httpVersion; const host = headers.host; const hostname = host?.replace(/^(.*)(:[0-9]{1,5})/, "$1") || "localhost"; const scheme = fullUrl.startsWith("https") ? "https" : "http"; const requestMethod = normalizedRequest.method || request.method?.toUpperCase(); const method = requestMethod || "GET"; const httpTargetWithoutQueryFragment = urlObj ? urlObj.pathname : stripUrlQueryAndFragment(fullUrl); const bestEffortTransactionName = `${method} ${httpTargetWithoutQueryFragment}`; const query = getUrlQuery(urlObj?.search); const fragment = getUrlFragment(urlObj?.hash); const span = startInactiveSpan({ // With span streaming, span names have to be low cardinality, so we can't fall back to the URL path. // Route instrumentations rename the span to `${method} ${route}` once a route is known. name: hasSpanStreamingEnabled(client) ? requestMethod || HTTP_SPAN_NAME_FALLBACK : bestEffortTransactionName, attributes: { // Sentry specific attributes [SENTRY_KIND]: "server", [SENTRY_OP]: HTTP_SERVER, [SENTRY_SEGMENT_NAME_SOURCE]: "url", [SENTRY_ORIGIN]: "auto.http.http_server", [SENTRY_HTTP_PREFETCH]: isKnownPrefetchRequest(request) || void 0, [URL_FULL]: filterCollectedUrl(fullUrl, client), [URL_PATH]: urlObj?.pathname ?? httpTargetWithoutQueryFragment, [URL_QUERY]: filterCollectedUrlQuery(query, client), [URL_FRAGMENT]: fragment, [HTTP_REQUEST_METHOD]: normalizedRequest.method, [USER_AGENT_ORIGINAL]: userAgent, [URL_SCHEME]: scheme, [SERVER_ADDRESS]: hostname, [NETWORK_PROTOCOL_NAME]: "http", [NETWORK_PROTOCOL_VERSION]: httpVersion, [NETWORK_TRANSPORT]: httpVersion?.toUpperCase() === "QUIC" ? "udp" : "tcp", "http.request.body.size": getContentLengthFromHeaders(request.headers), ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, client.getDataCollectionOptions()) } }); onSpanCreated?.(span, request, response); return withActiveSpan(span, () => { bindScopeToEmitter(request); bindScopeToEmitter(response); let isEnded = false; function endSpan(status) { if (isEnded) { return; } isEnded = true; const newAttributes = getIncomingRequestAttributesOnResponse( request, response, client.getDataCollectionOptions().userInfo ); span.setAttributes(newAttributes); span.setStatus(status); span.end(); } response.on("close", () => { endSpan(getSpanStatusFromHttpCode(response.statusCode)); }); response.on(errorMonitor, () => { const httpStatus = getSpanStatusFromHttpCode(response.statusCode); endSpan(httpStatus.code === SPAN_STATUS_ERROR ? httpStatus : { code: SPAN_STATUS_ERROR }); }); return next(); }); }; addStartSpanCallback(request, startSpan); }); }, processEvent(event) { return processHttpServerTransactionEvent(event, ignoreStatusCodes); }, afterAllSetup(client) { if (!DEBUG_BUILD) { return; } if (client.getIntegrationByName("Http")) { debug.warn( "It seems that you have manually added `httpServerSpansIntegration` 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 `httpServerSpansIntegration` without adding `httpServerIntegration`. This is a requiement for spans to be created - please add the `httpServerIntegration` integration." ); } } }; }); const httpServerSpansIntegration = _httpServerSpansIntegration; function isKnownPrefetchRequest(req) { return req.headers["next-router-prefetch"] === "1"; } function isStaticAssetRequest(urlPath) { const path = stripUrlQueryAndFragment(urlPath); if (path.match(/\.(ico|png|jpg|jpeg|gif|svg|css|js|woff|woff2|ttf|eot|webp|avif)$/)) { return true; } if (path.match(/^\/(robots\.txt|sitemap\.xml|manifest\.json|browserconfig\.xml)$/)) { return true; } return false; } function shouldIgnoreSpansForIncomingRequest(request, { ignoreStaticAssets, ignoreIncomingRequests }) { if (isTracingSuppressed()) { return true; } const urlPath = request.url; const method = request.method?.toUpperCase(); if (method === "OPTIONS" || method === "HEAD" || !urlPath) { return true; } if (ignoreStaticAssets && method === "GET" && isStaticAssetRequest(urlPath)) { return true; } if (ignoreIncomingRequests?.(urlPath, request)) { return true; } return false; } function getIncomingRequestAttributesOnResponse(request, response, collectClientAddress) { const { socket } = request; const { statusCode, statusMessage } = response; const newAttributes = { [HTTP_RESPONSE_STATUS_CODE]: statusCode, "http.response.status_text": statusMessage?.toUpperCase() }; const forwardedAddress = getClientIPAddress(request.headers); if (collectClientAddress) { newAttributes[CLIENT_ADDRESS] = forwardedAddress || socket?.remoteAddress; } if (socket) { const { localAddress, localPort, remoteAddress, remotePort } = socket; newAttributes[SERVER_PORT] = localPort; newAttributes[NETWORK_LOCAL_ADDRESS] = localAddress; newAttributes[NETWORK_LOCAL_PORT] = localPort; newAttributes[CLIENT_PORT] = forwardedAddress ? void 0 : remotePort; newAttributes[NETWORK_PEER_ADDRESS] = collectClientAddress ? remoteAddress : void 0; newAttributes[NETWORK_PEER_PORT] = remotePort; } return newAttributes; } export { httpServerSpansIntegration, isStaticAssetRequest }; //# sourceMappingURL=httpServerSpansIntegration.js.map