From 3ff7b2d02c30ad946806a72fbdee564d5d84acbc Mon Sep 17 00:00:00 2001 From: James Hrisho Date: Tue, 11 Oct 2016 20:02:22 -0400 Subject: [PATCH] Adds zipkin-instrumentation-redis support (#50) --- .travis.yml | 1 + README.md | 1 + appveyor.yml | 2 +- .../zipkin-instrumentation-redis/README.md | 22 +++++ .../zipkin-instrumentation-redis/package.json | 19 +++++ .../src/zipkinClient.js | 53 ++++++++++++ .../test/.eslintrc | 8 ++ .../test/integrationTest.js | 84 +++++++++++++++++++ 8 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 packages/zipkin-instrumentation-redis/README.md create mode 100644 packages/zipkin-instrumentation-redis/package.json create mode 100644 packages/zipkin-instrumentation-redis/src/zipkinClient.js create mode 100644 packages/zipkin-instrumentation-redis/test/.eslintrc create mode 100644 packages/zipkin-instrumentation-redis/test/integrationTest.js diff --git a/.travis.yml b/.travis.yml index 57e30f7e..75222ff4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ script: services: - memcached + - redis compiler: clang-3.6 env: diff --git a/README.md b/README.md index cf3c17f5..a0477f62 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ At the time of writing, zipkin-js instruments these libraries: - [fetch](packages/zipkin-instrumentation-fetch) (zipkin-instrumentation-fetch) - [hapi](packages/zipkin-instrumentation-hapi) (zipkin-instrumentation-hapi) - [memcached](packages/zipkin-instrumentation-memcached) (zipkin-instrumentation-memcached) +- [redis](packages/zipkin-instrumentation-redis) (zipkin-instrumentation-redis) - [restify](packages/zipkin-instrumentation-restify) (zipkin-instrumentation-restify) Every module has a README.md file that describes how to use it. diff --git a/appveyor.yml b/appveyor.yml index aa78a12f..cb18115a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ install: - - choco install nodejs memcached + - choco install nodejs memcached redis - node --version - npm --version - npm install diff --git a/packages/zipkin-instrumentation-redis/README.md b/packages/zipkin-instrumentation-redis/README.md new file mode 100644 index 00000000..1df69dc8 --- /dev/null +++ b/packages/zipkin-instrumentation-redis/README.md @@ -0,0 +1,22 @@ +# zipkin-instrumentation-redis + +This library will wrap the [redis client](https://www.npmjs.com/package/redis). + +## Usage + +```javascript +const {Tracer} = require('zipkin'); +const Redis = require('redis'); +const zipkinClient = require('zipkin-instrumentation-redis'); +const tracer = new Tracer({ctxImpl, recorder}); // configure your tracer properly here +const redisConnectionOptions = { + host: 'localhost', + port: '6379' +}; +const redis = zipkinClient(tracer, Redis, redisConnectionOptions); + +// Your application code here +redis.get('foo', (err, data) => { + console.log('got', data.foo); +}); +``` diff --git a/packages/zipkin-instrumentation-redis/package.json b/packages/zipkin-instrumentation-redis/package.json new file mode 100644 index 00000000..021edb42 --- /dev/null +++ b/packages/zipkin-instrumentation-redis/package.json @@ -0,0 +1,19 @@ +{ + "name": "zipkin-instrumentation-redis", + "version": "0.2.6", + "description": "Interceptor for redis clients", + "main": "src/zipkinClient.js", + "scripts": { + "test": "node ../../node_modules/mocha/bin/mocha --require ../../test/helper.js" + }, + "author": "OpenZipkin ", + "license": "Apache-2.0", + "repository": "https://github.com/openzipkin/zipkin-js", + "devDependencies": { + "redis": "^2.6.2", + "zipkin": "^0.2.6" + }, + "dependencies": { + "redis-commands": "^1.2.0" + } +} diff --git a/packages/zipkin-instrumentation-redis/src/zipkinClient.js b/packages/zipkin-instrumentation-redis/src/zipkinClient.js new file mode 100644 index 00000000..4514b74f --- /dev/null +++ b/packages/zipkin-instrumentation-redis/src/zipkinClient.js @@ -0,0 +1,53 @@ +const {Annotation} = require('zipkin'); +const redisCommands = require('redis-commands'); +module.exports = function zipkinClient(tracer, redis, options, serviceName = 'redis') { + function mkZipkinCallback(callback, id) { + return function zipkinCallback(...args) { + tracer.scoped(() => { + tracer.setId(id); + tracer.recordAnnotation(new Annotation.ClientRecv()); + }); + callback.apply(this, args); + }; + } + function commonAnnotations(rpc) { + tracer.recordAnnotation(new Annotation.ClientSend()); + tracer.recordServiceName(serviceName); + tracer.recordRpc(rpc); + } + + + const redisClient = redis.createClient(options); + const methodsToWrap = redisCommands.list; + const restrictedCommands = [ + 'ping', + 'flushall', + 'flushdb', + 'select', + 'auth', + 'info', + 'quit', + 'slaveof', + 'config', + 'sentinel']; + methodsToWrap.forEach((method) => { + if (restrictedCommands.indexOf(method) > -1) { + return; + } + const actualFn = redisClient[method]; + redisClient[method] = function(...args) { + const callback = args.pop(); + let id; + tracer.scoped(() => { + id = tracer.createChildId(); + tracer.setId(id); + commonAnnotations(method); + }); + const wrapper = mkZipkinCallback(callback, id); + const newArgs = [...args, wrapper]; + actualFn.apply(this, newArgs); + }; + }); + + return redisClient; +}; diff --git a/packages/zipkin-instrumentation-redis/test/.eslintrc b/packages/zipkin-instrumentation-redis/test/.eslintrc new file mode 100644 index 00000000..3c8fcd73 --- /dev/null +++ b/packages/zipkin-instrumentation-redis/test/.eslintrc @@ -0,0 +1,8 @@ +{ + "env": { + "mocha": true + }, + "globals": { + "expect": true + } +} diff --git a/packages/zipkin-instrumentation-redis/test/integrationTest.js b/packages/zipkin-instrumentation-redis/test/integrationTest.js new file mode 100644 index 00000000..bcec8659 --- /dev/null +++ b/packages/zipkin-instrumentation-redis/test/integrationTest.js @@ -0,0 +1,84 @@ +const sinon = require('sinon'); +const {Tracer, ExplicitContext} = require('zipkin'); +const zipkinClient = require('../src/zipkinClient'); + +const redisConnectionOptions = { + host: 'localhost', + port: '6379' +}; + +const Redis = require('redis'); + +function getRedis(tracer) { + return zipkinClient(tracer, Redis, redisConnectionOptions); +} + +describe('redis interceptor', () => { + it('should add zipkin annotations', (done) => { + const ctxImpl = new ExplicitContext(); + const recorder = {record: sinon.spy()}; + // const recorder = new ConsoleRecorder(); + const tracer = new Tracer({ctxImpl, recorder}); + + const redis = getRedis(tracer); + redis.on('error', done); + tracer.setId(tracer.createRootId()); + const ctx = ctxImpl.getContext(); + redis.set('ping', 'pong', 10, () => { + ctxImpl.letContext(ctx, () => { + redis.get('ping', () => { + const annotations = recorder.record.args.map(args => args[0]); + const firstAnn = annotations[0]; + expect(annotations).to.have.length(8); + + function runTest(start, stop) { + let lastSpanId; + annotations.slice(start, stop).forEach((ann) => { + if (!lastSpanId) { + lastSpanId = ann.traceId.spanId; + } + expect(ann.traceId.spanId).to.equal(lastSpanId); + }); + } + + runTest(0, 4); + runTest(4, 8); + + expect( + annotations[0].traceId.spanId + ).not.to.equal(annotations[4].traceId.spanId); + + annotations.forEach(ann => { + expect(ann.traceId.parentId).to.equal(firstAnn.traceId.traceId); + expect(ann.traceId.spanId).not.to.equal(firstAnn.traceId.traceId); + expect(ann.traceId.traceId).to.equal(firstAnn.traceId.traceId); + }); + + done(); + }); + }); + }); + }); + + it('should run redis calls', done => { + const ctxImpl = new ExplicitContext(); + const recorder = {record: () => { }}; + const tracer = new Tracer({ctxImpl, recorder}); + const redis = getRedis(tracer); + redis.on('error', done); + redis.set('foo', 'bar', err => { + if (err) { + done(err); + } else { + redis.get('foo', (err2, data) => { + if (err2) { + done(err2); + } else { + expect(data).to.deep.equal('bar'); + done(); + } + }); + } + }); + }); +});