@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
183 lines (180 loc) • 8.78 kB
JavaScript
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