Skip to content

Commit 64c1b7a

Browse files
committed
fix(redis): use new tracer after setTracerProvider
1 parent e0858f9 commit 64c1b7a

File tree

3 files changed

+162
-144
lines changed

3 files changed

+162
-144
lines changed

plugins/node/opentelemetry-instrumentation-redis/src/instrumentation.ts

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,28 @@ import {
1818
isWrapped,
1919
InstrumentationBase,
2020
InstrumentationNodeModuleDefinition,
21+
safeExecuteInTheMiddle,
2122
} from '@opentelemetry/instrumentation';
2223
import {
24+
endSpan,
2325
getTracedCreateClient,
2426
getTracedCreateStreamTrace,
25-
getTracedInternalSendCommand,
2627
} from './utils';
27-
import { RedisInstrumentationConfig } from './types';
28+
import { RedisCommand, RedisInstrumentationConfig } from './types';
2829
/** @knipignore */
2930
import { PACKAGE_NAME, PACKAGE_VERSION } from './version';
30-
31+
import { RedisPluginClientTypes } from './internal-types';
32+
import { SpanKind, context, trace } from '@opentelemetry/api';
33+
import {
34+
DBSYSTEMVALUES_REDIS,
35+
SEMATTRS_DB_CONNECTION_STRING,
36+
SEMATTRS_DB_STATEMENT,
37+
SEMATTRS_DB_SYSTEM,
38+
SEMATTRS_NET_PEER_NAME,
39+
SEMATTRS_NET_PEER_PORT,
40+
} from '@opentelemetry/semantic-conventions';
41+
import { defaultDbStatementSerializer } from '@opentelemetry/redis-common';
42+
3143
const DEFAULT_CONFIG: RedisInstrumentationConfig = {
3244
requireParentSpan: false,
3345
};
@@ -96,28 +108,110 @@ export class RedisInstrumentation extends InstrumentationBase<RedisInstrumentati
96108
),
97109
];
98110
}
111+
99112
/**
100113
* Patch internal_send_command(...) to trace requests
101114
*/
102115
private _getPatchInternalSendCommand() {
103-
const tracer = this.tracer;
104-
const config = this.getConfig();
116+
const instrumentation = this;
105117
return function internal_send_command(original: Function) {
106-
return getTracedInternalSendCommand(tracer, original, config);
118+
return function internal_send_command_trace(
119+
this: RedisPluginClientTypes,
120+
cmd?: RedisCommand
121+
) {
122+
// Versions of redis (2.4+) use a single options object
123+
// instead of named arguments
124+
if (arguments.length !== 1 || typeof cmd !== 'object') {
125+
// We don't know how to trace this call, so don't start/stop a span
126+
return original.apply(this, arguments);
127+
}
128+
129+
const config = instrumentation.getConfig();
130+
131+
const hasNoParentSpan = trace.getSpan(context.active()) === undefined;
132+
if (config.requireParentSpan === true && hasNoParentSpan) {
133+
return original.apply(this, arguments);
134+
}
135+
136+
const dbStatementSerializer =
137+
config?.dbStatementSerializer || defaultDbStatementSerializer;
138+
const span = instrumentation.tracer.startSpan(
139+
`${RedisInstrumentation.COMPONENT}-${cmd.command}`,
140+
{
141+
kind: SpanKind.CLIENT,
142+
attributes: {
143+
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_REDIS,
144+
[SEMATTRS_DB_STATEMENT]: dbStatementSerializer(cmd.command, cmd.args),
145+
},
146+
}
147+
);
148+
149+
// Set attributes for not explicitly typed RedisPluginClientTypes
150+
if (this.connection_options) {
151+
span.setAttributes({
152+
[SEMATTRS_NET_PEER_NAME]: this.connection_options.host,
153+
[SEMATTRS_NET_PEER_PORT]: this.connection_options.port,
154+
});
155+
}
156+
if (this.address) {
157+
span.setAttribute(
158+
SEMATTRS_DB_CONNECTION_STRING,
159+
`redis://${this.address}`
160+
);
161+
}
162+
163+
const originalCallback = arguments[0].callback;
164+
if (originalCallback) {
165+
const originalContext = context.active();
166+
(arguments[0] as RedisCommand).callback = function callback<T>(
167+
this: unknown,
168+
err: Error | null,
169+
reply: T
170+
) {
171+
if (config?.responseHook) {
172+
const responseHook = config.responseHook;
173+
safeExecuteInTheMiddle(
174+
() => {
175+
responseHook(span, cmd.command, cmd.args, reply);
176+
},
177+
err => {
178+
if (err) {
179+
instrumentation._diag.error('Error executing responseHook', err);
180+
}
181+
},
182+
true
183+
);
184+
}
185+
186+
endSpan(span, err);
187+
return context.with(
188+
originalContext,
189+
originalCallback,
190+
this,
191+
...arguments
192+
);
193+
};
194+
}
195+
try {
196+
// Span will be ended in callback
197+
return original.apply(this, arguments);
198+
} catch (rethrow: any) {
199+
endSpan(span, rethrow);
200+
throw rethrow; // rethrow after ending span
201+
}
202+
};
107203
};
108204
}
109205

