import { addBreadcrumb, captureException, debug, flushIfServerless, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR, startSpan } from "@sentry/core"; import { getDatabaseSpanData } from "./database-span-data.js"; const patchedStatement = /* @__PURE__ */ new WeakSet(); const SENTRY_ORIGIN = "auto.db.nuxt"; export function createDatabasePlugin(useDatabase, databaseConfig) { try { const databaseInstances = Object.keys(databaseConfig); debug.log("[Nitro Database Plugin]: Instrumenting databases..."); for (const instance of databaseInstances) { debug.log("[Nitro Database Plugin]: Instrumenting database instance:", instance); const db = useDatabase(instance); instrumentDatabase(db, databaseConfig[instance]); } debug.log("[Nitro Database Plugin]: Databases instrumented."); } catch (error) { if (error instanceof Error && /Cannot access 'instances'/.test(error.message)) { debug.log("[Nitro Database Plugin]: Database instrumentation skipped during build time."); return; } debug.error("[Nitro Database Plugin]: Failed to instrument database:", error); } } function instrumentDatabase(db, config) { if (db.__sentry_instrumented__) { debug.log("[Nitro Database Plugin]: Database already instrumented. Skipping..."); return; } const metadata = { "db.system.name": config?.connector ?? db.dialect, ...getDatabaseSpanData(config) }; db.prepare = new Proxy(db.prepare, { apply(target, thisArg, args) { const [query] = args; return instrumentPreparedStatement(target.apply(thisArg, args), query, metadata); } }); db.sql = new Proxy(db.sql, { apply(target, thisArg, args) { const query = args[0]?.[0] ?? ""; const opts = createStartSpanOptions(query, metadata); return startSpan( opts, handleSpanStart(() => target.apply(thisArg, args)) ); } }); db.exec = new Proxy(db.exec, { apply(target, thisArg, args) { return startSpan( createStartSpanOptions(args[0], metadata), handleSpanStart(() => target.apply(thisArg, args), { query: args[0] }) ); } }); db.__sentry_instrumented__ = true; } function instrumentPreparedStatement(statement, query, data) { statement.bind = new Proxy(statement.bind, { apply(target, thisArg, args) { return instrumentPreparedStatementQueries(target.apply(thisArg, args), query, data); } }); return instrumentPreparedStatementQueries(statement, query, data); } function instrumentPreparedStatementQueries(statement, query, data) { if (patchedStatement.has(statement)) { return statement; } statement.get = new Proxy(statement.get, { apply(target, thisArg, args) { return startSpan( createStartSpanOptions(query, data), handleSpanStart(() => target.apply(thisArg, args), { query }) ); } }); statement.run = new Proxy(statement.run, { apply(target, thisArg, args) { return startSpan( createStartSpanOptions(query, data), handleSpanStart(() => target.apply(thisArg, args), { query }) ); } }); statement.all = new Proxy(statement.all, { apply(target, thisArg, args) { return startSpan( createStartSpanOptions(query, data), handleSpanStart(() => target.apply(thisArg, args), { query }) ); } }); patchedStatement.add(statement); return statement; } function handleSpanStart(fn, breadcrumbOpts) { return async (span) => { try { const result = await fn(); if (breadcrumbOpts) { createBreadcrumb(breadcrumbOpts.query); } return result; } catch (error) { span.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" }); captureException(error, { mechanism: { handled: false, type: SENTRY_ORIGIN } }); throw error; } finally { await flushIfServerless(); } }; } function createBreadcrumb(query) { addBreadcrumb({ category: "query", message: query, data: { "db.query.text": query } }); } function createStartSpanOptions(query, data) { return { name: query, attributes: { "db.query.text": query, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: SENTRY_ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "db.query", ...data } }; }