@sentry/remix
Version:
Official Sentry SDK for Remix
195 lines (191 loc) • 7.37 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const diagnosticsChannel = require('node:diagnostics_channel');
const core = require('@sentry/core');
const serverUtils = require('@sentry/server-utils');
const attributes = require('@sentry/conventions/attributes');
const op = require('@sentry/conventions/op');
const config = require('@sentry/server-utils/orchestrion/config');
const formData = require('../../utils/formData.js');
const ORIGIN = "auto.http.remix";
const NOOP = () => {
};
const MATCH_ROUTE_ID = "match.route.id";
const MATCH_PARAMS = "match.params";
function getRequestAttributes(request) {
if (!core.isObjectLike(request)) {
return {};
}
const { method, url } = request;
const attributes$1 = {};
if (typeof method === "string") {
attributes$1[attributes.HTTP_REQUEST_METHOD] = method;
}
if (typeof url === "string") {
const urlObject = core.parseStringToURLObject(url);
attributes$1[attributes.URL_FULL] = core.filterCollectedUrl(
urlObject && !core.isURLObjectRelative(urlObject) ? urlObject.href : void 0
);
attributes$1[attributes.URL_PATH] = urlObject?.pathname;
}
return attributes$1;
}
function getMatchAttributes(params) {
const attributes$1 = {};
if (params.routeId) {
attributes$1[MATCH_ROUTE_ID] = params.routeId;
attributes$1[attributes.ROUTER_NAVIGATION_ROUTE_ID] = params.routeId;
}
for (const [name, value] of Object.entries(params.params ?? {})) {
attributes$1[`${MATCH_PARAMS}.${name}`] = value || "(undefined)";
}
return attributes$1;
}
function setResponseStatus(span, result) {
if (!core.isObjectLike(result)) {
return;
}
const status = result.status;
if (typeof status === "number") {
span.setAttribute(attributes.HTTP_RESPONSE_STATUS_CODE, status);
const spanStatus = core.getSpanStatusFromHttpCode(status);
span.setStatus(spanStatus);
}
}
function enrichActiveSpanWithRoute(result) {
const span = core.getActiveSpan();
if (!span) {
return;
}
const matches = Array.isArray(result) ? result : [];
const route = matches[matches.length - 1]?.route;
if (route?.path) {
span.setAttribute(attributes.HTTP_ROUTE, route.path);
const method = core.spanToJSON(span).attributes[attributes.HTTP_REQUEST_METHOD];
span.updateName(typeof method === "string" ? `${method} ${route.path}` : route.path);
span.setAttribute(attributes.SENTRY_SEGMENT_NAME_SOURCE, "route");
}
if (route?.id) {
span.setAttribute(MATCH_ROUTE_ID, route.id);
}
}
function subscribeRequestHandler() {
serverUtils.bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel(config.remixChannels.REMIX_REQUEST_HANDLER),
(data) => {
const requestAttributes = getRequestAttributes(data.arguments[0]);
const method = requestAttributes[attributes.HTTP_REQUEST_METHOD];
const path = requestAttributes[attributes.URL_PATH];
const hasUrlName = typeof method === "string" && typeof path === "string";
const client = core.getClient();
const isStreamed = !!client && core.hasSpanStreamingEnabled(client);
return core.startInactiveSpan({
name: isStreamed ? typeof method === "string" ? method : core.HTTP_SPAN_NAME_FALLBACK : hasUrlName ? `${method} ${path}` : "remix.request",
attributes: {
[attributes.SENTRY_KIND]: "server",
[attributes.SENTRY_ORIGIN]: ORIGIN,
[attributes.SENTRY_OP]: op.HTTP_SERVER,
...hasUrlName && { [attributes.SENTRY_SEGMENT_NAME_SOURCE]: "url" },
[attributes.CODE_FUNCTION_NAME]: "requestHandler",
...requestAttributes
}
});
},
{
beforeSpanEnd: (span, data) => setResponseStatus(span, data.result)
}
);
}
function subscribeMatchServerRoutes() {
diagnosticsChannel.tracingChannel(config.remixChannels.REMIX_MATCH_SERVER_ROUTES).subscribe({
start: NOOP,
end(data) {
enrichActiveSpanWithRoute(data.result);
},
asyncStart: NOOP,
asyncEnd: NOOP,
error: NOOP
});
}
function subscribeCallRouteLoader() {
serverUtils.bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel(config.remixChannels.REMIX_CALL_ROUTE_LOADER),
(data) => {
const params = data.arguments[0] ?? {};
const client = core.getClient();
const description = `LOADER ${params.routeId}`;
return core.startInactiveSpan({
// With span streaming, a `function` span is named after the function it wraps. The route id
// stays on `match.route.id`.
name: client && core.hasSpanStreamingEnabled(client) ? "loader" : description,
attributes: {
[attributes.SENTRY_ORIGIN]: ORIGIN,
[attributes.SENTRY_OP]: op.FUNCTION,
[attributes.SENTRY_DESCRIPTION]: description,
[attributes.CODE_FUNCTION_NAME]: "loader",
...getRequestAttributes(params.request),
...getMatchAttributes(params)
}
});
},
{
requiresParentSpan: true,
beforeSpanEnd: (span, data) => setResponseStatus(span, data.result)
}
);
}
function subscribeCallRouteAction(formDataCapture) {
serverUtils.bindTracingChannelToSpan(
diagnosticsChannel.tracingChannel(config.remixChannels.REMIX_CALL_ROUTE_ACTION),
(data) => {
const params = data.arguments[0] ?? {};
if (formDataCapture && params.request) {
const formData = params.request.clone().formData();
formData.catch(() => void 0);
data._sentryFormData = formData;
}
const client = core.getClient();
const description = `ACTION ${params.routeId}`;
return core.startInactiveSpan({
// With span streaming, a `function` span is named after the function it wraps. The route id
// stays on `match.route.id`.
name: client && core.hasSpanStreamingEnabled(client) ? "action" : description,
attributes: {
[attributes.SENTRY_ORIGIN]: ORIGIN,
[attributes.SENTRY_OP]: op.FUNCTION,
[attributes.SENTRY_DESCRIPTION]: description,
[attributes.CODE_FUNCTION_NAME]: "action",
...getRequestAttributes(params.request),
...getMatchAttributes(params)
}
});
},
{
requiresParentSpan: true,
beforeSpanEnd: (span, data) => setResponseStatus(span, data.result),
// Hold the span end until the (already in-flight) form-data read resolves, then apply the
// attributes and end (which sets the response status via `beforeSpanEnd`). On error, or when
// capture isn't configured, let the helper end the span normally.
deferSpanEnd: ({ span, data, end }) => {
const formData$1 = data._sentryFormData;
if (!formDataCapture || !formData$1 || "error" in data) {
return false;
}
formData$1.then((resolved) => formData.applyFormDataAttributes(span, resolved, formDataCapture)).catch(() => void 0).finally(() => end());
return true;
}
}
);
}
function instrumentRemix(formDataCapture) {
if (!diagnosticsChannel.tracingChannel) {
return;
}
core.waitForTracingChannelBinding(() => {
subscribeRequestHandler();
subscribeMatchServerRoutes();
subscribeCallRouteLoader();
subscribeCallRouteAction(formDataCapture);
});
}
exports.instrumentRemix = instrumentRemix;
//# sourceMappingURL=tracing-channel.js.map