@sentry/remix
Version:
Official Sentry SDK for Remix
116 lines (113 loc) • 3.86 kB
JavaScript
import { HTTP_ROUTE, HTTP_RESPONSE_STATUS_CODE } from '@sentry/conventions/attributes';
import { getIsolationScope, winterCGRequestToRequestData, getActiveSpan, getRootSpan, updateSpanName, INTERNAL_setSegmentNameSourceIfSegment, getSpanStatusFromHttpCode, getTraceData } from '@sentry/core';
import { formatRouteTiming } from '../routeTiming.js';
import { captureRequestError } from './errorFilter.js';
import { resolveRoutePattern } from './route.js';
function sentryRemixMiddleware(matcher) {
return async function sentryMiddleware(context, next) {
const isolationScope = getIsolationScope();
isolationScope.setSDKProcessingMetadata({ normalizedRequest: winterCGRequestToRequestData(context.request) });
const route = resolveRoutePattern(matcher, context);
if (route) {
applyRoute(isolationScope, route, context.method);
}
let response;
try {
response = await next();
} catch (error) {
captureRequestError(error, context.request, "auto.http.remix_v3.middleware");
throw error;
}
setResponseStatus(response);
addServerTimingHeaders(response, context.request, route);
return response;
};
}
function applyRoute(isolationScope, route, method) {
const name = `${method} ${route}`;
isolationScope.setTransactionName(name);
const activeSpan = getActiveSpan();
if (!activeSpan) {
return;
}
const rootSpan = getRootSpan(activeSpan);
updateSpanName(rootSpan, name);
INTERNAL_setSegmentNameSourceIfSegment(rootSpan, "route");
rootSpan.setAttribute(HTTP_ROUTE, route);
}
function addServerTimingHeaders(response, request, route) {
if (!request.headers.get("accept")?.includes("text/html")) {
return;
}
const entries = [];
if (!isSharedCacheable(response)) {
const traceData = getTraceData();
if (traceData["sentry-trace"]) {
entries.push(`sentry-trace;desc="${traceData["sentry-trace"]}"`);
}
if (traceData.baggage) {
entries.push(`baggage;desc="${traceData.baggage}"`);
}
}
if (route) {
entries.push(formatRouteTiming(route));
}
for (const entry of entries) {
try {
response.headers.append("Server-Timing", entry);
} catch {
return;
}
}
}
const CDN_CACHE_CONTROL_HEADERS = [
"cdn-cache-control",
"cloudflare-cdn-cache-control",
"vercel-cdn-cache-control",
"surrogate-control"
];
function isSharedCacheable(response) {
for (const name of CDN_CACHE_CONTROL_HEADERS) {
const value = response.headers.get(name);
if (value !== null && sharedCachingVerdict(value.toLowerCase()) === true) {
return true;
}
}
const verdict = sharedCachingVerdict(response.headers.get("cache-control")?.toLowerCase() ?? "");
if (verdict !== void 0) {
return verdict;
}
const expires = response.headers.get("expires");
return expires !== null && Date.parse(expires) > Date.now();
}
function sharedCachingVerdict(directives) {
if (/\b(?:no-store|private)\b/.test(directives)) {
return false;
}
if (/\b(?:stale-while-revalidate|stale-if-error)\s*=\s*[1-9]/.test(directives)) {
return true;
}
const sharedMaxAge = directives.match(/\bs-maxage\s*=\s*(\d+)/)?.[1];
if (sharedMaxAge !== void 0) {
return Number(sharedMaxAge) > 0;
}
if (/\bpublic\b/.test(directives)) {
return true;
}
const maxAge = directives.match(/\bmax-age\s*=\s*(\d+)/)?.[1];
if (maxAge !== void 0) {
return Number(maxAge) > 0;
}
return void 0;
}
function setResponseStatus(response) {
const activeSpan = getActiveSpan();
if (!activeSpan || typeof response?.status !== "number") {
return;
}
const rootSpan = getRootSpan(activeSpan);
rootSpan.setAttribute(HTTP_RESPONSE_STATUS_CODE, response.status);
rootSpan.setStatus(getSpanStatusFromHttpCode(response.status));
}
export { sentryRemixMiddleware };
//# sourceMappingURL=middleware.js.map