UNPKG

@sentry/core

Version:
231 lines (228 loc) 9.24 kB
import { DEBUG_BUILD } from '../debug-build.js'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes.js'; import { debug } from '../utils/debug-logger.js'; import { getActiveSpan } from '../utils/spanUtils.js'; import { SPAN_STATUS_ERROR } from '../tracing/spanstatus.js'; import { startSpanManual } from '../tracing/trace.js'; const SQL_OPERATION_REGEX = /^(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)/i; const CONNECTION_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol("sentryPostgresConnectionContext"); const INSTRUMENTED_MARKER = /* @__PURE__ */ Symbol.for("sentry.instrumented.postgresjs"); const QUERY_FROM_INSTRUMENTED_SQL = /* @__PURE__ */ Symbol.for("sentry.query.from.instrumented.sql"); function instrumentPostgresJsSql(sql, options) { if (!sql || typeof sql !== "function") { DEBUG_BUILD && debug.warn("instrumentPostgresJsSql: provided value is not a valid postgres.js sql instance"); return sql; } return _instrumentSqlInstance(sql, { requireParentSpan: true, ...options }); } function _instrumentSqlInstance(sql, options, parentConnectionContext) { if (sql[INSTRUMENTED_MARKER]) { return sql; } const proxiedSql = new Proxy(sql, { apply(target, thisArg, argumentsList) { const query = Reflect.apply(target, thisArg, argumentsList); if (query && typeof query === "object" && "handle" in query) { _wrapSingleQueryHandle(query, proxiedSql, options); } return query; }, get(target, prop) { const original = target[prop]; if (typeof prop !== "string" || typeof original !== "function") { return original; } if (prop === "unsafe" || prop === "file") { return _wrapQueryMethod(original, target, proxiedSql, options); } if (prop === "begin" || prop === "reserve") { return _wrapCallbackMethod(original, target, proxiedSql, options); } return original; } }); if (parentConnectionContext) { proxiedSql[CONNECTION_CONTEXT_SYMBOL] = parentConnectionContext; } else { _attachConnectionContext(sql, proxiedSql); } sql[INSTRUMENTED_MARKER] = true; proxiedSql[INSTRUMENTED_MARKER] = true; return proxiedSql; } function _wrapQueryMethod(original, target, proxiedSql, options) { return function(...args) { const query = Reflect.apply(original, target, args); if (query && typeof query === "object" && "handle" in query) { _wrapSingleQueryHandle(query, proxiedSql, options); } return query; }; } function _wrapCallbackMethod(original, target, parentSqlInstance, options) { return function(...args) { const parentContext = parentSqlInstance[CONNECTION_CONTEXT_SYMBOL]; const isCallbackBased = typeof args[args.length - 1] === "function"; if (!isCallbackBased) { const result = Reflect.apply(original, target, args); if (result && typeof result.then === "function") { return result.then((sqlInstance) => { return _instrumentSqlInstance(sqlInstance, options, parentContext); }); } return result; } const callback = args.length === 1 ? args[0] : args[1]; const wrappedCallback = function(sqlInstance) { const instrumentedSql = _instrumentSqlInstance(sqlInstance, options, parentContext); return callback(instrumentedSql); }; const newArgs = args.length === 1 ? [wrappedCallback] : [args[0], wrappedCallback]; return Reflect.apply(original, target, newArgs); }; } function _wrapSingleQueryHandle(query, sqlInstance, options) { if (query.handle?.__sentryWrapped) { return; } query[QUERY_FROM_INSTRUMENTED_SQL] = true; const originalHandle = query.handle; const wrappedHandle = async function(...args) { if (!_shouldCreateSpans(options)) { return originalHandle.apply(this, args); } const fullQuery = _reconstructQuery(query.strings); const sanitizedSqlQuery = _sanitizeSqlQuery(fullQuery); return startSpanManual( { name: sanitizedSqlQuery || "postgresjs.query", op: "db" }, (span) => { span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, "auto.db.postgresjs"); span.setAttributes({ "db.system.name": "postgres", "db.query.text": sanitizedSqlQuery }); const connectionContext = sqlInstance ? sqlInstance[CONNECTION_CONTEXT_SYMBOL] : void 0; _setConnectionAttributes(span, connectionContext); if (options.requestHook) { try { options.requestHook(span, sanitizedSqlQuery, connectionContext); } catch (e) { span.setAttribute("sentry.hook.error", "requestHook failed"); DEBUG_BUILD && debug.error("Error in requestHook for PostgresJs instrumentation:", e); } } const queryWithCallbacks = this; queryWithCallbacks.resolve = new Proxy(queryWithCallbacks.resolve, { apply: (resolveTarget, resolveThisArg, resolveArgs) => { try { _setOperationName(span, sanitizedSqlQuery, resolveArgs?.[0]?.command); span.end(); } catch (e) { DEBUG_BUILD && debug.error("Error ending span in resolve callback:", e); } return Reflect.apply(resolveTarget, resolveThisArg, resolveArgs); } }); queryWithCallbacks.reject = new Proxy(queryWithCallbacks.reject, { apply: (rejectTarget, rejectThisArg, rejectArgs) => { try { span.setStatus({ code: SPAN_STATUS_ERROR, message: rejectArgs?.[0]?.message || "unknown_error" }); span.setAttribute("db.response.status_code", rejectArgs?.[0]?.code || "unknown"); span.setAttribute("error.type", rejectArgs?.[0]?.name || "unknown"); _setOperationName(span, sanitizedSqlQuery); span.end(); } catch (e) { DEBUG_BUILD && debug.error("Error ending span in reject callback:", e); } return Reflect.apply(rejectTarget, rejectThisArg, rejectArgs); } }); try { return originalHandle.apply(this, args); } catch (e) { span.setStatus({ code: SPAN_STATUS_ERROR, message: e instanceof Error ? e.message : "unknown_error" }); span.end(); throw e; } } ); }; wrappedHandle.__sentryWrapped = true; query.handle = wrappedHandle; } function _shouldCreateSpans(options) { const hasParentSpan = getActiveSpan() !== void 0; return hasParentSpan || !options.requireParentSpan; } function _reconstructQuery(strings) { if (!strings?.length) { return void 0; } if (strings.length === 1) { return strings[0] || void 0; } return strings.reduce((acc, str, i) => i === 0 ? str : `${acc}$${i}${str}`, ""); } let integerLiteralRE; function _sanitizeSqlQuery(sqlQuery) { if (!sqlQuery) { return "Unknown SQL Query"; } if (!integerLiteralRE) { integerLiteralRE = new RegExp("(?<!\\$)-?\\b\\d+\\b", "g"); } return sqlQuery.replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/;\s*$/, "").replace(/\s+/g, " ").trim().replace(/\bX'[0-9A-Fa-f]*'/gi, "?").replace(/\bB'[01]*'/gi, "?").replace(/'(?:[^']|'')*'/g, "?").replace(/\b0x[0-9A-Fa-f]+/gi, "?").replace(/\b(?:TRUE|FALSE)\b/gi, "?").replace(/-?\b\d+\.?\d*[eE][+-]?\d+\b/g, "?").replace(/-?\b\d+\.\d+\b/g, "?").replace(/-?\.\d+\b/g, "?").replace(integerLiteralRE, "?").replace(/\bIN\b\s*\(\s*\?(?:\s*,\s*\?)*\s*\)/gi, "IN (?)").replace(/\bIN\b\s*\(\s*\$\d+(?:\s*,\s*\$\d+)*\s*\)/gi, "IN ($?)"); } function _setConnectionAttributes(span, connectionContext) { if (!connectionContext) { return; } if (connectionContext.ATTR_DB_NAMESPACE) { span.setAttribute("db.namespace", connectionContext.ATTR_DB_NAMESPACE); } if (connectionContext.ATTR_SERVER_ADDRESS) { span.setAttribute("server.address", connectionContext.ATTR_SERVER_ADDRESS); } if (connectionContext.ATTR_SERVER_PORT !== void 0) { const portNumber = parseInt(connectionContext.ATTR_SERVER_PORT, 10); if (!isNaN(portNumber)) { span.setAttribute("server.port", portNumber); } } } function _setOperationName(span, sanitizedQuery, command) { if (command) { span.setAttribute("db.operation.name", command); return; } const operationMatch = sanitizedQuery?.match(SQL_OPERATION_REGEX); if (operationMatch?.[1]) { span.setAttribute("db.operation.name", operationMatch[1].toUpperCase()); } } function _attachConnectionContext(sql, proxiedSql) { const sqlInstance = sql; if (!sqlInstance.options || typeof sqlInstance.options !== "object") { return; } const opts = sqlInstance.options; const host = opts.host?.[0] || "localhost"; const port = opts.port?.[0] || 5432; const connectionContext = { ATTR_DB_NAMESPACE: typeof opts.database === "string" && opts.database !== "" ? opts.database : void 0, ATTR_SERVER_ADDRESS: host, ATTR_SERVER_PORT: String(port) }; proxiedSql[CONNECTION_CONTEXT_SYMBOL] = connectionContext; } export { _reconstructQuery, _sanitizeSqlQuery, instrumentPostgresJsSql }; //# sourceMappingURL=postgresjs.js.map