UNPKG

@sentry/core

Version:
278 lines (275 loc) 12.6 kB
import { HTTP_ON_SERVER_REQUEST } from './constants.js'; import { DEBUG_BUILD } from '../../debug-build.js'; import { debug } from '../../utils/debug-logger.js'; import { getIsolationScope, getClient, withIsolationScope, getCurrentScope } from '../../currentScopes.js'; import { hasSpansEnabled } from '../../utils/hasSpansEnabled.js'; import { httpHeadersToSpanAttributes, getContentLengthFromHeaders, httpRequestToRequestData, headersToDict } from '../../utils/request.js'; import { patchRequestToCaptureBody } from './patch-request-to-capture-body.js'; import { stripUrlQueryAndFragment, parseStringToURLObject, getUrlFragment, getUrlQuery } from '../../utils/url.js'; import { recordRequestSession } from './record-request-session.js'; import { generateSpanId, generateTraceId } from '../../utils/propagationContext.js'; import { startSpanManual, continueTrace } from '../../tracing/trace.js'; import { safeMathRandom } from '../../utils/randomSafeContext.js'; import { NETWORK_TRANSPORT, URL_FRAGMENT, URL_QUERY, URL_PATH, URL_FULL, SENTRY_HTTP_PREFETCH, URL_SCHEME, USER_AGENT_ORIGINAL, NETWORK_PROTOCOL_VERSION, NETWORK_PROTOCOL_NAME, HTTP_REQUEST_METHOD, NETWORK_PEER_PORT, NETWORK_PEER_ADDRESS, CLIENT_PORT, CLIENT_ADDRESS, NETWORK_LOCAL_PORT, NETWORK_LOCAL_ADDRESS, SERVER_PORT, SERVER_ADDRESS, SENTRY_KIND, SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, HTTP_RESPONSE_STATUS_CODE } from '@sentry/conventions/attributes'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes.js'; import { getSpanStatusFromHttpCode, SPAN_STATUS_ERROR } from '../../tracing/spanstatus.js'; import { hasSpanStreamingEnabled } from '../../tracing/spans/hasSpanStreamingEnabled.js'; import { HTTP_SPAN_NAME_FALLBACK } from '../../tracing/spans/spanNames.js'; import { HTTP_SERVER } from '@sentry/conventions/op'; import { filterCollectedUrlQuery, filterCollectedUrl } from '../../utils/data-collection/filterCollectedUrl.js'; const INTEGRATION_NAME = "Http.Server"; const SPANS_INTEGRATION_NAME = "Http.SentryServerSpans"; const lastSentryEmitMap = /* @__PURE__ */ new WeakMap(); const kRequestMark = /* @__PURE__ */ Symbol.for("sentry_http_server_instrumented"); function markRequest(request) { return !request[kRequestMark] && (request[kRequestMark] = true); } function instrumentServer(options, server) { const currentEmit = server.emit; const instrumentedEmit = lastSentryEmitMap.get(server); if (currentEmit === instrumentedEmit) { return; } const newEmit = new Proxy(currentEmit, { apply(target, thisArg, args) { const [event, ...data] = args; if (event !== "request") { return target.apply(thisArg, args); } const client = getClient(); const [request, response] = data; if (!client || !markRequest(request)) { return target.apply(thisArg, args); } DEBUG_BUILD && debug.log(INTEGRATION_NAME, "Handling incoming request"); const isolationScope = getIsolationScope().clone(); isolationScope.setClient(client); const ipAddress = request.socket?.remoteAddress; const url = request.url || "/"; const normalizedRequest = httpRequestToRequestData(request); const { maxRequestBodySize: configuredBodySize, ignoreRequestBody, sessions = true, sessionFlushingDelayMS = 6e4 } = options; const effectiveBodySize = configuredBodySize ?? (client.getDataCollectionOptions().httpBodies.includes("incomingRequest") ? "medium" : "none"); if (effectiveBodySize !== "none" && !ignoreRequestBody?.(url, request)) { patchRequestToCaptureBody(request, isolationScope, effectiveBodySize, INTEGRATION_NAME); } isolationScope.setSDKProcessingMetadata({ normalizedRequest, ipAddress }); const httpMethod = (request.method || "GET").toUpperCase(); const httpTargetWithoutQueryFragment = stripUrlQueryAndFragment(url); const bestEffortTransactionName = `${httpMethod} ${httpTargetWithoutQueryFragment}`; isolationScope.setTransactionName(bestEffortTransactionName); if (sessions) { recordRequestSession(client, { requestIsolationScope: isolationScope, response, sessionFlushingDelayMS: sessionFlushingDelayMS ?? 6e4 }); } return withIsolationScope(isolationScope, () => { const sentryTrace = normalizedRequest.headers?.["sentry-trace"]; const baggage = normalizedRequest.headers?.["baggage"]; const sentryTraceValue = Array.isArray(sentryTrace) ? sentryTrace[0] : sentryTrace; return continueTrace( { sentryTrace: sentryTraceValue, baggage: Array.isArray(baggage) ? baggage[0] : baggage }, () => { const propagationContext = getCurrentScope().getPropagationContext(); propagationContext.propagationSpanId = generateSpanId(); if (!sentryTraceValue) { propagationContext.traceId = generateTraceId(); propagationContext.sampleRand = safeMathRandom(); } response.once("close", () => { isolationScope.setContext("response", { status_code: response.statusCode }); }); const wrap = options.wrapServerEmitRequest; let emitResult = false; if (wrap) { wrap(request, response, normalizedRequest, () => { emitResult = target.apply(thisArg, args); }); } else { emitResult = target.apply(thisArg, args); } return emitResult; } ); }); } }); lastSentryEmitMap.set(server, newEmit); server.emit = newEmit; } function getHttpServerSubscriptions(options) { const userWrap = options.wrapServerEmitRequest; const spanWrap = buildServerSpanWrap(options); const effectiveOptions = { ...options, wrapServerEmitRequest(request, response, normalizedRequest, next) { const clientOptions = getClient()?.getOptions(); const createSpans = options.spans ?? (clientOptions ? hasSpansEnabled(clientOptions) : false); if (createSpans) { spanWrap(request, response, normalizedRequest, next); } else if (userWrap) { userWrap(request, response, normalizedRequest, next); } else { next(); } } }; const onHttpServerRequest = (data) => { const { server } = data; instrumentServer(effectiveOptions, server); }; return { [HTTP_ON_SERVER_REQUEST]: onHttpServerRequest }; } function buildServerSpanWrap(options) { const { wrapServerEmitRequest: userWrap, ignoreIncomingRequests, ignoreStaticAssets = true, onSpanCreated, errorMonitor = "error", onSpanEnd } = options; return (request, response, normalizedRequest, next) => { if (typeof __SENTRY_TRACING__ !== "undefined" && !__SENTRY_TRACING__) { return next(); } return userWrap ? userWrap(request, response, normalizedRequest, createSpan) : createSpan(); function createSpan() { const isolationScope = getIsolationScope(); const client = isolationScope.getClient(); if (!client) { return next(); } const dataCollectionOptions = client.getDataCollectionOptions(); if (shouldIgnoreSpansForIncomingRequest(request, { ignoreStaticAssets, ignoreIncomingRequests })) { DEBUG_BUILD && debug.log(SPANS_INTEGRATION_NAME, "Skipping span creation for incoming request", request.url); return next(); } const fullUrl = normalizedRequest.url || request.url || "/"; const urlObj = parseStringToURLObject(fullUrl); const httpTargetWithoutQueryFragment = urlObj ? urlObj.pathname : stripUrlQueryAndFragment(fullUrl); const method = (request.method || "GET").toUpperCase(); const name = hasSpanStreamingEnabled(client) ? request.method?.toUpperCase() || HTTP_SPAN_NAME_FALLBACK : `${method} ${httpTargetWithoutQueryFragment}`; 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 scheme = fullUrl.startsWith("https") ? "https" : "http"; const { socket } = request; const { localAddress, localPort, remoteAddress, remotePort } = socket ?? {}; const collectClientAddress = client.getDataCollectionOptions().userInfo; const clientAddress = getForwardedClientAddress(ips) ?? remoteAddress; return startSpanManual( { name, attributes: { // Sentry-specific attributes [SENTRY_OP]: HTTP_SERVER, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.http.server", [SENTRY_SEGMENT_NAME_SOURCE]: "url", [SENTRY_KIND]: "server", // Network attributes [SERVER_ADDRESS]: hostname, [SERVER_PORT]: localPort, [NETWORK_LOCAL_ADDRESS]: localAddress, [NETWORK_LOCAL_PORT]: localPort, [CLIENT_ADDRESS]: collectClientAddress ? clientAddress : void 0, [CLIENT_PORT]: remotePort, [NETWORK_PEER_ADDRESS]: collectClientAddress ? remoteAddress : void 0, [NETWORK_PEER_PORT]: remotePort, [SENTRY_HTTP_PREFETCH]: isKnownPrefetchRequest(request) || void 0, [URL_FULL]: filterCollectedUrl(fullUrl, client), [URL_PATH]: urlObj?.pathname ?? httpTargetWithoutQueryFragment, [URL_QUERY]: filterCollectedUrlQuery(getUrlQuery(urlObj?.search), client), [URL_FRAGMENT]: getUrlFragment(urlObj?.hash), [HTTP_REQUEST_METHOD]: method, [NETWORK_PROTOCOL_NAME]: "http", [NETWORK_PROTOCOL_VERSION]: httpVersion, [USER_AGENT_ORIGINAL]: userAgent, [URL_SCHEME]: scheme, [NETWORK_TRANSPORT]: httpVersion?.toUpperCase() === "QUIC" ? "udp" : "tcp", "http.request.body.size": getContentLengthFromHeaders(request.headers), ...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, dataCollectionOptions) } }, (span) => { onSpanCreated?.(span, request, response); let isEnded = false; function endSpan(status) { if (isEnded) { return; } isEnded = true; span.setAttributes({ "http.response.status_text": response.statusMessage?.toUpperCase(), [HTTP_RESPONSE_STATUS_CODE]: response.statusCode, ...httpHeadersToSpanAttributes(headersToDict(response.headers), dataCollectionOptions, "response") }); span.setStatus(status); onSpanEnd?.(span, request, response); span.end(); } response.once("close", () => { endSpan(getSpanStatusFromHttpCode(response.statusCode)); }); response.once(errorMonitor, () => { const httpStatus = getSpanStatusFromHttpCode(response.statusCode); endSpan(httpStatus.code === SPAN_STATUS_ERROR ? httpStatus : { code: SPAN_STATUS_ERROR }); }); next(); } ); } }; } function getForwardedClientAddress(forwardedFor) { return typeof forwardedFor === "string" ? forwardedFor.split(",")[0]?.trim() || void 0 : void 0; } function shouldIgnoreSpansForIncomingRequest(request, { ignoreStaticAssets, ignoreIncomingRequests }) { 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 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 isKnownPrefetchRequest(req) { return req.headers["next-router-prefetch"] === "1"; } export { getHttpServerSubscriptions, instrumentServer, isStaticAssetRequest }; //# sourceMappingURL=server-subscription.js.map