UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

186 lines (182 loc) • 8.52 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const node_events = require('node:events'); const attributes = require('@sentry/conventions/attributes'); const op = require('@sentry/conventions/op'); const server = require('@sentry/core/server'); const core = require('@sentry/core'); const debugBuild = require('../../debug-build.js'); const httpServerIntegration = require('./httpServerIntegration.js'); const INTEGRATION_NAME = "Http.ServerSpans"; const _httpServerSpansIntegration = ((options = {}) => { const ignoreStaticAssets = options.ignoreStaticAssets ?? true; const ignoreIncomingRequests = options.ignoreIncomingRequests; const ignoreStatusCodes = options.ignoreStatusCodes ?? server.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 })) { debugBuild.DEBUG_BUILD && core.debug.log(INTEGRATION_NAME, "Skipping span creation for incoming request", request.url); return next(); } const fullUrl = normalizedRequest.url || request.url || "/"; const urlObj = core.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 : core.stripUrlQueryAndFragment(fullUrl); const bestEffortTransactionName = `${method} ${httpTargetWithoutQueryFragment}`; const query = core.getUrlQuery(urlObj?.search); const fragment = core.getUrlFragment(urlObj?.hash); const span = core.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: core.hasSpanStreamingEnabled(client) ? requestMethod || core.HTTP_SPAN_NAME_FALLBACK : bestEffortTransactionName, attributes: { // Sentry specific attributes [attributes.SENTRY_KIND]: "server", [attributes.SENTRY_OP]: op.HTTP_SERVER, [attributes.SENTRY_SEGMENT_NAME_SOURCE]: "url", [attributes.SENTRY_ORIGIN]: "auto.http.http_server", [attributes.SENTRY_HTTP_PREFETCH]: isKnownPrefetchRequest(request) || void 0, [attributes.URL_FULL]: core.filterCollectedUrl(fullUrl, client), [attributes.URL_PATH]: urlObj?.pathname ?? httpTargetWithoutQueryFragment, [attributes.URL_QUERY]: core.filterCollectedUrlQuery(query, client), [attributes.URL_FRAGMENT]: fragment, [attributes.HTTP_REQUEST_METHOD]: normalizedRequest.method, [attributes.USER_AGENT_ORIGINAL]: userAgent, [attributes.URL_SCHEME]: scheme, [attributes.SERVER_ADDRESS]: hostname, [attributes.NETWORK_PROTOCOL_NAME]: "http", [attributes.NETWORK_PROTOCOL_VERSION]: httpVersion, [attributes.NETWORK_TRANSPORT]: httpVersion?.toUpperCase() === "QUIC" ? "udp" : "tcp", "http.request.body.size": core.getContentLengthFromHeaders(request.headers), ...core.httpHeadersToSpanAttributes(normalizedRequest.headers || {}, client.getDataCollectionOptions()) } }); onSpanCreated?.(span, request, response); return core.withActiveSpan(span, () => { core.bindScopeToEmitter(request); core.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(core.getSpanStatusFromHttpCode(response.statusCode)); }); response.on(node_events.errorMonitor, () => { const httpStatus = core.getSpanStatusFromHttpCode(response.statusCode); endSpan(httpStatus.code === core.SPAN_STATUS_ERROR ? httpStatus : { code: core.SPAN_STATUS_ERROR }); }); return next(); }); }; httpServerIntegration.addStartSpanCallback(request, startSpan); }); }, processEvent(event) { return server.processHttpServerTransactionEvent(event, ignoreStatusCodes); }, afterAllSetup(client) { if (!debugBuild.DEBUG_BUILD) { return; } if (client.getIntegrationByName("Http")) { core.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")) { core.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 = core.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 (core.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 = { [attributes.HTTP_RESPONSE_STATUS_CODE]: statusCode, "http.response.status_text": statusMessage?.toUpperCase() }; const forwardedAddress = server.getClientIPAddress(request.headers); if (collectClientAddress) { newAttributes[attributes.CLIENT_ADDRESS] = forwardedAddress || socket?.remoteAddress; } if (socket) { const { localAddress, localPort, remoteAddress, remotePort } = socket; newAttributes[attributes.SERVER_PORT] = localPort; newAttributes[attributes.NETWORK_LOCAL_ADDRESS] = localAddress; newAttributes[attributes.NETWORK_LOCAL_PORT] = localPort; newAttributes[attributes.CLIENT_PORT] = forwardedAddress ? void 0 : remotePort; newAttributes[attributes.NETWORK_PEER_ADDRESS] = collectClientAddress ? remoteAddress : void 0; newAttributes[attributes.NETWORK_PEER_PORT] = remotePort; } return newAttributes; } exports.httpServerSpansIntegration = httpServerSpansIntegration; exports.isStaticAssetRequest = isStaticAssetRequest; //# sourceMappingURL=httpServerSpansIntegration.js.map