lib/instrumentation/modules/pg.js (115 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 (pg, agent, { version, enabled }) { if (!enabled) { return pg; } if (!semver.satisfies(version, '>=4.0.0 <9.0.0')) { agent.logger.debug('pg version %s not supported - aborting...', version); return pg; } patchClient(pg.Client, 'pg.Client', agent); // Trying to access the pg.native getter will trigger and log the warning // "Cannot find module 'pg-native'" to STDERR if the module isn't installed. // Overwriting the getter we can lazily patch the native client only if the // user is acually requesting it. var getter = pg.__lookupGetter__('native'); if (getter) { delete pg.native; // To be as true to the original pg module as possible, we use // __defineGetter__ instead of Object.defineProperty. pg.__defineGetter__('native', function () { var native = getter(); if (native && native.Client) { patchClient(native.Client, 'pg.native.Client', agent); } return native; }); } return pg; }; function patchClient(Client, klass, agent) { agent.logger.debug('shimming %s.prototype.query', klass); shimmer.wrap(Client.prototype, 'query', wrapQuery); function wrapQuery(orig, name) { return function wrappedFunction(sql) { agent.logger.debug('intercepted call to %s.prototype.%s', klass, name); const ins = agent._instrumentation; const span = ins.createSpan('SQL', 'db', 'postgresql', 'query', { exitSpan: true, }); if (!span) { return orig.apply(this, arguments); } // Get connection parameters from Client. let host, port, database, user; if (typeof this.connectionParameters === 'object') { ({ host, port, database, user } = this.connectionParameters); } span._setDestinationContext(getDBDestination(host, port)); const dbContext = { type: 'sql' }; let sqlText = sql; if (sql && typeof sql.text === 'string') { sqlText = sql.text; } if (typeof sqlText === 'string') { span.name = sqlSummary(sqlText); dbContext.statement = sqlText; } else { agent.logger.debug( 'unable to parse sql form pg module (type: %s)', typeof sqlText, ); } if (database) { dbContext.instance = database; } if (user) { dbContext.user = user; } span.setDbContext(dbContext); if (this[symbols.knexStackObj]) { span.customStackTrace(this[symbols.knexStackObj]); this[symbols.knexStackObj] = null; } let index = arguments.length - 1; let cb = arguments[index]; if (Array.isArray(cb)) { index = cb.length - 1; cb = cb[index]; } const spanRunContext = ins.currRunContext().enterSpan(span); const onQueryEnd = ins.bindFunctionToRunContext( spanRunContext, (_err) => { agent.logger.debug('intercepted end of %s.prototype.%s', klass, name); span.end(); }, ); if (typeof cb === 'function') { arguments[index] = ins.bindFunction((err, res) => { onQueryEnd(err); return cb(err, res); }); return orig.apply(this, arguments); } else { var queryOrPromise = orig.apply(this, arguments); // It is important to prefer `.on` to `.then` for pg <7 >=6.3.0, because // `query.then` is broken in those versions. See // https://github.com/brianc/node-postgres/commit/b5b49eb895727e01290e90d08292c0d61ab86322#r23267714 if (typeof queryOrPromise.on === 'function') { queryOrPromise.on('end', onQueryEnd); queryOrPromise.on('error', onQueryEnd); if (queryOrPromise instanceof EventEmitter) { ins.bindEmitter(queryOrPromise); } } else if (typeof queryOrPromise.then === 'function') { queryOrPromise.then(() => { onQueryEnd(); }, onQueryEnd); } else { agent.logger.debug( 'ERROR: unknown pg query type: %s', typeof queryOrPromise, ); } return queryOrPromise; } }; } }