110206
private _getPatchCreateClient() {
111-
const tracer = this.tracer;
112207
return function createClient(original: Function) {
113-
return getTracedCreateClient(tracer, original);
208+
return getTracedCreateClient(original);
114209
};
115210
}
116211

117212
private _getPatchCreateStream() {
118-
const tracer = this.tracer;
119213
return function createReadStream(original: Function) {
120-
return getTracedCreateStreamTrace(tracer, original);
214+
return getTracedCreateStreamTrace(original);
121215
};
122216
}
123217
}

plugins/node/opentelemetry-instrumentation-redis/src/utils.ts

Lines changed: 2 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,12 @@
1717
import type * as redisTypes from 'redis';
1818
import {
1919
context,
20-
Tracer,
21-
SpanKind,
2220
Span,
2321
SpanStatusCode,
24-
trace,
25-
diag,
2622
} from '@opentelemetry/api';
27-
import { RedisCommand, RedisInstrumentationConfig } from './types';
2823
import { EventEmitter } from 'events';
29-
import { RedisInstrumentation } from './';
30-
import {
31-
DBSYSTEMVALUES_REDIS,
32-
SEMATTRS_DB_CONNECTION_STRING,
33-
SEMATTRS_DB_STATEMENT,
34-
SEMATTRS_DB_SYSTEM,
35-
SEMATTRS_NET_PEER_NAME,
36-
SEMATTRS_NET_PEER_PORT,
37-
} from '@opentelemetry/semantic-conventions';
38-
import { safeExecuteInTheMiddle } from '@opentelemetry/instrumentation';
39-
import { RedisPluginClientTypes } from './internal-types';
40-
import { defaultDbStatementSerializer } from '@opentelemetry/redis-common';
4124

