From ab784dc1f75e9d99e2f092ce143475210e2e7f7b Mon Sep 17 00:00:00 2001 From: rochdev Date: Wed, 19 Sep 2018 22:23:50 -0400 Subject: [PATCH] add ioredis integration --- docs/API.md | 16 +++++ src/plugins/index.js | 1 + src/plugins/ioredis.js | 51 ++++++++++++++++ test/plugins/ioredis.spec.js | 109 +++++++++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+) create mode 100644 src/plugins/ioredis.js create mode 100644 test/plugins/ioredis.spec.js diff --git a/docs/API.md b/docs/API.md index 53409ae0ed2..51dd4a96cfe 100644 --- a/docs/API.md +++ b/docs/API.md @@ -217,6 +217,22 @@ query HelloWorld { | service | http-client | The service name for this integration. | | splitByDomain | false | Use the remote endpoint host as the service name instead of the default. | +

ioredis

+ +
Tags
+ +| Tag | Description | +|------------------|-----------------------------------------------------------| +| db.name | The index of the queried database. | +| out.host | The host of the Redis server. | +| out.port | The port of the Redis server. | + +
Configuration Options
+ +| Option | Default | Description | +|------------------|------------------|----------------------------------------| +| service | redis | The service name for this integration. | +

mongodb-core

Tags
diff --git a/src/plugins/index.js b/src/plugins/index.js index 2fdf82a23c9..9b0a875e1f7 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -8,6 +8,7 @@ module.exports = { 'graphql': require('./graphql'), 'hapi': require('./hapi'), 'http': require('./http'), + 'ioredis': require('./ioredis'), 'mongodb-core': require('./mongodb-core'), 'mysql': require('./mysql'), 'mysql2': require('./mysql2'), diff --git a/src/plugins/ioredis.js b/src/plugins/ioredis.js new file mode 100644 index 00000000000..8028545fdf9 --- /dev/null +++ b/src/plugins/ioredis.js @@ -0,0 +1,51 @@ +'use strict' + +function createWrapSendCommand (tracer, config) { + return function wrapSendCommand (sendCommand) { + return function sendCommandWithTrace (command, stream) { + const scope = tracer.scopeManager().active() + const span = tracer.startSpan('redis.command', { + childOf: scope && scope.span(), + tags: { + 'span.kind': 'client', + 'span.type': 'redis', + 'service.name': config.service || `${tracer._service}-redis`, + 'resource.name': command.name, + 'db.type': 'redis', + 'db.name': this.options.db || '0', + 'out.host': this.options.host, + 'out.port': String(this.options.port) + } + }) + + command.promise + .then(() => finish(span)) + .catch(err => finish(span, err)) + + return sendCommand.apply(this, arguments) + } + } +} + +function finish (span, error) { + if (error) { + span.addTags({ + 'error.type': error.name, + 'error.msg': error.message, + 'error.stack': error.stack + }) + } + + span.finish() +} + +module.exports = { + name: 'ioredis', + versions: ['4.x'], + patch (Redis, tracer, config) { + this.wrap(Redis.prototype, 'sendCommand', createWrapSendCommand(tracer, config)) + }, + unpatch (Redis) { + this.unwrap(Redis.prototype, 'sendCommand') + } +} diff --git a/test/plugins/ioredis.spec.js b/test/plugins/ioredis.spec.js new file mode 100644 index 00000000000..16e2bb3501e --- /dev/null +++ b/test/plugins/ioredis.spec.js @@ -0,0 +1,109 @@ +'use strict' + +const agent = require('./agent') +const plugin = require('../../src/plugins/ioredis') + +wrapIt() + +describe('Plugin', () => { + let Redis + let redis + let tracer + + describe('ioredis', () => { + withVersions(plugin, 'ioredis', version => { + beforeEach(() => { + tracer = require('../..') + }) + + afterEach(() => { + redis.quit() + return agent.close() + }) + + describe('without configuration', () => { + beforeEach(() => { + return agent.load(plugin, 'ioredis') + .then(() => { + Redis = require(`./versions/ioredis@${version}`).get() + redis = new Redis() + }) + }) + + it('should do automatic instrumentation when using callbacks', done => { + agent.use(() => {}) // wait for initial info command + agent + .use(traces => { + expect(traces[0][0]).to.have.property('name', 'redis.command') + expect(traces[0][0]).to.have.property('service', 'test-redis') + expect(traces[0][0]).to.have.property('resource', 'get') + expect(traces[0][0]).to.have.property('type', 'redis') + expect(traces[0][0].meta).to.have.property('db.name', '0') + expect(traces[0][0].meta).to.have.property('db.type', 'redis') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + expect(traces[0][0].meta).to.have.property('out.host', 'localhost') + expect(traces[0][0].meta).to.have.property('out.port', '6379') + }) + .then(done) + .catch(done) + + redis.get('foo').catch(done) + }) + + it('should run the callback in the parent context', () => { + if (process.env.DD_CONTEXT_PROPAGATION === 'false') return + + const scope = tracer.scopeManager().activate({}) + + return redis.get('foo') + .then(() => { + expect(tracer.scopeManager().active()).to.equal(scope) + }) + }) + + it('should handle errors', done => { + let error + + agent.use(() => {}) // wait for initial info command + agent + .use(traces => { + expect(traces[0][0]).to.have.property('error', 1) + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) + + redis.set('foo', 123, 'bar') + .then(() => done()) + .catch(err => { + error = err + done() + }) + }) + }) + + describe('with configuration', () => { + beforeEach(() => { + return agent.load(plugin, 'ioredis', { service: 'custom' }) + .then(() => { + Redis = require(`./versions/ioredis@${version}`).get() + redis = new Redis() + }) + }) + + it('should be configured with the correct values', done => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') + }) + .then(done) + .catch(done) + + redis.get('foo').catch(done) + }) + }) + }) + }) +})