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