nestjs-otel 3.0.0 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +34 -40
- package/examples/nestjs-prom-grafana-tempo/package.json +40 -37
- package/examples/nestjs-prom-grafana-tempo/src/tracing.ts +5 -5
- package/lib/metrics/metric-data.d.ts +4 -4
- package/lib/metrics/metric-data.js +6 -6
- package/lib/metrics/metric-data.js.map +1 -1
- package/lib/metrics/metric.service.d.ts +4 -4
- package/lib/metrics/metric.service.js +6 -6
- package/lib/metrics/metric.service.js.map +1 -1
- package/lib/tracing/decorators/span.js +23 -7
- package/lib/tracing/decorators/span.js.map +1 -1
- package/lib/tracing/decorators/span.spec.d.ts +1 -0
- package/lib/tracing/decorators/span.spec.js +96 -0
- package/lib/tracing/decorators/span.spec.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/src/metrics/metric-data.ts +7 -4
- package/src/metrics/metric.service.ts +20 -8
- package/src/tracing/decorators/span.spec.ts +91 -0
- package/src/tracing/decorators/span.ts +29 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-otel",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "NestJS OpenTelemetry Library",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/pragmaticivan/nestjs-otel#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@opentelemetry/host-metrics": "^0.27.0",
|
|
40
39
|
"@opentelemetry/api": "^1.0.4",
|
|
41
40
|
"@opentelemetry/api-metrics": "^0.27.0",
|
|
41
|
+
"@opentelemetry/host-metrics": "^0.27.0",
|
|
42
42
|
"opentelemetry-node-metrics": "^1.1.0",
|
|
43
43
|
"response-time": "^2.3.2"
|
|
44
44
|
},
|
|
@@ -52,11 +52,13 @@
|
|
|
52
52
|
"@nestjs/testing": "^8.2.4",
|
|
53
53
|
"@opentelemetry/exporter-prometheus": "^0.27.0",
|
|
54
54
|
"@opentelemetry/sdk-node": "^0.27.0",
|
|
55
|
+
"@opentelemetry/sdk-trace-node": "^1.0.1",
|
|
55
56
|
"@types/jest": "^27.0.3",
|
|
56
57
|
"@types/node": "^17.0.5",
|
|
57
58
|
"@types/supertest": "^2.0.11",
|
|
58
59
|
"@typescript-eslint/eslint-plugin": "5.8.1",
|
|
59
60
|
"@typescript-eslint/parser": "5.8.1",
|
|
61
|
+
"ansi-regex": "",
|
|
60
62
|
"eslint": "^8.5.0",
|
|
61
63
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
62
64
|
"eslint-config-airbnb-typescript": "^16.1.0",
|
|
@@ -69,8 +71,7 @@
|
|
|
69
71
|
"rxjs": "^7.5.1",
|
|
70
72
|
"supertest": "^6.1.6",
|
|
71
73
|
"ts-jest": "^27.1.2",
|
|
72
|
-
"typescript": "4.5.4"
|
|
73
|
-
"ansi-regex": ""
|
|
74
|
+
"typescript": "4.5.4"
|
|
74
75
|
},
|
|
75
76
|
"peerDependencies": {
|
|
76
77
|
"@nestjs/common": "^7.0.0 || ^8.0.0",
|
|
@@ -95,7 +96,7 @@
|
|
|
95
96
|
"testEnvironment": "node"
|
|
96
97
|
},
|
|
97
98
|
"lint-staged": {
|
|
98
|
-
"*.{js,ts}": "eslint --cache --fix"
|
|
99
|
+
"!(examples/**/*)*.{js,ts}": "eslint --cache --fix"
|
|
99
100
|
},
|
|
100
101
|
"commitlint": {
|
|
101
102
|
"extends": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Counter, MetricOptions, metrics, UpDownCounter,
|
|
3
|
-
Histogram, ObservableGauge, ObservableCounter, ObservableUpDownCounter,
|
|
3
|
+
Histogram, ObservableGauge, ObservableCounter, ObservableUpDownCounter, ObservableResult,
|
|
4
4
|
} from '@opentelemetry/api-metrics';
|
|
5
5
|
import { OTEL_METER_NAME } from '../opentelemetry.constants';
|
|
6
6
|
|
|
@@ -70,6 +70,7 @@ export function getOrCreateUpDownCounter(
|
|
|
70
70
|
export function getOrCreateObservableGauge(
|
|
71
71
|
name: string,
|
|
72
72
|
options: MetricOptions,
|
|
73
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
73
74
|
): ObservableGauge {
|
|
74
75
|
if (meterData.has(name)) {
|
|
75
76
|
return meterData.get(name) as ObservableGauge;
|
|
@@ -77,7 +78,7 @@ export function getOrCreateObservableGauge(
|
|
|
77
78
|
|
|
78
79
|
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
79
80
|
|
|
80
|
-
const observableGauge = meter.createObservableGauge(name, options);
|
|
81
|
+
const observableGauge = meter.createObservableGauge(name, options, callback);
|
|
81
82
|
meterData.set(name, observableGauge);
|
|
82
83
|
return observableGauge;
|
|
83
84
|
}
|
|
@@ -85,6 +86,7 @@ export function getOrCreateObservableGauge(
|
|
|
85
86
|
export function getOrCreateObservableCounter(
|
|
86
87
|
name: string,
|
|
87
88
|
options: MetricOptions,
|
|
89
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
88
90
|
): ObservableCounter {
|
|
89
91
|
if (meterData.has(name)) {
|
|
90
92
|
return meterData.get(name) as ObservableCounter;
|
|
@@ -92,7 +94,7 @@ export function getOrCreateObservableCounter(
|
|
|
92
94
|
|
|
93
95
|
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
94
96
|
|
|
95
|
-
const observableCounter = meter.createObservableCounter(name, options);
|
|
97
|
+
const observableCounter = meter.createObservableCounter(name, options, callback);
|
|
96
98
|
meterData.set(name, observableCounter);
|
|
97
99
|
return observableCounter;
|
|
98
100
|
}
|
|
@@ -100,6 +102,7 @@ export function getOrCreateObservableCounter(
|
|
|
100
102
|
export function getOrCreateObservableUpDownCounter(
|
|
101
103
|
name: string,
|
|
102
104
|
options: MetricOptions,
|
|
105
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
103
106
|
): ObservableUpDownCounter {
|
|
104
107
|
if (meterData.has(name)) {
|
|
105
108
|
return meterData.get(name) as ObservableUpDownCounter;
|
|
@@ -107,7 +110,7 @@ export function getOrCreateObservableUpDownCounter(
|
|
|
107
110
|
|
|
108
111
|
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
109
112
|
|
|
110
|
-
const observableCounter = meter.createObservableCounter(name, options);
|
|
113
|
+
const observableCounter = meter.createObservableCounter(name, options, callback);
|
|
111
114
|
meterData.set(name, observableCounter);
|
|
112
115
|
return observableCounter;
|
|
113
116
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Injectable } from '@nestjs/common';
|
|
2
2
|
import {
|
|
3
|
-
MetricOptions,
|
|
3
|
+
MetricOptions, ObservableResult,
|
|
4
4
|
} from '@opentelemetry/api-metrics';
|
|
5
5
|
import {
|
|
6
6
|
getOrCreateCounter, getOrCreateHistogram,
|
|
7
7
|
getOrCreateObservableCounter, getOrCreateObservableGauge,
|
|
8
|
-
getOrCreateObservableUpDownCounter, getOrCreateUpDownCounter,
|
|
8
|
+
getOrCreateObservableUpDownCounter, getOrCreateUpDownCounter,
|
|
9
9
|
} from './metric-data';
|
|
10
10
|
|
|
11
11
|
@Injectable()
|
|
@@ -22,15 +22,27 @@ export class MetricService {
|
|
|
22
22
|
return getOrCreateHistogram(name, options);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
getObservableCounter(
|
|
26
|
-
|
|
25
|
+
getObservableCounter(
|
|
26
|
+
name: string,
|
|
27
|
+
options?: MetricOptions,
|
|
28
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
29
|
+
) {
|
|
30
|
+
return getOrCreateObservableCounter(name, options, callback);
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
getObservableGauge(
|
|
30
|
-
|
|
33
|
+
getObservableGauge(
|
|
34
|
+
name: string,
|
|
35
|
+
options?: MetricOptions,
|
|
36
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
37
|
+
) {
|
|
38
|
+
return getOrCreateObservableGauge(name, options, callback);
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
getObservableUpDownCounter(
|
|
34
|
-
|
|
41
|
+
getObservableUpDownCounter(
|
|
42
|
+
name: string,
|
|
43
|
+
options?: MetricOptions,
|
|
44
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
45
|
+
) {
|
|
46
|
+
return getOrCreateObservableUpDownCounter(name, options, callback);
|
|
35
47
|
}
|
|
36
48
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
+
import { tracing } from '@opentelemetry/sdk-node';
|
|
3
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
4
|
+
import { Span } from './span';
|
|
5
|
+
|
|
6
|
+
class TestSpan {
|
|
7
|
+
@Span()
|
|
8
|
+
singleSpan() {}
|
|
9
|
+
|
|
10
|
+
@Span()
|
|
11
|
+
doubleSpan() {
|
|
12
|
+
return this.singleSpan();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Span()
|
|
16
|
+
error() {
|
|
17
|
+
throw new Error('hello world');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('Span', () => {
|
|
22
|
+
let instance: TestSpan;
|
|
23
|
+
let traceExporter: tracing.InMemorySpanExporter;
|
|
24
|
+
let spanProcessor: tracing.SimpleSpanProcessor;
|
|
25
|
+
let provider: NodeTracerProvider;
|
|
26
|
+
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
instance = new TestSpan();
|
|
29
|
+
traceExporter = new tracing.InMemorySpanExporter();
|
|
30
|
+
spanProcessor = new tracing.SimpleSpanProcessor(traceExporter);
|
|
31
|
+
|
|
32
|
+
provider = new NodeTracerProvider();
|
|
33
|
+
provider.addSpanProcessor(spanProcessor);
|
|
34
|
+
provider.register();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
spanProcessor.forceFlush();
|
|
39
|
+
traceExporter.reset();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterAll(async () => {
|
|
43
|
+
await provider.shutdown();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should set correct span', async () => {
|
|
47
|
+
instance.singleSpan();
|
|
48
|
+
|
|
49
|
+
const spans = traceExporter.getFinishedSpans();
|
|
50
|
+
|
|
51
|
+
expect(spans).toHaveLength(1);
|
|
52
|
+
expect(spans.map(span => span.name)).toEqual(['TestSpan.singleSpan']);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should set correct span even when calling other method with Span decorator', async () => {
|
|
56
|
+
instance.doubleSpan();
|
|
57
|
+
|
|
58
|
+
const spans = traceExporter.getFinishedSpans();
|
|
59
|
+
|
|
60
|
+
expect(spans).toHaveLength(2);
|
|
61
|
+
expect(spans.map(span => span.name)).toEqual(['TestSpan.singleSpan', 'TestSpan.doubleSpan']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should propagate errors', () => {
|
|
65
|
+
expect(instance.error).toThrowError('hello world');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should set setStatus to ERROR and message to error message', async () => {
|
|
69
|
+
expect(instance.error).toThrowError('hello world');
|
|
70
|
+
|
|
71
|
+
const spans = traceExporter.getFinishedSpans();
|
|
72
|
+
|
|
73
|
+
expect(spans).toHaveLength(1);
|
|
74
|
+
expect(spans[0].status).toEqual({ code: SpanStatusCode.ERROR, message: 'hello world' });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should set recordException with error', () => {
|
|
78
|
+
expect(instance.error).toThrowError('hello world');
|
|
79
|
+
|
|
80
|
+
const spans = traceExporter.getFinishedSpans();
|
|
81
|
+
|
|
82
|
+
expect(spans).toHaveLength(1);
|
|
83
|
+
// Contain one exception event
|
|
84
|
+
expect(spans[0].events).toHaveLength(1);
|
|
85
|
+
expect(spans[0].events[0]).toEqual({
|
|
86
|
+
name: 'exception',
|
|
87
|
+
attributes: expect.anything(),
|
|
88
|
+
time: expect.anything(),
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -1,29 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Span as ApiSpan, SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
const recordException = (span: ApiSpan, error: any) => {
|
|
4
|
+
span.recordException(error);
|
|
5
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
6
|
+
};
|
|
2
7
|
|
|
3
8
|
export function Span(name?: string) {
|
|
4
|
-
return (
|
|
5
|
-
target: any,
|
|
6
|
-
propertyKey: string,
|
|
7
|
-
propertyDescriptor: PropertyDescriptor,
|
|
8
|
-
) => {
|
|
9
|
+
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
|
|
9
10
|
const method = propertyDescriptor.value;
|
|
10
11
|
// eslint-disable-next-line no-param-reassign
|
|
11
12
|
propertyDescriptor.value = function PropertyDescriptor(...args: any[]) {
|
|
12
|
-
const currentSpan = trace.getSpan(context.active());
|
|
13
13
|
const tracer = trace.getTracer('default');
|
|
14
|
+
const spanName = name || `${target.constructor.name}.${propertyKey}`;
|
|
14
15
|
|
|
15
|
-
return
|
|
16
|
-
const span = tracer.startSpan(
|
|
17
|
-
name || `${target.constructor.name}.${propertyKey}`,
|
|
18
|
-
);
|
|
16
|
+
return tracer.startActiveSpan(spanName, span => {
|
|
19
17
|
if (method.constructor.name === 'AsyncFunction') {
|
|
20
|
-
return method
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
return method
|
|
19
|
+
.apply(this, args)
|
|
20
|
+
.catch(error => {
|
|
21
|
+
recordException(span, error);
|
|
22
|
+
// Throw error to propagate it further
|
|
23
|
+
throw error;
|
|
24
|
+
})
|
|
25
|
+
.finally(() => {
|
|
26
|
+
span.end();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
return method.apply(this, args);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
recordException(span, error);
|
|
34
|
+
// Throw error to propagate it further
|
|
35
|
+
throw error;
|
|
36
|
+
} finally {
|
|
37
|
+
span.end();
|
|
23
38
|
}
|
|
24
|
-
const result = method.apply(this, args);
|
|
25
|
-
span.end();
|
|
26
|
-
return result;
|
|
27
39
|
});
|
|
28
40
|
};
|
|
29
41
|
};
|