nestjs-otel 2.6.1 → 3.0.2
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 +28 -0
- package/README.md +50 -39
- package/examples/nestjs-prom-grafana-tempo/package.json +40 -37
- package/examples/nestjs-prom-grafana-tempo/src/tracing.ts +5 -5
- package/lib/interfaces/opentelemetry-options.interface.d.ts +2 -2
- package/lib/metrics/decorators/common.js +2 -2
- package/lib/metrics/decorators/common.js.map +1 -1
- package/lib/metrics/decorators/counter.js +2 -2
- package/lib/metrics/decorators/counter.js.map +1 -1
- package/lib/metrics/decorators/histogram.d.ts +1 -0
- package/lib/metrics/decorators/histogram.js +12 -0
- package/lib/metrics/decorators/histogram.js.map +1 -0
- package/lib/metrics/decorators/index.d.ts +2 -1
- package/lib/metrics/decorators/index.js +2 -1
- package/lib/metrics/decorators/index.js.map +1 -1
- package/lib/metrics/decorators/observable.d.ts +3 -0
- package/lib/metrics/decorators/observable.js +24 -0
- package/lib/metrics/decorators/observable.js.map +1 -0
- package/lib/metrics/metric-data.d.ts +12 -5
- package/lib/metrics/metric-data.js +53 -24
- package/lib/metrics/metric-data.js.map +1 -1
- package/lib/metrics/metric.service.d.ts +7 -6
- package/lib/metrics/metric.service.js +11 -8
- package/lib/metrics/metric.service.js.map +1 -1
- package/lib/middleware/api-metrics.middleware.d.ts +5 -5
- package/lib/middleware/api-metrics.middleware.js +17 -17
- package/lib/middleware/api-metrics.middleware.js.map +1 -1
- package/lib/opentelemetry-core.module.js.map +1 -1
- package/lib/tracing/decorators/span.js +2 -3
- 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 +58 -0
- package/lib/tracing/decorators/span.spec.js.map +1 -0
- package/lib/tracing/trace.service.d.ts +1 -0
- package/lib/tracing/trace.service.js +4 -2
- package/lib/tracing/trace.service.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -32
- package/src/interfaces/opentelemetry-options.interface.ts +3 -3
- package/src/metrics/decorators/common.ts +6 -4
- package/src/metrics/decorators/counter.ts +5 -9
- package/src/metrics/decorators/histogram.ts +10 -0
- package/src/metrics/decorators/observable.ts +28 -0
- package/src/metrics/metric-data.ts +89 -29
- package/src/metrics/metric.service.ts +28 -14
- package/src/middleware/api-metrics.middleware.ts +19 -19
- package/src/opentelemetry-core.module.ts +3 -3
- package/src/tracing/decorators/span.spec.ts +48 -0
- package/src/tracing/decorators/span.ts +4 -11
- package/src/tracing/trace.service.ts +5 -2
- package/lib/metrics/decorators/value-recorder.d.ts +0 -1
- package/lib/metrics/decorators/value-recorder.js +0 -12
- package/lib/metrics/decorators/value-recorder.js.map +0 -1
- package/src/metrics/decorators/value-recorder.ts +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-otel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "NestJS OpenTelemetry Library",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"test:coverage": "jest --coverage",
|
|
15
15
|
"test:unit": "jest --passWithNoTests",
|
|
16
16
|
"test:watch": "jest --watch",
|
|
17
|
-
"test:e2e": "jest --config ./tests/jest-e2e.json --runInBand --
|
|
17
|
+
"test:e2e": "jest --config ./tests/jest-e2e.json --runInBand --forceExit",
|
|
18
18
|
"test:e2e:watch": "jest --config ./tests/jest-e2e.json --runInBand --watch"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
@@ -31,47 +31,46 @@
|
|
|
31
31
|
],
|
|
32
32
|
"author": "pragmaticivan@gmail.com",
|
|
33
33
|
"license": "Apache-2.0",
|
|
34
|
-
"test": "jest --ci --runInBand",
|
|
35
34
|
"bugs": {
|
|
36
35
|
"url": "https://github.com/pragmaticivan/nestjs-otel/issues"
|
|
37
36
|
},
|
|
38
37
|
"homepage": "https://github.com/pragmaticivan/nestjs-otel#readme",
|
|
39
38
|
"dependencies": {
|
|
40
|
-
"@opentelemetry/host-metrics": "^0.
|
|
41
|
-
"@opentelemetry/api": "^1.0.
|
|
42
|
-
"@opentelemetry/api-metrics": "^0.
|
|
43
|
-
"
|
|
44
|
-
"opentelemetry-node-metrics": "^1.0.3",
|
|
39
|
+
"@opentelemetry/host-metrics": "^0.27.0",
|
|
40
|
+
"@opentelemetry/api": "^1.0.4",
|
|
41
|
+
"@opentelemetry/api-metrics": "^0.27.0",
|
|
42
|
+
"opentelemetry-node-metrics": "^1.1.0",
|
|
45
43
|
"response-time": "^2.3.2"
|
|
46
44
|
},
|
|
47
45
|
"devDependencies": {
|
|
48
|
-
"@commitlint/cli": "^
|
|
49
|
-
"@commitlint/config-conventional": "^
|
|
50
|
-
"@nestjs/common": "^8.
|
|
51
|
-
"@nestjs/core": "^8.
|
|
52
|
-
"@nestjs/platform-express": "^8.
|
|
53
|
-
"@nestjs/platform-fastify": "^8.
|
|
54
|
-
"@nestjs/testing": "^8.
|
|
55
|
-
"@opentelemetry/exporter-prometheus": "^0.
|
|
56
|
-
"@opentelemetry/sdk-node": "^0.
|
|
57
|
-
"@types/jest": "^27.0.
|
|
58
|
-
"@types/node": "^
|
|
46
|
+
"@commitlint/cli": "^16.0.1",
|
|
47
|
+
"@commitlint/config-conventional": "^16.0.0",
|
|
48
|
+
"@nestjs/common": "^8.2.3",
|
|
49
|
+
"@nestjs/core": "^8.2.3",
|
|
50
|
+
"@nestjs/platform-express": "^8.2.4",
|
|
51
|
+
"@nestjs/platform-fastify": "^8.2.4",
|
|
52
|
+
"@nestjs/testing": "^8.2.4",
|
|
53
|
+
"@opentelemetry/exporter-prometheus": "^0.27.0",
|
|
54
|
+
"@opentelemetry/sdk-node": "^0.27.0",
|
|
55
|
+
"@types/jest": "^27.0.3",
|
|
56
|
+
"@types/node": "^17.0.5",
|
|
59
57
|
"@types/supertest": "^2.0.11",
|
|
60
|
-
"@typescript-eslint/eslint-plugin": "
|
|
61
|
-
"@typescript-eslint/parser": "
|
|
62
|
-
"eslint": "^
|
|
63
|
-
"eslint-config-airbnb-base": "^
|
|
64
|
-
"eslint-config-airbnb-typescript": "^
|
|
65
|
-
"eslint-plugin-import": "^2.25.
|
|
66
|
-
"husky": "^7.0.
|
|
67
|
-
"jest": "^27.
|
|
68
|
-
"lint-staged": "^
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "5.8.1",
|
|
59
|
+
"@typescript-eslint/parser": "5.8.1",
|
|
60
|
+
"eslint": "^8.5.0",
|
|
61
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
62
|
+
"eslint-config-airbnb-typescript": "^16.1.0",
|
|
63
|
+
"eslint-plugin-import": "^2.25.3",
|
|
64
|
+
"husky": "^7.0.4",
|
|
65
|
+
"jest": "^27.4.5",
|
|
66
|
+
"lint-staged": "^12.1.4",
|
|
69
67
|
"reflect-metadata": "^0.1.13",
|
|
70
68
|
"rimraf": "^3.0.2",
|
|
71
|
-
"rxjs": "^7.
|
|
69
|
+
"rxjs": "^7.5.1",
|
|
72
70
|
"supertest": "^6.1.6",
|
|
73
|
-
"ts-jest": "^27.
|
|
74
|
-
"typescript": "4.4
|
|
71
|
+
"ts-jest": "^27.1.2",
|
|
72
|
+
"typescript": "4.5.4",
|
|
73
|
+
"ansi-regex": ""
|
|
75
74
|
},
|
|
76
75
|
"peerDependencies": {
|
|
77
76
|
"@nestjs/common": "^7.0.0 || ^8.0.0",
|
|
@@ -96,7 +95,7 @@
|
|
|
96
95
|
"testEnvironment": "node"
|
|
97
96
|
},
|
|
98
97
|
"lint-staged": {
|
|
99
|
-
"*.{js,ts}": "eslint --cache --fix"
|
|
98
|
+
"!(examples/**/*)*.{js,ts}": "eslint --cache --fix"
|
|
100
99
|
},
|
|
101
100
|
"commitlint": {
|
|
102
101
|
"extends": [
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ModuleMetadata, Type, Abstract } from '@nestjs/common';
|
|
2
|
-
import {
|
|
2
|
+
import { Attributes } from '@opentelemetry/api-metrics';
|
|
3
3
|
import { RouteInfo } from '@nestjs/common/interfaces';
|
|
4
4
|
|
|
5
5
|
export type OpenTelemetryModuleOptions = {
|
|
6
6
|
/**
|
|
7
7
|
* OpenTelemetry Metrics Setup
|
|
8
8
|
*/
|
|
9
|
-
metrics?: OpenTelemetryMetrics
|
|
9
|
+
metrics?: OpenTelemetryMetrics;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export interface OpenTelemetryOptionsFactory {
|
|
@@ -52,7 +52,7 @@ export type OpenTelemetryMetrics = {
|
|
|
52
52
|
timeBuckets?: number[],
|
|
53
53
|
requestSizeBuckets?: number[],
|
|
54
54
|
responseSizeBuckets?: number[],
|
|
55
|
-
|
|
55
|
+
defaultAttributes?: Attributes,
|
|
56
56
|
ignoreRoutes?: (string | RouteInfo)[],
|
|
57
57
|
ignoreUndefinedRoutes?: boolean,
|
|
58
58
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Counter, MetricOptions } from '@opentelemetry/api-metrics';
|
|
2
|
-
import { getOrCreateCounter
|
|
2
|
+
import { getOrCreateCounter } from '../metric-data';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Create and increment a counter when a new instance is created
|
|
@@ -16,7 +16,7 @@ export const OtelInstanceCounter = (
|
|
|
16
16
|
return class extends ctor {
|
|
17
17
|
constructor(...args) {
|
|
18
18
|
if (!counterMetric) {
|
|
19
|
-
counterMetric = getOrCreateCounter(name,
|
|
19
|
+
counterMetric = getOrCreateCounter(name, { description, ...options });
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
counterMetric.add(1);
|
|
@@ -31,7 +31,9 @@ export const OtelInstanceCounter = (
|
|
|
31
31
|
export const OtelMethodCounter = (
|
|
32
32
|
options?: MetricOptions,
|
|
33
33
|
) => (
|
|
34
|
-
target: Object,
|
|
34
|
+
target: Object,
|
|
35
|
+
propertyKey: string | symbol,
|
|
36
|
+
descriptor: TypedPropertyDescriptor<(...args: any[]
|
|
35
37
|
) => any>,
|
|
36
38
|
) => {
|
|
37
39
|
const className = target.constructor.name;
|
|
@@ -42,7 +44,7 @@ export const OtelMethodCounter = (
|
|
|
42
44
|
// eslint-disable-next-line no-param-reassign, func-names
|
|
43
45
|
descriptor.value = function (...args: any[]) {
|
|
44
46
|
if (!counterMetric) {
|
|
45
|
-
counterMetric = getOrCreateCounter(name,
|
|
47
|
+
counterMetric = getOrCreateCounter(name, { description, ...options });
|
|
46
48
|
}
|
|
47
49
|
counterMetric.add(1);
|
|
48
50
|
return methodFunc.apply(this, args);
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import { createParamDecorator } from '@nestjs/common';
|
|
2
2
|
import { MetricOptions } from '@opentelemetry/api-metrics';
|
|
3
|
-
import { getOrCreateCounter,
|
|
3
|
+
import { getOrCreateCounter, MetricType } from '../metric-data';
|
|
4
4
|
|
|
5
|
-
export const OtelCounter = createParamDecorator((
|
|
6
|
-
name: string, options?: MetricOptions,
|
|
7
|
-
) => {
|
|
5
|
+
export const OtelCounter = createParamDecorator((name: string, options?: MetricOptions) => {
|
|
8
6
|
if (!name || name.length === 0) {
|
|
9
7
|
throw new Error('OtelCounter need a name argument');
|
|
10
8
|
}
|
|
11
|
-
return getOrCreateCounter(name,
|
|
9
|
+
return getOrCreateCounter(name, options);
|
|
12
10
|
});
|
|
13
11
|
|
|
14
|
-
export const OtelUpDownCounter = createParamDecorator((
|
|
15
|
-
name: string, options?: MetricOptions,
|
|
16
|
-
) => {
|
|
12
|
+
export const OtelUpDownCounter = createParamDecorator((name: string, options?: MetricOptions) => {
|
|
17
13
|
if (!name || name.length === 0) {
|
|
18
14
|
throw new Error('OtelUpDownCounter need a name argument');
|
|
19
15
|
}
|
|
20
|
-
return getOrCreateCounter(name,
|
|
16
|
+
return getOrCreateCounter(name, options);
|
|
21
17
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createParamDecorator } from '@nestjs/common';
|
|
2
|
+
import { MetricOptions } from '@opentelemetry/api-metrics';
|
|
3
|
+
import { getOrCreateHistogram } from '../metric-data';
|
|
4
|
+
|
|
5
|
+
export const OtelHistogram = createParamDecorator((name: string, options?: MetricOptions) => {
|
|
6
|
+
if (!name || name.length === 0) {
|
|
7
|
+
throw new Error('OtelHistogram need a name argument');
|
|
8
|
+
}
|
|
9
|
+
return getOrCreateHistogram(name, options);
|
|
10
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createParamDecorator } from '@nestjs/common';
|
|
2
|
+
import { MetricOptions } from '@opentelemetry/api-metrics';
|
|
3
|
+
import { getOrCreateObservableCounter, getOrCreateObservableGauge, getOrCreateObservableUpDownCounter } from '../metric-data';
|
|
4
|
+
|
|
5
|
+
export const OtelObservableGauge = createParamDecorator((name: string, options?: MetricOptions) => {
|
|
6
|
+
if (!name || name.length === 0) {
|
|
7
|
+
throw new Error('OtelObservableGauge need a name argument');
|
|
8
|
+
}
|
|
9
|
+
return getOrCreateObservableGauge(name, options);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const OtelObservableCounter = createParamDecorator(
|
|
13
|
+
(name: string, options?: MetricOptions) => {
|
|
14
|
+
if (!name || name.length === 0) {
|
|
15
|
+
throw new Error('OtelObservableCounter need a name argument');
|
|
16
|
+
}
|
|
17
|
+
return getOrCreateObservableCounter(name, options);
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const OtelObservableUpDownCounter = createParamDecorator(
|
|
22
|
+
(name: string, options?: MetricOptions) => {
|
|
23
|
+
if (!name || name.length === 0) {
|
|
24
|
+
throw new Error('OtelObservableUpDownCounter need a name argument');
|
|
25
|
+
}
|
|
26
|
+
return getOrCreateObservableUpDownCounter(name, options);
|
|
27
|
+
},
|
|
28
|
+
);
|
|
@@ -1,56 +1,116 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Counter, MetricOptions, metrics, UpDownCounter,
|
|
2
|
+
Counter, MetricOptions, metrics, UpDownCounter,
|
|
3
|
+
Histogram, ObservableGauge, ObservableCounter, ObservableUpDownCounter, ObservableResult,
|
|
3
4
|
} from '@opentelemetry/api-metrics';
|
|
4
5
|
import { OTEL_METER_NAME } from '../opentelemetry.constants';
|
|
5
6
|
|
|
6
|
-
export type GenericMetric =
|
|
7
|
+
export type GenericMetric =
|
|
8
|
+
Counter |
|
|
9
|
+
UpDownCounter |
|
|
10
|
+
Histogram |
|
|
11
|
+
ObservableGauge |
|
|
12
|
+
ObservableCounter |
|
|
13
|
+
ObservableUpDownCounter;
|
|
7
14
|
|
|
8
15
|
export enum MetricType {
|
|
9
16
|
'Counter' = 'Counter',
|
|
10
17
|
'UpDownCounter' = 'UpDownCounter',
|
|
11
|
-
'
|
|
18
|
+
'Histogram' = 'Histogram',
|
|
19
|
+
'ObservableGauge' = 'ObservableGauge',
|
|
20
|
+
'ObservableCounter' = 'ObservableCounter',
|
|
21
|
+
'ObservableUpDownCounter' = 'ObservableUpDownCounter',
|
|
12
22
|
}
|
|
13
23
|
|
|
14
24
|
export const meterData: Map<string, GenericMetric> = new Map();
|
|
15
25
|
|
|
16
|
-
export function
|
|
17
|
-
name: string,
|
|
18
|
-
|
|
26
|
+
export function getOrCreateHistogram(
|
|
27
|
+
name: string,
|
|
28
|
+
options: MetricOptions,
|
|
29
|
+
): Histogram {
|
|
19
30
|
if (meterData.has(name)) {
|
|
20
|
-
return meterData.get(name) as
|
|
31
|
+
return meterData.get(name) as Histogram;
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
35
|
+
const histogram = meter.createHistogram(name, options);
|
|
36
|
+
meterData.set(name, histogram);
|
|
37
|
+
return histogram;
|
|
38
|
+
}
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
throw new Error(`Unknown type: ${type}`);
|
|
40
|
+
export function getOrCreateCounter(
|
|
41
|
+
name: string,
|
|
42
|
+
options: MetricOptions,
|
|
43
|
+
): Counter {
|
|
44
|
+
if (meterData.has(name)) {
|
|
45
|
+
return meterData.get(name) as Counter;
|
|
32
46
|
}
|
|
47
|
+
|
|
48
|
+
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
49
|
+
|
|
50
|
+
const counter = meter.createCounter(name, options);
|
|
51
|
+
meterData.set(name, counter);
|
|
52
|
+
return counter;
|
|
33
53
|
}
|
|
34
54
|
|
|
35
|
-
export function
|
|
36
|
-
name: string,
|
|
37
|
-
|
|
55
|
+
export function getOrCreateUpDownCounter(
|
|
56
|
+
name: string,
|
|
57
|
+
options: MetricOptions,
|
|
58
|
+
): UpDownCounter {
|
|
38
59
|
if (meterData.has(name)) {
|
|
39
|
-
return meterData.get(name) as
|
|
60
|
+
return meterData.get(name) as UpDownCounter;
|
|
40
61
|
}
|
|
41
62
|
|
|
42
63
|
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
const upDownCounter = meter.createUpDownCounter(name, options);
|
|
66
|
+
meterData.set(name, upDownCounter);
|
|
67
|
+
return upDownCounter;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getOrCreateObservableGauge(
|
|
71
|
+
name: string,
|
|
72
|
+
options: MetricOptions,
|
|
73
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
74
|
+
): ObservableGauge {
|
|
75
|
+
if (meterData.has(name)) {
|
|
76
|
+
return meterData.get(name) as ObservableGauge;
|
|
55
77
|
}
|
|
78
|
+
|
|
79
|
+
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
80
|
+
|
|
81
|
+
const observableGauge = meter.createObservableGauge(name, options, callback);
|
|
82
|
+
meterData.set(name, observableGauge);
|
|
83
|
+
return observableGauge;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getOrCreateObservableCounter(
|
|
87
|
+
name: string,
|
|
88
|
+
options: MetricOptions,
|
|
89
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
90
|
+
): ObservableCounter {
|
|
91
|
+
if (meterData.has(name)) {
|
|
92
|
+
return meterData.get(name) as ObservableCounter;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
96
|
+
|
|
97
|
+
const observableCounter = meter.createObservableCounter(name, options, callback);
|
|
98
|
+
meterData.set(name, observableCounter);
|
|
99
|
+
return observableCounter;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getOrCreateObservableUpDownCounter(
|
|
103
|
+
name: string,
|
|
104
|
+
options: MetricOptions,
|
|
105
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
106
|
+
): ObservableUpDownCounter {
|
|
107
|
+
if (meterData.has(name)) {
|
|
108
|
+
return meterData.get(name) as ObservableUpDownCounter;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
|
|
112
|
+
|
|
113
|
+
const observableCounter = meter.createObservableCounter(name, options, callback);
|
|
114
|
+
meterData.set(name, observableCounter);
|
|
115
|
+
return observableCounter;
|
|
56
116
|
}
|
|
@@ -1,34 +1,48 @@
|
|
|
1
1
|
import { Injectable } from '@nestjs/common';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
MetricOptions, ObservableResult,
|
|
4
4
|
} from '@opentelemetry/api-metrics';
|
|
5
5
|
import {
|
|
6
|
-
getOrCreateCounter,
|
|
6
|
+
getOrCreateCounter, getOrCreateHistogram,
|
|
7
|
+
getOrCreateObservableCounter, getOrCreateObservableGauge,
|
|
8
|
+
getOrCreateObservableUpDownCounter, getOrCreateUpDownCounter,
|
|
7
9
|
} from './metric-data';
|
|
8
10
|
|
|
9
11
|
@Injectable()
|
|
10
12
|
export class MetricService {
|
|
11
13
|
getCounter(name: string, options?: MetricOptions) {
|
|
12
|
-
return
|
|
14
|
+
return getOrCreateCounter(name, options);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
getUpDownCounter(name: string, options?: MetricOptions) {
|
|
16
|
-
return
|
|
18
|
+
return getOrCreateUpDownCounter(name, options);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
return
|
|
21
|
+
getHistogram(name: string, options?: MetricOptions) {
|
|
22
|
+
return getOrCreateHistogram(name, options);
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
name: string,
|
|
25
|
-
|
|
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
|
-
|
|
30
|
-
name: string,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
getObservableGauge(
|
|
34
|
+
name: string,
|
|
35
|
+
options?: MetricOptions,
|
|
36
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
37
|
+
) {
|
|
38
|
+
return getOrCreateObservableGauge(name, options, callback);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getObservableUpDownCounter(
|
|
42
|
+
name: string,
|
|
43
|
+
options?: MetricOptions,
|
|
44
|
+
callback?: (observableResult: ObservableResult) => void,
|
|
45
|
+
) {
|
|
46
|
+
return getOrCreateObservableUpDownCounter(name, options, callback);
|
|
33
47
|
}
|
|
34
48
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Inject, Injectable, NestMiddleware } from '@nestjs/common';
|
|
2
2
|
import * as responseTime from 'response-time';
|
|
3
3
|
import * as urlParser from 'url';
|
|
4
|
-
import { Counter,
|
|
4
|
+
import { Counter, Attributes, Histogram } from '@opentelemetry/api-metrics';
|
|
5
5
|
import { OpenTelemetryModuleOptions } from '../interfaces';
|
|
6
6
|
import { MetricService } from '../metrics/metric.service';
|
|
7
7
|
import { OPENTELEMETRY_MODULE_OPTIONS } from '../opentelemetry.constants';
|
|
@@ -37,19 +37,19 @@ export class ApiMetricsMiddleware implements NestMiddleware {
|
|
|
37
37
|
|
|
38
38
|
private serverAbortsTotal: Counter;
|
|
39
39
|
|
|
40
|
-
private requestDuration:
|
|
40
|
+
private requestDuration: Histogram;
|
|
41
41
|
|
|
42
|
-
private
|
|
42
|
+
private requestSizeHistogram: Histogram;
|
|
43
43
|
|
|
44
|
-
private
|
|
44
|
+
private responseSizeHistogram: Histogram;
|
|
45
45
|
|
|
46
|
-
private
|
|
46
|
+
private defaultAttributes: Attributes;
|
|
47
47
|
|
|
48
48
|
private readonly ignoreUndefinedRoutes: boolean;
|
|
49
49
|
|
|
50
50
|
constructor(
|
|
51
|
-
@Inject(OPENTELEMETRY_MODULE_OPTIONS) private readonly options: OpenTelemetryModuleOptions = {},
|
|
52
51
|
@Inject(MetricService) private readonly metricService: MetricService,
|
|
52
|
+
@Inject(OPENTELEMETRY_MODULE_OPTIONS) private readonly options: OpenTelemetryModuleOptions = {},
|
|
53
53
|
) {
|
|
54
54
|
this.requestTotal = this.metricService.getCounter('http_request_total', {
|
|
55
55
|
description: 'Total number of HTTP requests',
|
|
@@ -80,25 +80,25 @@ export class ApiMetricsMiddleware implements NestMiddleware {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
const {
|
|
83
|
-
timeBuckets = [], requestSizeBuckets = [], responseSizeBuckets = [],
|
|
83
|
+
timeBuckets = [], requestSizeBuckets = [], responseSizeBuckets = [], defaultAttributes = {},
|
|
84
84
|
ignoreUndefinedRoutes = false,
|
|
85
85
|
} = options?.metrics?.apiMetrics;
|
|
86
86
|
|
|
87
|
-
this.
|
|
87
|
+
this.defaultAttributes = defaultAttributes;
|
|
88
88
|
this.ignoreUndefinedRoutes = ignoreUndefinedRoutes;
|
|
89
89
|
|
|
90
|
-
this.requestDuration = this.metricService.
|
|
90
|
+
this.requestDuration = this.metricService.getHistogram('http_request_duration_seconds', {
|
|
91
91
|
boundaries: timeBuckets.length > 0 ? timeBuckets : this.defaultLongRunningRequestBuckets,
|
|
92
92
|
description: 'HTTP latency value recorder in seconds',
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
this.
|
|
95
|
+
this.requestSizeHistogram = this.metricService.getHistogram('http_request_size_bytes', {
|
|
96
96
|
boundaries:
|
|
97
97
|
requestSizeBuckets.length > 0 ? requestSizeBuckets : this.defaultRequestSizeBuckets,
|
|
98
98
|
description: 'Current total of incoming bytes',
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
this.
|
|
101
|
+
this.responseSizeHistogram = this.metricService.getHistogram('http_response_size_bytes', {
|
|
102
102
|
boundaries:
|
|
103
103
|
responseSizeBuckets.length > 0 ? responseSizeBuckets : this.defaultResponseSizeBuckets,
|
|
104
104
|
description: 'Current total of outgoing bytes',
|
|
@@ -119,21 +119,21 @@ export class ApiMetricsMiddleware implements NestMiddleware {
|
|
|
119
119
|
path = urlParser.parse(url).pathname;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
this.requestTotal.
|
|
122
|
+
this.requestTotal.add(1, { method, path });
|
|
123
123
|
|
|
124
124
|
const requestLength = parseInt(req.headers['content-length'], 10) || 0;
|
|
125
125
|
const responseLength: number = parseInt(res.getHeader('Content-Length'), 10) || 0;
|
|
126
126
|
|
|
127
127
|
const status = res.statusCode || 500;
|
|
128
|
-
const
|
|
129
|
-
method, status, path, ...this.
|
|
128
|
+
const attributes: Attributes = {
|
|
129
|
+
method, status, path, ...this.defaultAttributes,
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
this.
|
|
133
|
-
this.
|
|
132
|
+
this.requestSizeHistogram.record(requestLength, attributes);
|
|
133
|
+
this.responseSizeHistogram.record(responseLength, attributes);
|
|
134
134
|
|
|
135
|
-
this.responseTotal.
|
|
136
|
-
this.requestDuration.
|
|
135
|
+
this.responseTotal.add(1, attributes);
|
|
136
|
+
this.requestDuration.record(time / 1000, attributes);
|
|
137
137
|
|
|
138
138
|
const codeClass = this.getStatusCodeClass(status);
|
|
139
139
|
|
|
@@ -143,7 +143,7 @@ export class ApiMetricsMiddleware implements NestMiddleware {
|
|
|
143
143
|
this.responseSuccessTotal.add(1);
|
|
144
144
|
break;
|
|
145
145
|
case 'redirect':
|
|
146
|
-
|
|
146
|
+
// TODO: Review what should be appropriate for redirects.
|
|
147
147
|
this.responseSuccessTotal.add(1);
|
|
148
148
|
break;
|
|
149
149
|
case 'client_error':
|
|
@@ -27,7 +27,7 @@ export class OpenTelemetryCoreModule implements OnApplicationBootstrap {
|
|
|
27
27
|
|
|
28
28
|
constructor(
|
|
29
29
|
@Inject(OPENTELEMETRY_MODULE_OPTIONS) private readonly options: OpenTelemetryModuleOptions = {},
|
|
30
|
-
) {}
|
|
30
|
+
) { }
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Bootstraps the internal OpenTelemetry Module with the given options
|
|
@@ -93,8 +93,8 @@ export class OpenTelemetryCoreModule implements OnApplicationBootstrap {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async onApplicationBootstrap() {
|
|
96
|
-
let defaultMetrics:boolean = false;
|
|
97
|
-
let hostMetrics:boolean = false;
|
|
96
|
+
let defaultMetrics: boolean = false;
|
|
97
|
+
let hostMetrics: boolean = false;
|
|
98
98
|
|
|
99
99
|
if (this.options?.metrics) {
|
|
100
100
|
defaultMetrics = this.options.metrics.defaultMetrics
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { context, trace } from '@opentelemetry/api';
|
|
2
|
+
import { NodeSDK, tracing } from '@opentelemetry/sdk-node';
|
|
3
|
+
import { Span } from './span';
|
|
4
|
+
|
|
5
|
+
class TestSpan {
|
|
6
|
+
@Span()
|
|
7
|
+
singleSpan() {
|
|
8
|
+
return trace.getSpan(context.active());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@Span()
|
|
12
|
+
doubleSpan() {
|
|
13
|
+
return this.singleSpan();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('Span', () => {
|
|
18
|
+
let instance: TestSpan;
|
|
19
|
+
let otelSdk: NodeSDK;
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
instance = new TestSpan();
|
|
23
|
+
|
|
24
|
+
otelSdk = new NodeSDK({
|
|
25
|
+
traceExporter: new tracing.ConsoleSpanExporter(),
|
|
26
|
+
// Noop Span Processor disables any spans from being outputted
|
|
27
|
+
// by ConsoleSpanExporter.
|
|
28
|
+
// Remove it if you want to debug spans exported.
|
|
29
|
+
spanProcessor: new tracing.NoopSpanProcessor(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await otelSdk.start();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
await otelSdk.shutdown();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should set correct span', async () => {
|
|
40
|
+
const response = instance.singleSpan();
|
|
41
|
+
expect((response as any).name).toEqual('TestSpan.singleSpan');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should set correct span even when calling other method with Span decorator', async () => {
|
|
45
|
+
const response = instance.doubleSpan();
|
|
46
|
+
expect((response as any).name).toEqual('TestSpan.singleSpan');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { trace } from '@opentelemetry/api';
|
|
2
2
|
|
|
3
3
|
export function Span(name?: string) {
|
|
4
|
-
return (
|
|
5
|
-
target: any,
|
|
6
|
-
propertyKey: string,
|
|
7
|
-
propertyDescriptor: PropertyDescriptor,
|
|
8
|
-
) => {
|
|
4
|
+
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
|
|
9
5
|
const method = propertyDescriptor.value;
|
|
10
6
|
// eslint-disable-next-line no-param-reassign
|
|
11
7
|
propertyDescriptor.value = function PropertyDescriptor(...args: any[]) {
|
|
12
|
-
const currentSpan = trace.getSpan(context.active());
|
|
13
8
|
const tracer = trace.getTracer('default');
|
|
9
|
+
const spanName = name || `${target.constructor.name}.${propertyKey}`;
|
|
14
10
|
|
|
15
|
-
return
|
|
16
|
-
const span = tracer.startSpan(
|
|
17
|
-
name || `${target.constructor.name}.${propertyKey}`,
|
|
18
|
-
);
|
|
11
|
+
return tracer.startActiveSpan(spanName, span => {
|
|
19
12
|
if (method.constructor.name === 'AsyncFunction') {
|
|
20
13
|
return method.apply(this, args).finally(() => {
|
|
21
14
|
span.end();
|
|
@@ -3,12 +3,15 @@ import { Injectable } from '@nestjs/common';
|
|
|
3
3
|
|
|
4
4
|
@Injectable()
|
|
5
5
|
export class TraceService {
|
|
6
|
+
public getTracer() {
|
|
7
|
+
return trace.getTracer('default');
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
public getSpan(): Span {
|
|
7
11
|
return trace.getSpan(context.active());
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
public startSpan(name: string): Span {
|
|
11
|
-
|
|
12
|
-
return tracer.startSpan(name);
|
|
15
|
+
return this.getTracer().startSpan(name);
|
|
13
16
|
}
|
|
14
17
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const OtelValueRecorder: (...dataOrPipes: (string | import("@nestjs/common").PipeTransform<any, any> | import("@nestjs/common").Type<import("@nestjs/common").PipeTransform<any, any>>)[]) => ParameterDecorator;
|