42-
const endSpan = (span: Span, err?: Error | null) => {
25+
export const endSpan = (span: Span, err?: Error | null) => {
4326
if (err) {
4427
span.setStatus({
4528
code: SpanStatusCode.ERROR,
@@ -49,15 +32,14 @@ const endSpan = (span: Span, err?: Error | null) => {
4932
span.end();
5033
};
5134

52-
export const getTracedCreateClient = (tracer: Tracer, original: Function) => {
35+
export const getTracedCreateClient = (original: Function) => {
5336
return function createClientTrace(this: redisTypes.RedisClient) {
5437
const client: redisTypes.RedisClient = original.apply(this, arguments);
5538
return context.bind(context.active(), client);
5639
};
5740
};
5841

5942
export const getTracedCreateStreamTrace = (
60-
tracer: Tracer,
6143
original: Function
6244
) => {
6345
return function create_stream_trace(this: redisTypes.RedisClient) {
@@ -75,93 +57,3 @@ export const getTracedCreateStreamTrace = (
7557
return original.apply(this, arguments);
7658
};
7759
};
78-
79-
export const getTracedInternalSendCommand = (
80-
tracer: Tracer,
81-
original: Function,
82-
config?: RedisInstrumentationConfig
83-
) => {
84-
return function internal_send_command_trace(
85-
this: RedisPluginClientTypes,
86-
cmd?: RedisCommand
87-
) {
88-
// New versions of redis (2.4+) use a single options object
89-
// instead of named arguments
90-
if (arguments.length !== 1 || typeof cmd !== 'object') {
91-
// We don't know how to trace this call, so don't start/stop a span
92-
return original.apply(this, arguments);
93-
}
94-
95-
const hasNoParentSpan = trace.getSpan(context.active()) === undefined;
96-
if (config?.requireParentSpan === true && hasNoParentSpan) {
97-
return original.apply(this, arguments);
98-
}
99-
100-
const dbStatementSerializer =
101-
config?.dbStatementSerializer || defaultDbStatementSerializer;
102-
const span = tracer.startSpan(
103-
`${RedisInstrumentation.COMPONENT}-${cmd.command}`,
104-
{
105-
kind: SpanKind.CLIENT,
106-
attributes: {
107-
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_REDIS,
108-
[SEMATTRS_DB_STATEMENT]: dbStatementSerializer(cmd.command, cmd.args),
109-
},
110-
}
111-
);
112-
113-
// Set attributes for not explicitly typed RedisPluginClientTypes
114-
if (this.connection_options) {
115-
span.setAttributes({
116-
[SEMATTRS_NET_PEER_NAME]: this.connection_options.host,
117-
[SEMATTRS_NET_PEER_PORT]: this.connection_options.port,
118-
});
119-
}
120-
if (this.address) {
121-
span.setAttribute(
122-
SEMATTRS_DB_CONNECTION_STRING,
123-
`redis://${this.address}`
124-
);
125-
}
126-
127-
const originalCallback = arguments[0].callback;
128-
if (originalCallback) {
129-
const originalContext = context.active();
130-
(arguments[0] as RedisCommand).callback = function callback<T>(
131-
this: unknown,
132-
err: Error | null,
133-
reply: T
134-
) {
135-
if (config?.responseHook) {
136-
const responseHook = config.responseHook;
137-
safeExecuteInTheMiddle(
138-
() => {
139-
responseHook(span, cmd.command, cmd.args, reply);
140-
},
141-
err => {
142-
if (err) {
143-
diag.error('Error executing responseHook', err);
144-
}
145-
},
146-
true
147-
);
148-
}
149-
150-
endSpan(span, err);
151-
return context.with(
152-
originalContext,
153-
originalCallback,
154-
this,
155-
...arguments
156-
);
157-
};
158-
}
159-
try {
160-
// Span will be ended in callback
161-
return original.apply(this, arguments);
162-
} catch (rethrow: any) {
163-
endSpan(span, rethrow);
164-
throw rethrow; // rethrow after ending span
165-
}
166-
};
167-
};

plugins/node/opentelemetry-instrumentation-redis/test/redis.test.ts

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ describe('redis@2.x', () => {
8080
beforeEach(() => {
8181
contextManager = new AsyncHooksContextManager().enable();
8282
context.setGlobalContextManager(contextManager);
83+
// set the default tracer provider before each test
84+
// specific ones can override it to assert certain things
85+
instrumentation.setTracerProvider(provider);
8386
});
8487

8588
afterEach(() => {
@@ -141,30 +144,30 @@ describe('redis@2.x', () => {
141144
expectedDbStatement: string;
142145
method: (cb: redisTypes.Callback<unknown>) => unknown;
143146
}> = [
144-
{
145-
description: 'insert',
146-
command: 'hset',
147-
args: ['hash', 'random', 'random'],
148-
expectedDbStatement: 'hash random [1 other arguments]',
149-
method: (cb: redisTypes.Callback<number>) =>
150-
client.hset('hash', 'random', 'random', cb),
151-
},
152-
{
153-
description: 'get',
154-
command: 'get',
155-
args: ['test'],
156-
expectedDbStatement: 'test',
157-
method: (cb: redisTypes.Callback<string | null>) =>
158-
client.get('test', cb),
159-
},
160-
{
161-
description: 'delete',
162-
command: 'del',
163-
args: ['test'],
164-
expectedDbStatement: 'test',
165-
method: (cb: redisTypes.Callback<number>) => client.del('test', cb),
166-
},
167-
];
147+
{
148+
description: 'insert',
149+
command: 'hset',
150+
args: ['hash', 'random', 'random'],
151+
expectedDbStatement: 'hash random [1 other arguments]',
152+
method: (cb: redisTypes.Callback<number>) =>
153+
client.hset('hash', 'random', 'random', cb),
154+
},
155+
{
156+
description: 'get',
157+
command: 'get',
158+
args: ['test'],
159+
expectedDbStatement: 'test',
160+
method: (cb: redisTypes.Callback<string | null>) =>
161+
client.get('test', cb),
162+
},
163+
{
164+
description: 'delete',
165+
command: 'del',
166+
args: ['test'],
167+
expectedDbStatement: 'test',
168+
method: (cb: redisTypes.Callback<number>) => client.del('test', cb),
169+
},
170+
];
168171

169172
before(done => {
170173
client = redis.createClient(URL);
@@ -389,5 +392,34 @@ describe('redis@2.x', () => {
389392
});
390393
});
391394
});
395+
396+
describe('setTracerProvider', () => {
397+
before(() => {
398+
instrumentation.disable();
399+
instrumentation.setConfig({});
400+
instrumentation.enable();
401+
});
402+
403+
it('should use new tracer provider after setTracerProvider is called', done => {
404+
const testSpecificMemoryExporter = new InMemorySpanExporter();
405+
const spanProcessor = new SimpleSpanProcessor(testSpecificMemoryExporter);
406+
const tracerProvider = new NodeTracerProvider({
407+
spanProcessors: [spanProcessor],
408+
});
409+
410+
// key point of this test, setting new tracer provider and making sure
411+
// new spans use it.
412+
instrumentation.setTracerProvider(tracerProvider);
413+
414+
client.set('foo', 'bar-value-from-test', (err) => {
415+
assert.ifError(err);
416+
// assert that the span was exported by the new tracer provider
417+
// which is using the test specific span processor
418+
const spans = testSpecificMemoryExporter.getFinishedSpans();
419+
assert.strictEqual(spans.length, 1);
420+
done();
421+
})
422+
});
423+
});
392424
});
393425
});

0 commit comments

Comments
 (0)