lib/instrumentation/modules/mysql.js (154 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and other contributors where applicable. * Licensed under the BSD 2-Clause License; you may not use this file except in * compliance with the BSD 2-Clause License. */ 'use strict'; const EventEmitter = require('events'); var semver = require('semver'); var sqlSummary = require('sql-summary'); var shimmer = require('../shimmer'); var symbols = require('../../symbols'); var { getDBDestination } = require('../context'); module.exports = function (mysql, agent, { version, enabled }) { if (!enabled) { return mysql; } if (!semver.satisfies(version, '^2.0.0')) { agent.logger.debug('mysql version %s not supported - aborting...', version); return mysql; } agent.logger.debug('shimming mysql.createPool'); shimmer.wrap(mysql, 'createPool', wrapCreatePool); agent.logger.debug('shimming mysql.createPoolCluster'); shimmer.wrap(mysql, 'createPoolCluster', wrapCreatePoolCluster); agent.logger.debug('shimming mysql.createConnection'); shimmer.wrap(mysql, 'createConnection', wrapCreateConnection); return mysql; function wrapCreateConnection(original) { return function wrappedCreateConnection() { var connection = original.apply(this, arguments); wrapQueryable(connection, 'connection', agent); return connection; }; } function wrapCreatePool(original) { return function wrappedCreatePool() { var pool = original.apply(this, arguments); agent.logger.debug('shimming mysql pool.getConnection'); shimmer.wrap(pool, 'getConnection', wrapGetConnection); return pool; }; } function wrapCreatePoolCluster(original) { return function wrappedCreatePoolCluster() { var cluster = original.apply(this, arguments); agent.logger.debug('shimming mysql cluster.of'); shimmer.wrap(cluster, 'of', function wrapOf(original) { return function wrappedOf() { var ofCluster = original.apply(this, arguments); agent.logger.debug('shimming mysql cluster of.getConnection'); shimmer.wrap(ofCluster, 'getConnection', wrapGetConnection); return ofCluster; }; }); return cluster; }; } function wrapGetConnection(original) { return function wrappedGetConnection() { var cb = arguments[0]; if (typeof cb === 'function') { arguments[0] = agent._instrumentation.bindFunction( function wrapedCallback(_err, connection) { if (connection) wrapQueryable(connection, 'getConnection() > connection', agent); return cb.apply(this, arguments); }, ); } return original.apply(this, arguments); }; } }; function wrapQueryable(connection, objType, agent) { const ins = agent._instrumentation; agent.logger.debug('shimming mysql %s.query', objType); shimmer.wrap(connection, 'query', wrapQuery); let host, port, user, database; if (typeof connection.config === 'object') { ({ host, port, user, database } = connection.config); } function wrapQuery(original) { return function wrappedQuery(sql, values, cb) { agent.logger.debug('intercepted call to mysql %s.query', objType); var span = ins.createSpan(null, 'db', 'mysql', 'query', { exitSpan: true, }); if (!span) { return original.apply(this, arguments); } var hasCallback = false; var sqlStr; if (this[symbols.knexStackObj]) { span.customStackTrace(this[symbols.knexStackObj]); this[symbols.knexStackObj] = null; } const wrapCallback = function (origCallback) { hasCallback = true; return ins.bindFunction(function wrappedCallback(_err) { span.end(); return origCallback.apply(this, arguments); }); }; switch (typeof sql) { case 'string': sqlStr = sql; break; case 'object': if (typeof sql._callback === 'function') { sql._callback = wrapCallback(sql._callback); } sqlStr = sql.sql; break; case 'function': arguments[0] = wrapCallback(sql); break; } if (sqlStr) { agent.logger.debug({ sql: sqlStr }, 'extracted sql from mysql query'); span.setDbContext({ statement: sqlStr, type: 'sql', user, instance: database, }); span.name = sqlSummary(sqlStr); } span._setDestinationContext(getDBDestination(host, port)); if (typeof values === 'function') { arguments[1] = wrapCallback(values); } else if (typeof cb === 'function') { arguments[2] = wrapCallback(cb); } const spanRunContext = ins.currRunContext().enterSpan(span); const result = ins.withRunContext( spanRunContext, original, this, ...arguments, ); if (!hasCallback && result instanceof EventEmitter) { // Wrap `result.emit` instead of `result.once('error', ...)` to avoid // changing app behaviour by possibly setting the only 'error' handler. shimmer.wrap(result, 'emit', function (origEmit) { return function wrappedEmit(event, data) { // The 'mysql' module emits 'end' even after an 'error' event. switch (event) { case 'error': break; case 'end': span.end(); break; } return origEmit.apply(this, arguments); }; }); // Ensure event handlers execute in the caller run context. ins.bindEmitter(result); } return result; }; } }