UNPKG

@instana/core

Version:
257 lines (216 loc) 7.34 kB
/* * (c) Copyright IBM Corp. 2021 * (c) Copyright Instana Inc. and contributors 2017 */ 'use strict'; const shimmer = require('../../shimmer'); const hook = require('../../../util/hook'); const tracingUtil = require('../../tracingUtil'); const constants = require('../../constants'); const cls = require('../../cls'); let isActive = false; exports.spanName = 'mysql'; exports.batchable = true; exports.init = function init() { hook.onModuleLoad('mysql', instrumentMysql); hook.onModuleLoad('mysql2', instrumentMysql2); hook.onModuleLoad('mysql2/promise', instrumentMysql2WithPromises); }; function instrumentMysql(mysql) { instrumentConnection(Object.getPrototypeOf(mysql.createConnection({})), false); instrumentPool(Object.getPrototypeOf(mysql.createPool({}))); } function instrumentMysql2(mysql) { /** * In mysql2 version 3.11.5 and later, the internal structure of the `Connection` and `Pool` classes was reorganized. * Methods like `query` and `execute` were moved into the `BaseConnection` class located in `lib/base/connection.js`, * and similar changes were applied to the `Pool` class. See: https://github.com/sidorares/node-mysql2/pull/3081 * * Prior to v3.11.5, the `Connection` and `Pool` prototypes were directly used for instrumentation. */ const connectionPrototype = Object.getPrototypeOf(mysql.Connection.prototype)?.constructor?.name === 'BaseConnection' ? Object.getPrototypeOf(mysql.Connection.prototype) : mysql.Connection.prototype; const poolPrototype = mysql.Pool && Object.getPrototypeOf(mysql.Pool.prototype)?.constructor?.name === 'BasePool' ? Object.getPrototypeOf(mysql.Pool.prototype) : mysql.Pool?.prototype; instrumentConnection(connectionPrototype, true); if (poolPrototype) { instrumentPool(poolPrototype); } } function instrumentMysql2WithPromises(mysql) { // Currently only pooled connections will be instrumented. instrumentPoolWithPromises(mysql); } function instrumentPool(Pool) { shimmer.wrap(Pool, 'query', shimQuery); // There is also an 'execute' method on the pool object but it uses the connection internally, so we do not need to // instrument it. This is handled by the instrumented methods on Connection. We do need to instrument 'pool.query', // though. shimmer.wrap(Pool, 'getConnection', shimGetConnection); } function instrumentConnection(Connection, mysql2) { shimmer.wrap(Connection, 'query', shimQuery); if (mysql2) { shimmer.wrap(Connection, 'execute', shimExecute); } } function instrumentPoolWithPromises(mysql) { shimmer.wrap( mysql, 'createPool', original => function () { const Pool = original.apply(this, arguments); const poolPrototype = Object.getPrototypeOf(Pool); shimmer.wrap(poolPrototype, 'getConnection', shimPromiseConnection); shimmer.wrap(poolPrototype, 'query', shimPromiseQuery); shimmer.wrap(poolPrototype, 'execute', shimPromiseExecute); return Pool; } ); } function shimQuery(original) { return function () { if (cls.skipExitTracing({ isActive })) { return original.apply(this, arguments); } return instrumentedAccessFunction(this, original, arguments[0], arguments[1], arguments[2]); }; } function shimExecute(original) { return function () { if (cls.skipExitTracing({ isActive })) { return original.apply(this, arguments); } return instrumentedAccessFunction(this, original, arguments[0], arguments[1], arguments[2]); }; } function shimPromiseQuery(originalQuery) { return function () { if (cls.skipExitTracing({ isActive })) { return originalQuery.apply(this, arguments); } return instrumentedAccessFunction(this, originalQuery, arguments[0], arguments[1], null, true); }; } function shimPromiseExecute(originalExecute) { return function () { if (cls.skipExitTracing({ isActive })) { return originalExecute.apply(this, arguments); } return instrumentedAccessFunction(this, originalExecute, arguments[0], arguments[1], null, true); }; } function instrumentedAccessFunction( ctx, originalFunction, statementOrOpts, valuesOrCallback, optCallback, isPromiseImpl ) { const originalArgs = [statementOrOpts, valuesOrCallback]; if (typeof optCallback !== 'undefined') { originalArgs.push(optCallback); } let host; let port; let user; let db; // if ctx.connection is defined, we are in a PromiseConnection context const config = ctx.connection != null ? ctx.connection.config : ctx.config; if (config) { if (config.connectionConfig) { host = config.connectionConfig.host; port = config.connectionConfig.port; user = config.connectionConfig.user; db = config.connectionConfig.database; } else { host = config.host; port = config.port; user = config.user; db = config.database; } } return cls.ns.runAndReturn(() => { const span = cls.startSpan({ spanName: exports.spanName, kind: constants.EXIT }); span.b = { s: 1 }; span.stack = tracingUtil.getStackTrace(instrumentedAccessFunction); span.data.mysql = { stmt: tracingUtil.shortenDatabaseStatement( typeof statementOrOpts === 'string' ? statementOrOpts : statementOrOpts.sql ), host, port, user, db }; if (isPromiseImpl) { const resultPromise = originalFunction.apply(ctx, originalArgs); resultPromise .then(result => { span.d = Date.now() - span.ts; span.transmit(); return result; }) .catch(error => { span.ec = 1; tracingUtil.setErrorDetails(span, error, exports.spanName); span.d = Date.now() - span.ts; span.transmit(); return error; }); return resultPromise; } // no promise, continue with standard instrumentation let originalCallback; function onResult(error) { if (error) { span.ec = 1; tracingUtil.setErrorDetails(span, error, exports.spanName); } span.d = Date.now() - span.ts; span.transmit(); if (originalCallback) { return originalCallback.apply(this, arguments); } } if (typeof statementOrOpts._callback === 'function') { originalCallback = cls.ns.bind(statementOrOpts._callback); statementOrOpts._callback = onResult; } else { if (typeof originalArgs[originalArgs.length - 1] === 'function') { originalCallback = cls.ns.bind(originalArgs[originalArgs.length - 1]); } originalArgs[originalArgs.length - 1] = onResult; } return originalFunction.apply(ctx, originalArgs); }); } function shimGetConnection(original) { return function getConnection(cb) { return original.call(this, cls.ns.bind(cb)); }; } function shimPromiseConnection(original) { return function getConnection() { return original.apply(this, arguments).then(connection => { shimmer.wrap(connection, 'query', shimPromiseQuery); shimmer.wrap(connection, 'execute', shimPromiseExecute); return connection; }); }; } exports.activate = function activate() { isActive = true; }; exports.deactivate = function deactivate() { isActive = false; };