autotel
Version:
Write Once, Observe Anywhere
233 lines (230 loc) • 7.57 kB
JavaScript
;
var chunkESLWRGAG_cjs = require('./chunk-ESLWRGAG.cjs');
require('./chunk-YREV3LGG.cjs');
require('./chunk-JEQ2X3Z6.cjs');
var api = require('@opentelemetry/api');
async function tracebQuery(dbSystem, operation, fn, attributes) {
const config = chunkESLWRGAG_cjs.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: api.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: api.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 = chunkESLWRGAG_cjs.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: api.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: api.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;
}
exports.DB_OPERATIONS = DB_OPERATIONS;
exports.DB_SYSTEMS = DB_SYSTEMS;
exports.instrumentDatabase = instrumentDatabase;
exports.tracebQuery = tracebQuery;
//# sourceMappingURL=db.cjs.map
//# sourceMappingURL=db.cjs.map