UNPKG

autotel

Version:
228 lines (226 loc) 7.42 kB
import { getConfig } from './chunk-J5QENANM.js'; import './chunk-HA2WBOGQ.js'; import './chunk-DGUM43GV.js'; import { SpanStatusCode } from '@opentelemetry/api'; async function tracebQuery(dbSystem, operation, fn, attributes) { const config = getConfig(); const tracer = config.tracer; const spanName = `${dbSystem}.${operation}`; return tracer.startActiveSpan(spanName, async (span) => { const startTime = performance.now(); try { span.setAttributes({ "db.system": dbSystem, "db.operation": operation, ...attributes }); const result = await fn(); const duration = performance.now() - startTime; span.setStatus({ code: SpanStatusCode.OK }); span.setAttribute("db.duration_ms", duration); if (Array.isArray(result)) { span.setAttribute("db.result_count", result.length); } return result; } catch (error) { const duration = performance.now() - startTime; span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : "Unknown error" }); span.setAttributes({ "db.duration_ms": duration, "error.type": error instanceof Error ? error.constructor.name : "Unknown", "error.message": error instanceof Error ? error.message : "Unknown error" }); throw error; } finally { span.end(); } }); } function inferDbOperation(methodName) { const lower = methodName.toLowerCase(); if (lower.includes("find") || lower.includes("get") || lower.includes("list")) return "SELECT"; if (lower.includes("create") || lower.includes("insert")) return "INSERT"; if (lower.includes("update") || lower.includes("modify")) return "UPDATE"; if (lower.includes("delete") || lower.includes("remove")) return "DELETE"; if (lower.includes("count")) return "COUNT"; return "QUERY"; } function inferTableName(methodName) { const patterns = [ /find([A-Z][a-zA-Z]+)/, /get([A-Z][a-zA-Z]+)/, /list([A-Z][a-zA-Z]+)/, /create([A-Z][a-zA-Z]+)/, /update([A-Z][a-zA-Z]+)/, /delete([A-Z][a-zA-Z]+)/, /remove([A-Z][a-zA-Z]+)/ ]; for (const pattern of patterns) { const match = methodName.match(pattern); if (match && match[1]) { return match[1].toLowerCase(); } } return void 0; } function sanitizeSqlQuery(query) { return query.replaceAll(/'[^']*'/g, "'?'").replaceAll(/"[^"]*"/g, '"?"').replaceAll(/\b\d+\b/g, "?").trim(); } var DB_OPERATIONS = { SELECT: "SELECT", INSERT: "INSERT", UPDATE: "UPDATE", DELETE: "DELETE", COUNT: "COUNT", AGGREGATE: "AGGREGATE" }; var DB_SYSTEMS = { POSTGRESQL: "postgresql", MYSQL: "mysql", MONGODB: "mongodb", REDIS: "redis", SQLITE: "sqlite", MSSQL: "mssql" }; var INSTRUMENTED_SYMBOL = /* @__PURE__ */ Symbol.for("autotel.db.instrumented"); function instrumentDatabase(client, options) { if (client[INSTRUMENTED_SYMBOL]) { return client; } const { dbSystem, dbName, methods, skipMethods = [], sanitizeQuery = true, slowQueryThresholdMs = 1e3 } = options; const config = getConfig(); const tracer = config.tracer; const methodsToInstrument = methods || extractDatabaseMethods(client); const skipSet = new Set(skipMethods); for (const methodName of methodsToInstrument) { if (skipSet.has(methodName)) continue; if (methodName.startsWith("_")) continue; const method = client[methodName]; if (typeof method !== "function") continue; const originalMethod = method; client[methodName] = async function(...args) { const operation = inferDbOperation(methodName); const table = inferTableName(methodName); const spanName = table ? `${dbSystem}.${operation} ${table}` : `${dbSystem}.${operation}`; return tracer.startActiveSpan(spanName, async (span) => { const startTime = performance.now(); try { span.setAttributes({ "db.system": dbSystem, "db.operation": operation }); if (dbName) { span.setAttribute("db.name", dbName); } if (table) { span.setAttribute("db.sql.table", table); } const query = extractQueryFromArgs(args); if (query) { span.setAttribute( "db.statement", sanitizeQuery ? sanitizeSqlQuery(query) : query ); } const result = await originalMethod.apply(this, args); const duration = performance.now() - startTime; span.setStatus({ code: SpanStatusCode.OK }); span.setAttributes({ "db.duration_ms": duration }); if (duration > slowQueryThresholdMs) { span.setAttribute("db.slow_query", true); span.setAttribute( "db.slow_query_threshold_ms", slowQueryThresholdMs ); } if (Array.isArray(result)) { span.setAttribute("db.result_count", result.length); } return result; } catch (error) { const duration = performance.now() - startTime; span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : "Unknown error" }); span.setAttributes({ "db.duration_ms": duration, "error.type": error instanceof Error ? error.constructor.name : "Unknown", "error.message": error instanceof Error ? error.message : "Unknown error" }); span.recordException( error instanceof Error ? error : new Error(String(error)) ); throw error; } finally { span.end(); } }); }; Object.defineProperty(client[methodName], "name", { value: methodName, configurable: true }); } client[INSTRUMENTED_SYMBOL] = true; return client; } function extractDatabaseMethods(client) { const methods = []; const proto = Object.getPrototypeOf(client); for (const key of Object.getOwnPropertyNames(client)) { if (typeof client[key] === "function" && !key.startsWith("_")) { methods.push(key); } } if (proto) { for (const key of Object.getOwnPropertyNames(proto)) { if (typeof proto[key] === "function" && !key.startsWith("_") && key !== "constructor") { methods.push(key); } } } return [...new Set(methods)]; } function extractQueryFromArgs(args) { if (args.length === 0) return void 0; const firstArg = args[0]; if (typeof firstArg === "string") { return firstArg; } if (firstArg && typeof firstArg === "object") { if ("sql" in firstArg && typeof firstArg.sql === "string") { return firstArg.sql; } if ("text" in firstArg && typeof firstArg.text === "string") { return firstArg.text; } if ("toQuery" in firstArg && typeof firstArg.toQuery === "function") { try { const queryResult = firstArg.toQuery(); if (typeof queryResult === "string") return queryResult; if (queryResult && typeof queryResult === "object" && "sql" in queryResult) { return queryResult.sql; } } catch { } } } return void 0; } export { DB_OPERATIONS, DB_SYSTEMS, instrumentDatabase, tracebQuery }; //# sourceMappingURL=db.js.map //# sourceMappingURL=db.js.map