Skip to content
This repository was archived by the owner on Oct 31, 2024. It is now read-only.

Commit 2af9ef2

Browse files
author
Nir Hadassi
authored
feat: add moduleVersionAttributeName config to all plugins (#71)
1 parent 446b359 commit 2af9ef2

File tree

20 files changed

+260
-58
lines changed

20 files changed

+260
-58
lines changed

packages/instrumentation-aws-sdk/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ aws-sdk instrumentation has few options available to choose from. You can set th
4343
| `preRequestHook` | `AwsSdkRequestCustomAttributeFunction` | Hook called before request send, which allow to add custom attributes to span. |
4444
| `responseHook` | `AwsSdkResponseCustomAttributeFunction` | Hook for adding custom attributes when response is received from aws. |
4545
| `sqsProcessHook` | `AwsSdkSqsProcessCustomAttributeFunction` | Hook called after starting sqs `process` span (for each sqs received message), which allow to add custom attributes to it. |
46-
| `suppressInternalInstrumentation` | boolean | Most aws operation use http request under the hood. If http instrumentation is enabled, each aws operation will also create an http/s child describing the communication with amazon servers. Setting the suppressInternalInstrumentation` config value to `true` will cause the instrumentation to suppress instrumentation of underlying operations, effectively causing those http spans to be non-recordable. |
46+
| `suppressInternalInstrumentation` | `boolean` | Most aws operation use http requests under the hood. Set this to `true` to hide all underlying http spans. |
47+
| `moduleVersionAttributeName` | `string` | If passed, a span attribute will be added to all spans with key of the provided `moduleVersionAttributeName` and value of the patched module version |
4748

4849

4950

packages/instrumentation-aws-sdk/src/aws-sdk.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,24 @@ import { AwsSdkInstrumentationConfig } from './types';
1616
import { VERSION } from './version';
1717
import {
1818
InstrumentationBase,
19-
InstrumentationConfig,
2019
InstrumentationModuleDefinition,
2120
InstrumentationNodeModuleDefinition,
2221
isWrapped,
2322
safeExecuteInTheMiddle,
2423
} from '@opentelemetry/instrumentation';
2524

26-
type Config = InstrumentationConfig & AwsSdkInstrumentationConfig;
27-
2825
export class AwsInstrumentation extends InstrumentationBase<typeof AWS> {
2926
static readonly component = 'aws-sdk';
30-
protected _config!: Config;
27+
protected _config!: AwsSdkInstrumentationConfig;
3128
private REQUEST_SPAN_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.span');
3229
private servicesExtensions: ServicesExtensions;
30+
private moduleVersion: string;
3331

34-
constructor(config: Config = {}) {
32+
constructor(config: AwsSdkInstrumentationConfig = {}) {
3533
super('opentelemetry-instrumentation-aws-sdk', VERSION, Object.assign({}, config));
3634
}
3735

38-
setConfig(config: Config = {}) {
36+
setConfig(config: AwsSdkInstrumentationConfig = {}) {
3937
this._config = Object.assign({}, config);
4038
}
4139

@@ -49,7 +47,8 @@ export class AwsInstrumentation extends InstrumentationBase<typeof AWS> {
4947
return module;
5048
}
5149

52-
protected patch(moduleExports: typeof AWS) {
50+
protected patch(moduleExports: typeof AWS, moduleVersion: string) {
51+
this.moduleVersion = moduleVersion;
5352
this.servicesExtensions = new ServicesExtensions(this.tracer, this._config);
5453

5554
diag.debug(`applying patch to ${AwsInstrumentation.component}`);
@@ -103,6 +102,10 @@ export class AwsInstrumentation extends InstrumentationBase<typeof AWS> {
103102
},
104103
});
105104

105+
if (this._config.moduleVersionAttributeName) {
106+
newSpan.setAttribute(this._config.moduleVersionAttributeName, this.moduleVersion);
107+
}
108+
106109
request[this.REQUEST_SPAN_KEY] = newSpan;
107110
return newSpan;
108111
}

packages/instrumentation-aws-sdk/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,10 @@ export interface AwsSdkInstrumentationConfig extends InstrumentationConfig {
3333
* effectively causing those http spans to be non-recordable.
3434
*/
3535
suppressInternalInstrumentation?: boolean;
36+
37+
/**
38+
* If passed, a span attribute will be added to all spans with key of the provided "moduleVersionAttributeName"
39+
* and value of the module version.
40+
*/
41+
moduleVersionAttributeName?: string;
3642
}

packages/instrumentation-aws-sdk/test/aws-sdk.spec.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ describe('instrumentation-aws-sdk', () => {
267267
it('responseHook called and add response attribute to span', (done) => {
268268
mockAwsSend(responseMockSuccess, 'data returned from operation');
269269
const config = {
270-
enabled: true,
271270
responseHook: (span: Span, response: any) => {
272271
span.setAttribute('attribute from response hook', response['data']);
273272
},
@@ -312,7 +311,6 @@ describe('instrumentation-aws-sdk', () => {
312311
it('suppressInternalInstrumentation set to true with promise()', async () => {
313312
mockAwsSend(responseMockSuccess, 'data returned from operation', true);
314313
const config = {
315-
enabled: true,
316314
suppressInternalInstrumentation: true,
317315
};
318316

@@ -326,5 +324,25 @@ describe('instrumentation-aws-sdk', () => {
326324
const awsSpans = getAwsSpans();
327325
expect(awsSpans.length).toBe(1);
328326
});
327+
328+
it('setting moduleVersionAttributeName is adding module version', async () => {
329+
mockAwsSend(responseMockSuccess, 'data returned from operation', true);
330+
const config = {
331+
moduleVersionAttributeName: 'module.version',
332+
suppressInternalInstrumentation: true
333+
};
334+
335+
instrumentation.disable();
336+
instrumentation.setConfig(config);
337+
instrumentation.enable();
338+
339+
const s3 = new AWS.S3();
340+
341+
await s3.createBucket({ Bucket: 'aws-test-bucket' }).promise();
342+
const awsSpans = getAwsSpans();
343+
expect(awsSpans.length).toBe(1);
344+
345+
expect(awsSpans[0].attributes['module.version']).toMatch(/\d{1,4}\.\d{1,4}\.\d{1,5}.*/);
346+
});
329347
});
330348
});

packages/instrumentation-kafkajs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ kafkajs instrumentation has few options available to choose from. You can set th
4343
| -------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------- |
4444
| `producerHook` | `KafkaProducerCustomAttributeFunction` | Hook called before producer message is sent, which allow to add custom attributes to span. |
4545
| `consumerHook` | `KafkaConsumerCustomAttributeFunction` | Hook called before consumer message is processed, which allow to add custom attributes to span. |
46+
| `moduleVersionAttributeName` | `string` | If passed, a span attribute will be added to all spans with key of the provided `moduleVersionAttributeName` and value of the patched module version |
4647

4748
---
4849

packages/instrumentation-kafkajs/src/kafkajs.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,22 @@ import { VERSION } from './version';
1919
import { bufferTextMapGetter } from './propagtor';
2020
import {
2121
InstrumentationBase,
22-
InstrumentationConfig,
2322
InstrumentationModuleDefinition,
2423
InstrumentationNodeModuleDefinition,
2524
safeExecuteInTheMiddle,
2625
isWrapped,
2726
} from '@opentelemetry/instrumentation';
2827

29-
type Config = InstrumentationConfig & KafkaJsInstrumentationConfig;
30-
3128
export class KafkaJsInstrumentation extends InstrumentationBase<typeof kafkaJs> {
3229
static readonly component = 'kafkajs';
33-
protected _config!: Config;
30+
protected _config!: KafkaJsInstrumentationConfig;
31+
private moduleVersion: string;
3432

35-
constructor(config: Config = {}) {
33+
constructor(config: KafkaJsInstrumentationConfig = {}) {
3634
super('opentelemetry-instrumentation-kafkajs', VERSION, Object.assign({}, config));
3735
}
3836

39-
setConfig(config: Config = {}) {
37+
setConfig(config: KafkaJsInstrumentationConfig = {}) {
4038
this._config = Object.assign({}, config);
4139
}
4240

@@ -50,8 +48,9 @@ export class KafkaJsInstrumentation extends InstrumentationBase<typeof kafkaJs>
5048
return module;
5149
}
5250

53-
protected patch(moduleExports: typeof kafkaJs) {
51+
protected patch(moduleExports: typeof kafkaJs, moduleVersion: string) {
5452
diag.debug('kafkajs instrumentation: applying patch');
53+
this.moduleVersion = moduleVersion
5554

5655
this.unpatch(moduleExports);
5756
this._wrap(moduleExports?.Kafka?.prototype, 'producer', this._getProducerPatch.bind(this));
@@ -266,6 +265,10 @@ export class KafkaJsInstrumentation extends InstrumentationBase<typeof kafkaJs>
266265
context
267266
);
268267

268+
if (this._config.moduleVersionAttributeName) {
269+
span.setAttribute(this._config.moduleVersionAttributeName, this.moduleVersion);
270+
}
271+
269272
if (this._config?.consumerHook && message) {
270273
safeExecuteInTheMiddle(
271274
() => this._config.consumerHook!(span, topic, message),
@@ -289,6 +292,10 @@ export class KafkaJsInstrumentation extends InstrumentationBase<typeof kafkaJs>
289292
},
290293
});
291294

295+
if (this._config.moduleVersionAttributeName) {
296+
span.setAttribute(this._config.moduleVersionAttributeName, this.moduleVersion);
297+
}
298+
292299
message.headers = message.headers ?? {};
293300
propagation.inject(setSpan(context.active(), span), message.headers);
294301

packages/instrumentation-kafkajs/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,10 @@ export interface KafkaJsInstrumentationConfig extends InstrumentationConfig {
1616

1717
/** hook for adding custom attributes before consumer message is processed */
1818
consumerHook?: KafkaConsumerCustomAttributeFunction;
19+
20+
/**
21+
* If passed, a span attribute will be added to all spans with key of the provided "moduleVersionAttributeName"
22+
* and value of the module version.
23+
*/
24+
moduleVersionAttributeName?: string;
1925
}

packages/instrumentation-kafkajs/test/kafkajs.spec.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'mocha';
2-
import { KafkaJsInstrumentation } from '../src';
2+
import { KafkaJsInstrumentation, KafkaJsInstrumentationConfig } from '../src';
33
import { InMemorySpanExporter, SimpleSpanProcessor, ReadableSpan } from '@opentelemetry/tracing';
44
import { NodeTracerProvider } from '@opentelemetry/node';
55
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
@@ -296,8 +296,7 @@ describe('instrumentation-kafkajs', () => {
296296
beforeEach(async () => {
297297
patchProducerSend(async (): Promise<RecordMetadata[]> => []);
298298

299-
const config = {
300-
enabled: true,
299+
const config: KafkaJsInstrumentationConfig = {
301300
producerHook: (span: Span, topic: string, message: Message) => {
302301
span.setAttribute('attribute-from-hook', message.value as string);
303302
},
@@ -329,8 +328,7 @@ describe('instrumentation-kafkajs', () => {
329328
beforeEach(async () => {
330329
patchProducerSend(async (): Promise<RecordMetadata[]> => []);
331330

332-
const config = {
333-
enabled: true,
331+
const config: KafkaJsInstrumentationConfig = {
334332
producerHook: (span: Span, topic: string, message: Message) => {
335333
throw new Error('error thrown from producer hook');
336334
},
@@ -357,6 +355,30 @@ describe('instrumentation-kafkajs', () => {
357355
expect(span.status.code).toStrictEqual(SpanStatusCode.UNSET);
358356
});
359357
});
358+
359+
describe('moduleVersionAttributeName config', () => {
360+
beforeEach(async () => {
361+
const config: KafkaJsInstrumentationConfig = {
362+
moduleVersionAttributeName: 'module.version'
363+
};
364+
instrumentation.disable();
365+
instrumentation.setConfig(config);
366+
instrumentation.enable();
367+
producer = kafka.producer();
368+
});
369+
370+
it('adds module version to producer span', async () => {
371+
await producer.send({
372+
topic: 'topic-name-1',
373+
messages: [{ value: 'testing message content' }],
374+
});
375+
376+
const spans = memoryExporter.getFinishedSpans();
377+
expect(spans.length).toBe(1);
378+
const span = spans[0];
379+
expect(span.attributes['module.version']).toMatch(/\d{1,4}\.\d{1,4}\.\d{1,5}.*/);
380+
});
381+
});
360382
});
361383

362384
describe('consumer', () => {
@@ -426,8 +448,7 @@ describe('instrumentation-kafkajs', () => {
426448

427449
describe('successful consumer hook', () => {
428450
beforeEach(async () => {
429-
const config = {
430-
enabled: true,
451+
const config: KafkaJsInstrumentationConfig = {
431452
consumerHook: (span: Span, topic: string, message: Message) => {
432453
span.setAttribute('attribute key from hook', message.value.toString());
433454
},
@@ -456,8 +477,7 @@ describe('instrumentation-kafkajs', () => {
456477

457478
describe('throwing consumer hook', () => {
458479
beforeEach(async () => {
459-
const config = {
460-
enabled: true,
480+
const config: KafkaJsInstrumentationConfig = {
461481
consumerHook: (span: Span, topic: string, message: Message) => {
462482
throw new Error('error thrown from consumer hook');
463483
},
@@ -604,6 +624,33 @@ describe('instrumentation-kafkajs', () => {
604624
expect(msg2Span.attributes[MessagingAttribute.MESSAGING_OPERATION]).toStrictEqual('process');
605625
});
606626
});
627+
628+
describe('moduleVersionAttributeName config', () => {
629+
beforeEach(async () => {
630+
const config: KafkaJsInstrumentationConfig = {
631+
moduleVersionAttributeName: 'module.version'
632+
};
633+
instrumentation.disable();
634+
instrumentation.setConfig(config);
635+
instrumentation.enable();
636+
consumer = kafka.consumer({
637+
groupId: 'testing-group-id',
638+
});
639+
consumer.run({
640+
eachMessage: async (payload: EachMessagePayload): Promise<void> => {},
641+
});
642+
});
643+
644+
it('adds module version to consumer span', async () => {
645+
const payload: EachMessagePayload = createEachMessagePayload();
646+
await runConfig.eachMessage(payload);
647+
648+
const spans = memoryExporter.getFinishedSpans();
649+
expect(spans.length).toBe(1);
650+
const span = spans[0];
651+
expect(span.attributes['module.version']).toMatch(/\d{1,4}\.\d{1,4}\.\d{1,5}.*/);
652+
});
653+
});
607654
});
608655

609656
describe('context propagation', () => {

packages/instrumentation-mongoose/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Mongoose instrumentation has few options available to choose from. You can set t
3737
| `suppressInternalInstrumentation` | `boolean` | Mongoose operation use mongodb under the hood. Setting this to true will hide the underlying mongodb spans (if instrumented). |
3838
| `responseHook` | `MongooseResponseCustomAttributesFunction` | Hook called before response is returned, which allows to add custom attributes to span. |
3939
| `dbStatementSerializer` | `DbStatementSerializer` | Mongoose instrumentation will serialize `db.statement` using the specified function.
40+
| `moduleVersionAttributeName` | `string` | If passed, a span attribute will be added to all spans with key of the provided `moduleVersionAttributeName` and value of the patched module version |
4041

4142
### Custom `db.statement` Serializer
4243

packages/instrumentation-mongoose/src/mongoose.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { MongooseInstrumentationConfig, SerializerPayload } from './types';
44
import { startSpan, handleCallbackResponse, handlePromiseResponse } from './utils';
55
import {
66
InstrumentationBase,
7-
InstrumentationConfig,
87
InstrumentationModuleDefinition,
98
InstrumentationNodeModuleDefinition,
109
} from '@opentelemetry/instrumentation';
@@ -29,8 +28,6 @@ const contextCaptureFunctions = [
2928
'findOneAndRemove',
3029
];
3130

32-
type Config = InstrumentationConfig & MongooseInstrumentationConfig;
33-
3431
// when mongoose functions are called, we store the original call context
3532
// and then set it as the parent for the spans created by Query/Aggregate exec()
3633
// calls. this bypass the unlinked spans issue on thenables await operations.
@@ -39,15 +36,16 @@ export const _STORED_PARENT_SPAN: unique symbol = Symbol('stored-parent-span');
3936
export class MongooseInstrumentation extends InstrumentationBase<typeof mongoose> {
4037
static readonly component = 'mongoose';
4138
protected _config: MongooseInstrumentationConfig;
39+
private moduleVersion: string;
4240

43-
constructor(config: Config = {}) {
41+
constructor(config: MongooseInstrumentationConfig = {}) {
4442
super('opentelemetry-instrumentation-mongoose', VERSION, Object.assign({}, config));
4543

4644
// According to specification, statement is not set by default on mongodb spans.
4745
if (!config.dbStatementSerializer) this._config.dbStatementSerializer = () => undefined;
4846
}
4947

50-
setConfig(config: Config = {}) {
48+
setConfig(config: MongooseInstrumentationConfig = {}) {
5149
this._config = Object.assign({}, config);
5250
if (!config.dbStatementSerializer) this._config.dbStatementSerializer = () => undefined;
5351
}
@@ -62,8 +60,9 @@ export class MongooseInstrumentation extends InstrumentationBase<typeof mongoose
6260
return module;
6361
}
6462

65-
protected patch(moduleExports: typeof mongoose) {
63+
protected patch(moduleExports: typeof mongoose, moduleVersion: string) {
6664
diag.debug('mongoose instrumentation: patching');
65+
this.moduleVersion = moduleVersion
6766

6867
this._wrap(moduleExports.Model.prototype, 'save', this.patchOnModelMethods('save'));
6968
this._wrap(moduleExports.Model.prototype, 'remove', this.patchOnModelMethods('remove'));
@@ -112,6 +111,7 @@ export class MongooseInstrumentation extends InstrumentationBase<typeof mongoose
112111
collection: this._model.collection,
113112
parentSpan,
114113
});
114+
self._addModuleVersionIfNeeded(span);
115115

116116
return self._handleResponse(span, originalAggregate, this, arguments, callback);
117117
};
@@ -139,6 +139,7 @@ export class MongooseInstrumentation extends InstrumentationBase<typeof mongoose
139139
parentSpan,
140140
collection: this.mongooseCollection,
141141
});
142+
self._addModuleVersionIfNeeded(span);
142143

143144
return self._handleResponse(span, originalExec, this, arguments, callback);
144145
};
@@ -164,6 +165,7 @@ export class MongooseInstrumentation extends InstrumentationBase<typeof mongoose
164165
attributes,
165166
collection: this.constructor.collection,
166167
});
168+
self._addModuleVersionIfNeeded(span);
167169

168170
if (options instanceof Function) {
169171
callback = options;
@@ -228,4 +230,10 @@ export class MongooseInstrumentation extends InstrumentationBase<typeof mongoose
228230
return originalFunction();
229231
}
230232
}
233+
234+
private _addModuleVersionIfNeeded(span: Span) {
235+
if (this._config.moduleVersionAttributeName) {
236+
span.setAttribute(this._config.moduleVersionAttributeName, this.moduleVersion);
237+
}
238+
}
231239
}

0 commit comments

Comments
 (0)