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