import { captureException, debug, flushIfServerless, getClient, httpHeadersToSpanAttributes, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SPAN_STATUS_ERROR, SPAN_STATUS_OK, startSpan } from "@sentry/core"; export function wrapMiddlewareHandlerWithSentry(handler, fileName) { if (!isEventHandlerObject(handler)) { return wrapEventHandler(handler, fileName); } const handlerObj = handler; const result = { ...handlerObj, ...handlerObj.handler && { handler: wrapEventHandler(handlerObj.handler, fileName) } }; if (result.onRequest) { result.onRequest = normalizeHandlers( result.onRequest, (h, index) => wrapEventHandler(h, fileName, "onRequest", index) ); } if (result.onBeforeResponse) { result.onBeforeResponse = normalizeHandlers( result.onBeforeResponse, (h, index) => wrapResponseHandler(h, fileName, index) ); } if (result.middleware?.length) { result.middleware = result.middleware.map((h, index) => wrapEventHandler(h, fileName, "middleware", index)); } return result; } function wrapEventHandler(handler, middlewareName, hookName, index) { return async (event) => { debug.log(`Sentry middleware: ${middlewareName}${hookName ? `.${hookName}` : ""} handling ${event.path}`); const attributes = getSpanAttributes(event, middlewareName, hookName, index); return withSpan(() => handler(event), attributes, middlewareName, hookName); }; } function wrapResponseHandler(handler, middlewareName, index) { return async (event, response) => { debug.log(`Sentry middleware: ${middlewareName}.onBeforeResponse handling ${event.path}`); const attributes = getSpanAttributes(event, middlewareName, "onBeforeResponse", index); return withSpan(() => handler(event, response), attributes, middlewareName, "onBeforeResponse"); }; } function withSpan(handler, attributes, middlewareName, hookName) { const spanName = hookName && hookName !== "handler" ? `${middlewareName}.${hookName}` : middlewareName; return startSpan( { name: spanName, attributes }, async (span) => { try { const result = await handler(); span.setStatus({ code: SPAN_STATUS_OK }); return result; } catch (error) { span.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" }); captureException(error, { mechanism: { handled: false, type: attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] } }); throw error; } finally { await flushIfServerless(); } } ); } function normalizeHandlers(handlers, normalizer) { return Array.isArray(handlers) ? handlers.map((handler, index) => normalizer(handler, index)) : normalizer(handlers); } function getSpanAttributes(event, middlewareName, hookName, index) { const attributes = { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "middleware.nuxt", [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "custom", [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.middleware.nuxt", "nuxt.middleware.name": middlewareName, "nuxt.middleware.hook.name": hookName ?? "handler" }; if (typeof index === "number") { attributes["nuxt.middleware.hook.index"] = index; } const eventH3v2 = event; const method = event.method ?? eventH3v2?.req?.method; const path = event.path ?? eventH3v2?.url?.pathname; if (method) { attributes["http.request.method"] = method; } if (path) { attributes["http.route"] = path; } let headers = event.node?.req?.headers || {}; if (!Object.keys(headers).length && eventH3v2?.req?.headers instanceof Headers) { headers = Object.fromEntries(eventH3v2?.req.headers.entries()); } const headerAttributes = httpHeadersToSpanAttributes(headers, getClient()?.getDataCollectionOptions() ?? false); Object.assign(attributes, headerAttributes); return attributes; } function isEventHandlerObject(handler) { return typeof handler !== "function"; }