lib/instrumentation/modules/ioredis.js (70 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';
// Instrumentation of the 'ioredis' package:
// https://github.com/luin/ioredis
// https://github.com/luin/ioredis/blob/master/API.md
const semver = require('semver');
const constants = require('../../constants');
const { getDBDestination } = require('../context');
const shimmer = require('../shimmer');
const TYPE = 'db';
const SUBTYPE = 'redis';
const ACTION = 'query';
const hasIoredisSpanSym = Symbol('ElasticAPMHasIoredisSpan');
module.exports = function (ioredis, agent, { version, enabled }) {
if (!enabled) {
return ioredis;
}
if (!semver.satisfies(version, '>=2.0.0 <6.0.0')) {
agent.logger.debug(
'ioredis version %s not supported - aborting...',
version,
);
return ioredis;
}
const ins = agent._instrumentation;
agent.logger.debug('shimming ioredis.prototype.sendCommand');
shimmer.wrap(ioredis.prototype, 'sendCommand', wrapSendCommand);
return ioredis;
function wrapSendCommand(origSendCommand) {
return function wrappedSendCommand(command) {
if (!command || !command.name || !command.promise) {
// Doesn't look like an ioredis.Command, skip instrumenting.
return origSendCommand.apply(this, arguments);
}
if (command[hasIoredisSpanSym]) {
// Avoid double-instrumenting a command when ioredis *re*-calls
// sendCommand for queued commands when "ready".
return origSendCommand.apply(this, arguments);
}
agent.logger.debug(
{ command: command.name },
'intercepted call to ioredis.prototype.sendCommand',
);
const span = ins.createSpan(
command.name.toUpperCase(),
TYPE,
SUBTYPE,
ACTION,
{ exitSpan: true },
);
if (!span) {
return origSendCommand.apply(this, arguments);
}
command[hasIoredisSpanSym] = true;
const options = this.options || {}; // `this` is the `Redis` client.
span._setDestinationContext(getDBDestination(options.host, options.port));
span.setDbContext({ type: 'redis' });
const spanRunContext = ins.currRunContext().enterSpan(span);
command.promise.then(
() => {
span.end();
},
ins.bindFunctionToRunContext(spanRunContext, (err) => {
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
agent.captureError(err, { skipOutcome: true });
span.end();
}),
);
return ins.withRunContext(
spanRunContext,
origSendCommand,
this,
...arguments,
);
};
}
};