Skip to content

Commit

Permalink
feat(tracing): Add ioredis support
Browse files Browse the repository at this point in the history
closes #30
  • Loading branch information
Andrew Schmadel authored and bripkens committed Jan 26, 2018
1 parent e907799 commit 327750e
Show file tree
Hide file tree
Showing 8 changed files with 605 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Unreleased
- - Add ioredis instrumentation.

## 1.32.0
- Include details about uncaught errors in express handlers.

Expand Down
158 changes: 157 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"eslint": "1.7.2",
"eslint-config-airbnb": "0.1.0",
"express": "4.14.0",
"ioredis": "^3.2.2",
"kafka-node": "^1.0.7",
"lodash": "^4.15.0",
"mocha": "2.3.3",
Expand Down
3 changes: 3 additions & 0 deletions src/tracing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ exports.init = function(_config) {
require('./instrumentation/mysql.js').init(config);
require('./instrumentation/redis.js').init(config);
require('./instrumentation/express.js').init(config);
require('./instrumentation/ioredis.js').init(config);
}
}
};
Expand Down Expand Up @@ -88,6 +89,7 @@ exports.activate = function() {
require('./instrumentation/kafka.js').activate();
require('./instrumentation/mysql.js').activate();
require('./instrumentation/redis.js').activate();
require('./instrumentation/ioredis.js').activate();
require('./instrumentation/express.js').activate();
}
}
Expand All @@ -104,6 +106,7 @@ exports.deactivate = function() {
require('./instrumentation/httpServer.js').deactivate();
require('./instrumentation/httpClient.js').deactivate();
require('./instrumentation/redis.js').deactivate();
require('./instrumentation/ioredis.js').deactivate();
require('./instrumentation/express.js').deactivate();
}

Expand Down
115 changes: 115 additions & 0 deletions src/tracing/instrumentation/ioredis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use strict';

var shimmer = require('shimmer');

var requireHook = require('../../util/requireHook');
var transmission = require('../transmission');
var tracingUtil = require('../tracingUtil');
var cls = require('../cls');

var isActive = false;

exports.activate = function() {
isActive = true;
};

exports.deactivate = function() {
isActive = false;
};

exports.init = function() {
requireHook.on('ioredis', instrument);
};


function instrument(ioredis) {
shimmer.wrap(ioredis.prototype, 'sendCommand', instrumentIoredisSendCommand);
}

function instrumentIoredisSendCommand(original) {
return function wrappedInternalSendCommand(command) {
var client = this;

if (command.promise == null ||
typeof command.name !== 'string' ||
!isActive ||
!cls.isTracing()) {
return original.apply(this, arguments);
}

var callback;
var parentSpan = cls.getCurrentSpan();
if (cls.isExitSpan(parentSpan)) {
// multi commands could actually be recorded as multiple spans, but we only want to record one
// batched span
if (parentSpan.n === 'redis' && parentSpan.data.redis.command === 'multi') {

This comment has been minimized.

Copy link
@schmod

schmod Jan 26, 2018

Contributor

I think we need to support pipeline here also

var parentSpanSubCommands = parentSpan.data.redis.subCommands = parentSpan.data.redis.subCommands || [];
parentSpanSubCommands.push(command.name);

if (command.name.toLowerCase() === 'exec') {
callback = cls.ns.bind(getMultiCommandEndCall(parentSpan));
command.promise.then(
// make sure that the first parameter is never truthy
callback.bind(null, null),
callback);
}
}
return original.apply(this, arguments);
}

var span = cls.startSpan('redis');
span.stack = tracingUtil.getStackTrace(wrappedInternalSendCommand);
span.data = {
redis: {
connection: client.options.host + ':' + client.options.port,
command: command.name.toLowerCase()
}
};

callback = cls.ns.bind(onResult);
command.promise.then(
// make sure that the first parameter is never truthy
callback.bind(null, null),
callback);
return original.apply(this, arguments);

function onResult(error) {
// multi commands are ended by exec. Wait for the exec result
if (command.name === 'pipeline') {

This comment has been minimized.

Copy link
@schmod

schmod Jan 26, 2018

Contributor

I think we need to also consider multi here

return;
}

span.d = Date.now() - span.ts;

if (error) {
span.error = true;
span.ec = 1;
span.data.redis.error = error.message;
}

transmission.addSpan(span);
}
};
}

function getMultiCommandEndCall(span) {
return function multiCommandEndCallback(error) {
span.d = Date.now() - span.ts;

var subCommands = span.data.redis.subCommands;
var commandCount = subCommands ? subCommands.length : 1;

span.b = {
s: commandCount,
u: false
};

if (error) {
span.error = true;
span.ec = commandCount;
span.data.redis.error = error.message;
}

transmission.addSpan(span);
};
}
Loading

0 comments on commit 327750e

Please sign in to comment.