diff --git a/.circleci/config.yml b/.circleci/config.yml
index 95d4c61fa23..ab5b0ddceb9 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,21 +1,26 @@
version: 2
-test_env: &test_env
+node_test_env: &node_test_env
RUN_POSTGRES_TESTS: 1
RUN_MONGODB_TESTS: 1
+ RUN_REDIS_TESTS: 1
POSTGRES_USER: postgres
POSTGRES_DB: circle_database
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
+ OPENTELEMETRY_REDIS_HOST: 'localhost'
+ OPENTELEMETRY_REDIS_PORT: 6379
MONGODB_HOST: localhost
MONGODB_PORT: 27017
MONGODB_DB: opentelemetry-tests
postgres_service: &postgres_service
image: circleci/postgres:9.6-alpine
- environment: # env to pass to CircleCI, specified values must match test_env
+ environment: # env to pass to CircleCI, specified values must match node_test_env
POSTGRES_USER: postgres
POSTGRES_DB: circle_database
+redis_service: &redis_service
+ image: redis
mongo_service: &mongo_service
image: mongo
@@ -150,22 +155,25 @@ jobs:
node8:
docker:
- image: node:8
- environment: *test_env
+ environment: *node_test_env
- *postgres_service
+ - *redis_service
- *mongo_service
<<: *node_unit_tests
node10:
docker:
- image: node:10
- environment: *test_env
+ environment: *node_test_env
- *postgres_service
+ - *redis_service
- *mongo_service
<<: *node_unit_tests
node12:
docker:
- image: node:12
- environment: *test_env
+ environment: *node_test_env
- *postgres_service
+ - *redis_service
- *mongo_service
<<: *node_unit_tests
node12-browsers:
diff --git a/packages/opentelemetry-plugin-redis/README.md b/packages/opentelemetry-plugin-redis/README.md
index 3509b21de35..266435a421d 100644
--- a/packages/opentelemetry-plugin-redis/README.md
+++ b/packages/opentelemetry-plugin-redis/README.md
@@ -4,7 +4,7 @@
[![devDependencies][devDependencies-image]][devDependencies-url]
[![Apache License][license-image]][license-image]
-This module provides automatic instrumentation for [`redis`](https://github.com/NodeRedis/node_redis).
+This module provides automatic instrumentation for [`redis@^2.6.0`](https://github.com/NodeRedis/node_redis).
For automatic instrumentation see the
[@opentelemetry/node](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node) package.
@@ -15,14 +15,37 @@ For automatic instrumentation see the
npm install --save @opentelemetry/plugin-redis
```
+### Supported Versions
+ - `>=2.6.0`
+
## Usage
+OptenTelemetry Redis Instrumentation allows the user to automatically collect trace data and export them to the backend of choice, to give observability to distributed systems when working with [redis](https://www.npmjs.com/package/redis).
+
+To load a specific plugin (**redis** in this case), specify it in the Node Tracer's configuration
+```js
+const { NodeTracer } = require('@opentelemetry/node');
+
+const tracer = new NodeTracer({
+ plugins: {
+ redis: {
+ enabled: true,
+ // You may use a package name or absolute path to the file.
+ path: '@opentelemetry/plugin-redis',
+ }
+ }
+});
```
-const opentelemetry = require('@opentelemetry/plugin-redis');
-// TODO: DEMONSTRATE API
+To load all the [supported plugins](https://github.com/open-telemetry/opentelemetry-js#plugins), use below approach. Each plugin is only loaded when the module that it patches is loaded; in other words, there is no computational overhead for listing plugins for unused modules.
+```javascript
+const { NodeTracer } = require('@opentelemetry/node');
+
+const tracer = new NodeTracer();
```
+
+
## Useful links
- For more information on OpenTelemetry, visit:
- For more about OpenTelemetry JavaScript:
diff --git a/packages/opentelemetry-plugin-redis/package.json b/packages/opentelemetry-plugin-redis/package.json
index baa86c0f4f5..07ea3b3e482 100644
--- a/packages/opentelemetry-plugin-redis/package.json
+++ b/packages/opentelemetry-plugin-redis/package.json
@@ -2,17 +2,19 @@
"name": "@opentelemetry/plugin-redis",
"version": "0.2.0",
"description": "OpenTelemetry redis automatic instrumentation package.",
- "private": true,
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js",
"scripts": {
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts'",
+ "test:debug": "cross-env RUN_REDIS_TESTS_LOCAL=true ts-mocha --inspect-brk --no-timeouts -p tsconfig.json 'test/**/*.test.ts'",
+ "test:local": "cross-env RUN_REDIS_TESTS_LOCAL=true yarn test",
"tdd": "yarn test -- --watch-extensions ts --watch",
"clean": "rimraf build/*",
"check": "gts check",
"precompile": "tsc --version",
"compile": "tsc -p .",
+ "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"fix": "gts fix",
"prepare": "npm run compile"
},
@@ -42,10 +44,16 @@
"devDependencies": {
"@types/mocha": "^5.2.7",
"@types/node": "^12.6.9",
+ "@types/redis": "^2.8.14",
+ "@types/shimmer": "^1.0.1",
+ "@opentelemetry/node": "^0.2.0",
+ "@opentelemetry/tracing": "^0.2.0",
"codecov": "^3.6.1",
+ "cross-env": "^6.0.3",
"gts": "^1.1.0",
"mocha": "^6.2.0",
"nyc": "^14.1.1",
+ "redis": "^2.8.0",
"rimraf": "^3.0.0",
"ts-mocha": "^6.0.0",
"ts-node": "^8.3.0",
@@ -55,7 +63,7 @@
},
"dependencies": {
"@opentelemetry/core": "^0.2.0",
- "@opentelemetry/node": "^0.2.0",
- "@opentelemetry/types": "^0.2.0"
+ "@opentelemetry/types": "^0.2.0",
+ "shimmer": "^1.2.1"
}
}
diff --git a/packages/opentelemetry-plugin-redis/src/enums.ts b/packages/opentelemetry-plugin-redis/src/enums.ts
new file mode 100644
index 00000000000..035287a14bb
--- /dev/null
+++ b/packages/opentelemetry-plugin-redis/src/enums.ts
@@ -0,0 +1,32 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum AttributeNames {
+ // required by https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md#databases-client-calls
+ COMPONENT = 'component',
+ DB_TYPE = 'db.type',
+ DB_INSTANCE = 'db.instance',
+ DB_STATEMENT = 'db.statement',
+ PEER_ADDRESS = 'peer.address',
+ PEER_HOSTNAME = 'peer.host',
+
+ // optional
+ DB_USER = 'db.user',
+ PEER_PORT = 'peer.port',
+ PEER_IPV4 = 'peer.ipv4',
+ PEER_IPV6 = 'peer.ipv6',
+ PEER_SERVICE = 'peer.service',
+}
diff --git a/packages/opentelemetry-plugin-redis/src/index.ts b/packages/opentelemetry-plugin-redis/src/index.ts
index ae225f6b521..4595acb43a2 100644
--- a/packages/opentelemetry-plugin-redis/src/index.ts
+++ b/packages/opentelemetry-plugin-redis/src/index.ts
@@ -13,3 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+export * from './redis';
diff --git a/packages/opentelemetry-plugin-redis/src/redis.ts b/packages/opentelemetry-plugin-redis/src/redis.ts
new file mode 100644
index 00000000000..5d4cb833f89
--- /dev/null
+++ b/packages/opentelemetry-plugin-redis/src/redis.ts
@@ -0,0 +1,101 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BasePlugin } from '@opentelemetry/core';
+import * as redisTypes from 'redis';
+import * as shimmer from 'shimmer';
+import {
+ getTracedCreateClient,
+ getTracedCreateStreamTrace,
+ getTracedInternalSendCommand,
+} from './utils';
+
+export class RedisPlugin extends BasePlugin {
+ static readonly COMPONENT = 'redis';
+ readonly supportedVersions = ['^2.6.0']; // equivalent to >= 2.6.0 <3
+
+ constructor(readonly moduleName: string) {
+ super();
+ }
+
+ protected patch() {
+ if (this._moduleExports.RedisClient) {
+ this._logger.debug(
+ 'Patching redis.RedisClient.prototype.internal_send_command'
+ );
+ shimmer.wrap(
+ this._moduleExports.RedisClient.prototype,
+ 'internal_send_command',
+ this._getPatchInternalSendCommand()
+ );
+
+ this._logger.debug('patching redis.create_stream');
+ shimmer.wrap(
+ this._moduleExports.RedisClient.prototype,
+ 'create_stream',
+ this._getPatchCreateStream()
+ );
+
+ this._logger.debug('patching redis.createClient');
+ shimmer.wrap(
+ this._moduleExports,
+ 'createClient',
+ this._getPatchCreateClient()
+ );
+ }
+ return this._moduleExports;
+ }
+
+ protected unpatch(): void {
+ if (this._moduleExports) {
+ shimmer.unwrap(
+ this._moduleExports.RedisClient.prototype,
+ 'internal_send_command'
+ );
+ shimmer.unwrap(
+ this._moduleExports.RedisClient.prototype,
+ 'create_stream'
+ );
+ shimmer.unwrap(this._moduleExports, 'createClient');
+ }
+ }
+
+ /**
+ * Patch internal_send_command(...) to trace requests
+ */
+ private _getPatchInternalSendCommand() {
+ const tracer = this._tracer;
+ return function internal_send_command(original: Function) {
+ return getTracedInternalSendCommand(tracer, original);
+ };
+ }
+
+ private _getPatchCreateClient() {
+ const tracer = this._tracer;
+ return function createClient(original: Function) {
+ return getTracedCreateClient(tracer, original);
+ };
+ }
+
+ private _getPatchCreateStream() {
+ const tracer = this._tracer;
+ return function createReadStream(original: Function) {
+ return getTracedCreateStreamTrace(tracer, original);
+ };
+ }
+}
+
+export const plugin = new RedisPlugin(RedisPlugin.COMPONENT);
diff --git a/packages/opentelemetry-plugin-redis/src/types.ts b/packages/opentelemetry-plugin-redis/src/types.ts
new file mode 100644
index 00000000000..4014508f49a
--- /dev/null
+++ b/packages/opentelemetry-plugin-redis/src/types.ts
@@ -0,0 +1,41 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as redisTypes from 'redis';
+import { EventEmitter } from 'events';
+
+// exported from
+// https://github.com/NodeRedis/node_redis/blob/master/lib/command.js
+export interface RedisCommand {
+ command: string;
+ args: string[];
+ buffer_args: boolean;
+ callback: redisTypes.Callback;
+ call_on_write: boolean;
+}
+
+export interface RedisPluginClientTypes {
+ options?: {
+ host: string;
+ port: string;
+ };
+
+ address?: string;
+}
+
+export interface RedisPluginStreamTypes {
+ stream?: { get(): EventEmitter; set(val: EventEmitter): void };
+}
diff --git a/packages/opentelemetry-plugin-redis/src/utils.ts b/packages/opentelemetry-plugin-redis/src/utils.ts
new file mode 100644
index 00000000000..0be83cf6446
--- /dev/null
+++ b/packages/opentelemetry-plugin-redis/src/utils.ts
@@ -0,0 +1,126 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as redisTypes from 'redis';
+import { Tracer, SpanKind, Span, CanonicalCode } from '@opentelemetry/types';
+import {
+ RedisPluginStreamTypes,
+ RedisPluginClientTypes,
+ RedisCommand,
+} from './types';
+import { EventEmitter } from 'events';
+import { RedisPlugin } from './redis';
+import { AttributeNames } from './enums';
+
+const endSpan = (span: Span, err?: Error | null) => {
+ if (err) {
+ span.setStatus({
+ code: CanonicalCode.UNKNOWN,
+ message: err.message,
+ });
+ } else {
+ span.setStatus({ code: CanonicalCode.OK });
+ }
+ span.end();
+};
+
+export const getTracedCreateClient = (tracer: Tracer, original: Function) => {
+ return function createClientTrace(this: redisTypes.RedisClient) {
+ const client: redisTypes.RedisClient = original.apply(this, arguments);
+ return tracer.bind(client);
+ };
+};
+
+export const getTracedCreateStreamTrace = (
+ tracer: Tracer,
+ original: Function
+) => {
+ return function create_stream_trace(this: RedisPluginStreamTypes) {
+ if (!this.stream) {
+ Object.defineProperty(this, 'stream', {
+ get() {
+ return this._patched_redis_stream;
+ },
+ set(val: EventEmitter) {
+ tracer.bind(val);
+ this._patched_redis_stream = val;
+ },
+ });
+ }
+ return original.apply(this, arguments);
+ };
+};
+
+export const getTracedInternalSendCommand = (
+ tracer: Tracer,
+ original: Function
+) => {
+ return function internal_send_command_trace(
+ this: redisTypes.RedisClient & RedisPluginClientTypes,
+ cmd?: RedisCommand
+ ) {
+ const parentSpan = tracer.getCurrentSpan();
+
+ // New versions of redis (2.4+) use a single options object
+ // instead of named arguments
+ if (arguments.length === 1 && typeof cmd === 'object') {
+ const span = tracer.startSpan(`${RedisPlugin.COMPONENT}-${cmd.command}`, {
+ kind: SpanKind.CLIENT,
+ parent: parentSpan || undefined,
+ attributes: {
+ [AttributeNames.COMPONENT]: RedisPlugin.COMPONENT,
+ [AttributeNames.DB_STATEMENT]: cmd.command,
+ },
+ });
+
+ // Set attributes for not explicitly typed RedisPluginClientTypes
+ if (this.options) {
+ span.setAttributes({
+ [AttributeNames.PEER_HOSTNAME]: this.options.host,
+ [AttributeNames.PEER_PORT]: this.options.port,
+ });
+ }
+ if (this.address) {
+ span.setAttribute(
+ AttributeNames.PEER_ADDRESS,
+ `redis://${this.address}`
+ );
+ }
+
+ const originalCallback = arguments[0].callback;
+ if (originalCallback) {
+ (arguments[0] as RedisCommand).callback = function callback(
+ this: unknown,
+ err: Error | null,
+ _reply: T
+ ) {
+ endSpan(span, err);
+ return originalCallback.apply(this, arguments);
+ };
+ }
+ try {
+ // Span will be ended in callback
+ return original.apply(this, arguments);
+ } catch (rethrow) {
+ endSpan(span, rethrow);
+ throw rethrow; // rethrow after ending span
+ }
+ }
+
+ // We don't know how to trace this call, so don't start/stop a span
+ return original.apply(this, arguments);
+ };
+};
diff --git a/packages/opentelemetry-plugin-redis/test/assertionUtils.ts b/packages/opentelemetry-plugin-redis/test/assertionUtils.ts
new file mode 100644
index 00000000000..fdb517c7a09
--- /dev/null
+++ b/packages/opentelemetry-plugin-redis/test/assertionUtils.ts
@@ -0,0 +1,76 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ SpanKind,
+ Attributes,
+ Event,
+ Span,
+ Status,
+} from '@opentelemetry/types';
+import * as assert from 'assert';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import {
+ hrTimeToMilliseconds,
+ hrTimeToMicroseconds,
+} from '@opentelemetry/core';
+
+export const assertSpan = (
+ span: ReadableSpan,
+ kind: SpanKind,
+ attributes: Attributes,
+ events: Event[],
+ status: Status
+) => {
+ assert.strictEqual(span.spanContext.traceId.length, 32);
+ assert.strictEqual(span.spanContext.spanId.length, 16);
+ assert.strictEqual(span.kind, kind);
+
+ assert.ok(span.endTime);
+ assert.strictEqual(span.links.length, 0);
+
+ assert.ok(
+ hrTimeToMicroseconds(span.startTime) < hrTimeToMicroseconds(span.endTime)
+ );
+ assert.ok(hrTimeToMilliseconds(span.endTime) > 0);
+
+ // attributes
+ assert.deepStrictEqual(span.attributes, attributes);
+
+ // events
+ assert.deepStrictEqual(span.events, events);
+
+ assert.strictEqual(span.status.code, status.code);
+ if (status.message) {
+ assert.strictEqual(span.status.message, status.message);
+ }
+};
+
+// Check if childSpan was propagated from parentSpan
+export const assertPropagation = (
+ childSpan: ReadableSpan,
+ parentSpan: Span
+) => {
+ const targetSpanContext = childSpan.spanContext;
+ const sourceSpanContext = parentSpan.context();
+ assert.strictEqual(targetSpanContext.traceId, sourceSpanContext.traceId);
+ assert.strictEqual(childSpan.parentSpanId, sourceSpanContext.spanId);
+ assert.strictEqual(
+ targetSpanContext.traceFlags,
+ sourceSpanContext.traceFlags
+ );
+ assert.notStrictEqual(targetSpanContext.spanId, sourceSpanContext.spanId);
+};
diff --git a/packages/opentelemetry-plugin-redis/test/redis.test.ts b/packages/opentelemetry-plugin-redis/test/redis.test.ts
new file mode 100644
index 00000000000..72d6730a69a
--- /dev/null
+++ b/packages/opentelemetry-plugin-redis/test/redis.test.ts
@@ -0,0 +1,215 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as assert from 'assert';
+import {
+ InMemorySpanExporter,
+ SimpleSpanProcessor,
+} from '@opentelemetry/tracing';
+import { NodeTracer } from '@opentelemetry/node';
+import { plugin, RedisPlugin } from '../src';
+import * as redisTypes from 'redis';
+import { NoopLogger } from '@opentelemetry/core';
+import * as dockerUtils from './testUtils';
+import * as assertionUtils from './assertionUtils';
+import { SpanKind, Status, CanonicalCode } from '@opentelemetry/types';
+import { AttributeNames } from '../src/enums';
+
+const memoryExporter = new InMemorySpanExporter();
+
+const CONFIG = {
+ host: process.env.OPENTELEMETRY_REDIS_HOST || 'localhost',
+ port: process.env.OPENTELEMETRY_REDIS_PORT || '63790',
+};
+
+const URL = `redis://${CONFIG.host}:${CONFIG.port}`;
+
+const DEFAULT_ATTRIBUTES = {
+ [AttributeNames.COMPONENT]: RedisPlugin.COMPONENT,
+ [AttributeNames.PEER_HOSTNAME]: CONFIG.host,
+ [AttributeNames.PEER_PORT]: CONFIG.port,
+ [AttributeNames.PEER_ADDRESS]: URL,
+};
+
+const okStatus: Status = {
+ code: CanonicalCode.OK,
+};
+
+describe('redis@2.x', () => {
+ const tracer = new NodeTracer();
+ let redis: typeof redisTypes;
+ const shouldTestLocal = process.env.RUN_REDIS_TESTS_LOCAL;
+ const shouldTest = process.env.RUN_REDIS_TESTS || shouldTestLocal;
+
+ before(function() {
+ // needs to be "function" to have MochaContext "this" scope
+ if (!shouldTest) {
+ // this.skip() workaround
+ // https://github.com/mochajs/mocha/issues/2683#issuecomment-375629901
+ this.test!.parent!.pending = true;
+ this.skip();
+ }
+
+ if (shouldTestLocal) {
+ dockerUtils.startDocker();
+ }
+
+ redis = require('redis');
+ tracer.addSpanProcessor(new SimpleSpanProcessor(memoryExporter));
+ plugin.enable(redis, tracer, new NoopLogger());
+ });
+
+ after(() => {
+ if (shouldTestLocal) {
+ dockerUtils.cleanUpDocker();
+ }
+ });
+
+ it('should have correct module name', () => {
+ assert.strictEqual(plugin.moduleName, RedisPlugin.COMPONENT);
+ });
+
+ describe('#createClient()', () => {
+ it('should propagate the current span to event handlers', done => {
+ const span = tracer.startSpan('test span');
+ let client: redisTypes.RedisClient;
+ const readyHandler = () => {
+ assert.strictEqual(tracer.getCurrentSpan(), span);
+ client.quit(done);
+ };
+ const errorHandler = (err: Error) => {
+ assert.ifError(err);
+ client.quit(done);
+ };
+
+ tracer.withSpan(span, () => {
+ client = redis.createClient(URL);
+ client.on('ready', readyHandler);
+ client.on('error', errorHandler);
+ });
+ });
+ });
+
+ describe('#send_internal_message()', () => {
+ let client: redisTypes.RedisClient;
+
+ const REDIS_OPERATIONS: Array<{
+ description: string;
+ command: string;
+ method: (cb: redisTypes.Callback) => unknown;
+ }> = [
+ {
+ description: 'insert',
+ command: 'hset',
+ method: (cb: redisTypes.Callback) =>
+ client.hset('hash', 'random', 'random', cb),
+ },
+ {
+ description: 'get',
+ command: 'get',
+ method: (cb: redisTypes.Callback) => client.get('test', cb),
+ },
+ {
+ description: 'delete',
+ command: 'del',
+ method: (cb: redisTypes.Callback) => client.del('test', cb),
+ },
+ ];
+
+ before(done => {
+ client = redis.createClient(URL);
+ client.on('error', err => {
+ done(err);
+ });
+ client.on('ready', done);
+ });
+
+ beforeEach(done => {
+ client.set('test', 'data', () => {
+ memoryExporter.reset();
+ done();
+ });
+ });
+
+ after(done => {
+ client.quit(done);
+ });
+
+ afterEach(done => {
+ client.del('hash', () => {
+ memoryExporter.reset();
+ done();
+ });
+ });
+
+ describe('Instrumenting query operations', () => {
+ REDIS_OPERATIONS.forEach(operation => {
+ it(`should create a child span for ${operation.description}`, done => {
+ const attributes = {
+ ...DEFAULT_ATTRIBUTES,
+ [AttributeNames.DB_STATEMENT]: operation.command,
+ };
+ const span = tracer.startSpan('test span');
+ tracer.withSpan(span, () => {
+ operation.method((err, _result) => {
+ assert.ifError(err);
+ assert.strictEqual(memoryExporter.getFinishedSpans().length, 1);
+ span.end();
+ const endedSpans = memoryExporter.getFinishedSpans();
+ assert.strictEqual(endedSpans.length, 2);
+ assert.strictEqual(
+ endedSpans[0].name,
+ `redis-${operation.command}`
+ );
+ assertionUtils.assertSpan(
+ endedSpans[0],
+ SpanKind.CLIENT,
+ attributes,
+ [],
+ okStatus
+ );
+ assertionUtils.assertPropagation(endedSpans[0], span);
+ done();
+ });
+ });
+ });
+ });
+ });
+
+ describe('Removing instrumentation', () => {
+ before(() => {
+ plugin.disable();
+ });
+
+ REDIS_OPERATIONS.forEach(operation => {
+ it(`should not create a child span for ${operation.description}`, done => {
+ const span = tracer.startSpan('test span');
+ tracer.withSpan(span, () => {
+ operation.method((err, _) => {
+ assert.ifError(err);
+ assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);
+ span.end();
+ const endedSpans = memoryExporter.getFinishedSpans();
+ assert.strictEqual(endedSpans.length, 1);
+ assert.strictEqual(endedSpans[0], span);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/packages/opentelemetry-plugin-redis/test/testUtils.ts b/packages/opentelemetry-plugin-redis/test/testUtils.ts
new file mode 100644
index 00000000000..a40b0b4aab0
--- /dev/null
+++ b/packages/opentelemetry-plugin-redis/test/testUtils.ts
@@ -0,0 +1,54 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as childProcess from 'child_process';
+export function startDocker() {
+ const tasks = [
+ run('docker run -d -p 63790:6379 --name otjsredis redis:alpine'),
+ ];
+
+ for (let i = 0; i < tasks.length; i++) {
+ const task = tasks[i];
+ if (task && task.code !== 0) {
+ console.error('Failed to start container!');
+ console.error(task.output);
+ return false;
+ }
+ }
+ return true;
+}
+
+export function cleanUpDocker() {
+ run('docker stop otjsredis');
+ run('docker rm otjsredis');
+}
+
+function run(cmd: string) {
+ try {
+ const proc = childProcess.spawnSync(cmd, {
+ shell: true,
+ });
+ return {
+ code: proc.status,
+ output: proc.output
+ .map(v => String.fromCharCode.apply(null, v as any))
+ .join(''),
+ };
+ } catch (e) {
+ console.log(e);
+ return;
+ }
+}