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.
Files changed (54) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +50 -39
  3. package/examples/nestjs-prom-grafana-tempo/package.json +40 -37
  4. package/examples/nestjs-prom-grafana-tempo/src/tracing.ts +5 -5
  5. package/lib/interfaces/opentelemetry-options.interface.d.ts +2 -2
  6. package/lib/metrics/decorators/common.js +2 -2
  7. package/lib/metrics/decorators/common.js.map +1 -1
  8. package/lib/metrics/decorators/counter.js +2 -2
  9. package/lib/metrics/decorators/counter.js.map +1 -1
  10. package/lib/metrics/decorators/histogram.d.ts +1 -0
  11. package/lib/metrics/decorators/histogram.js +12 -0
  12. package/lib/metrics/decorators/histogram.js.map +1 -0
  13. package/lib/metrics/decorators/index.d.ts +2 -1
  14. package/lib/metrics/decorators/index.js +2 -1
  15. package/lib/metrics/decorators/index.js.map +1 -1
  16. package/lib/metrics/decorators/observable.d.ts +3 -0
  17. package/lib/metrics/decorators/observable.js +24 -0
  18. package/lib/metrics/decorators/observable.js.map +1 -0
  19. package/lib/metrics/metric-data.d.ts +12 -5
  20. package/lib/metrics/metric-data.js +53 -24
  21. package/lib/metrics/metric-data.js.map +1 -1
  22. package/lib/metrics/metric.service.d.ts +7 -6
  23. package/lib/metrics/metric.service.js +11 -8
  24. package/lib/metrics/metric.service.js.map +1 -1
  25. package/lib/middleware/api-metrics.middleware.d.ts +5 -5
  26. package/lib/middleware/api-metrics.middleware.js +17 -17
  27. package/lib/middleware/api-metrics.middleware.js.map +1 -1
  28. package/lib/opentelemetry-core.module.js.map +1 -1
  29. package/lib/tracing/decorators/span.js +2 -3
  30. package/lib/tracing/decorators/span.js.map +1 -1
  31. package/lib/tracing/decorators/span.spec.d.ts +1 -0
  32. package/lib/tracing/decorators/span.spec.js +58 -0
  33. package/lib/tracing/decorators/span.spec.js.map +1 -0
  34. package/lib/tracing/trace.service.d.ts +1 -0
  35. package/lib/tracing/trace.service.js +4 -2
  36. package/lib/tracing/trace.service.js.map +1 -1
  37. package/lib/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +31 -32
  39. package/src/interfaces/opentelemetry-options.interface.ts +3 -3
  40. package/src/metrics/decorators/common.ts +6 -4
  41. package/src/metrics/decorators/counter.ts +5 -9
  42. package/src/metrics/decorators/histogram.ts +10 -0
  43. package/src/metrics/decorators/observable.ts +28 -0
  44. package/src/metrics/metric-data.ts +89 -29
  45. package/src/metrics/metric.service.ts +28 -14
  46. package/src/middleware/api-metrics.middleware.ts +19 -19
  47. package/src/opentelemetry-core.module.ts +3 -3
  48. package/src/tracing/decorators/span.spec.ts +48 -0
  49. package/src/tracing/decorators/span.ts +4 -11
  50. package/src/tracing/trace.service.ts +5 -2
  51. package/lib/metrics/decorators/value-recorder.d.ts +0 -1
  52. package/lib/metrics/decorators/value-recorder.js +0 -12
  53. package/lib/metrics/decorators/value-recorder.js.map +0 -1
  54. 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": "2.6.1",
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 --detectOpenHandles",
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.25.0",
41
- "@opentelemetry/api": "^1.0.3",
42
- "@opentelemetry/api-metrics": "^0.25.0",
43
- "@opentelemetry/metrics": "^0.24.0",
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": "^13.1.0",
49
- "@commitlint/config-conventional": "^13.1.0",
50
- "@nestjs/common": "^8.0.10",
51
- "@nestjs/core": "^8.0.10",
52
- "@nestjs/platform-express": "^8.0.10",
53
- "@nestjs/platform-fastify": "^8.0.10",
54
- "@nestjs/testing": "^8.0.10",
55
- "@opentelemetry/exporter-prometheus": "^0.25.0",
56
- "@opentelemetry/sdk-node": "^0.25.0",
57
- "@types/jest": "^27.0.2",
58
- "@types/node": "^16.9.6",
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": "4.31.2",
61
- "@typescript-eslint/parser": "4.31.2",
62
- "eslint": "^7.32.0",
63
- "eslint-config-airbnb-base": "^14.2.1",
64
- "eslint-config-airbnb-typescript": "^14.0.1",
65
- "eslint-plugin-import": "^2.25.1",
66
- "husky": "^7.0.2",
67
- "jest": "^27.2.1",
68
- "lint-staged": "^11.1.2",
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.3.0",
69
+ "rxjs": "^7.5.1",
72
70
  "supertest": "^6.1.6",
73
- "ts-jest": "^27.0.5",
74
- "typescript": "4.4.3"
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 { Labels } from '@opentelemetry/api-metrics';
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
- defaultLabels?: Labels,
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, getOrCreateValueRecorder, MetricType } from '../metric-data';
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, MetricType.Counter, { description, ...options });
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, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<(...args: any[]
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, MetricType.Counter, { description, ...options });
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, getOrCreateValueRecorder, MetricType } from '../metric-data';
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, MetricType.Counter, options);
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, MetricType.UpDownCounter, options);
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, ValueRecorder,
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 = Counter | UpDownCounter | ValueRecorder;
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
- 'ValueRecorder' = 'ValueRecorder',
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 getOrCreateValueRecorder(
17
- name: string, type: MetricType, options: MetricOptions,
18
- ): ValueRecorder {
26
+ export function getOrCreateHistogram(
27
+ name: string,
28
+ options: MetricOptions,
29
+ ): Histogram {
19
30
  if (meterData.has(name)) {
20
- return meterData.get(name) as ValueRecorder;
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
- switch (type) {
26
- case MetricType.ValueRecorder:
27
- const valueRecorder = meter.createValueRecorder(name, options);
28
- meterData.set(name, valueRecorder);
29
- return valueRecorder;
30
- default:
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 getOrCreateCounter(
36
- name: string, type: MetricType, options: MetricOptions,
37
- ): Counter | UpDownCounter {
55
+ export function getOrCreateUpDownCounter(
56
+ name: string,
57
+ options: MetricOptions,
58
+ ): UpDownCounter {
38
59
  if (meterData.has(name)) {
39
- return meterData.get(name) as Counter | UpDownCounter;
60
+ return meterData.get(name) as UpDownCounter;
40
61
  }
41
62
 
42
63
  const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
43
64
 
44
- switch (type) {
45
- case MetricType.Counter:
46
- const counter = meter.createCounter(name, options);
47
- meterData.set(name, counter);
48
- return counter;
49
- case MetricType.UpDownCounter:
50
- const upDownCounter = meter.createUpDownCounter(name, options);
51
- meterData.set(name, upDownCounter);
52
- return upDownCounter;
53
- default:
54
- throw new Error(`Unknown type: ${type}`);
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
- Counter, UpDownCounter, ValueRecorder, MetricOptions,
3
+ MetricOptions, ObservableResult,
4
4
  } from '@opentelemetry/api-metrics';
5
5
  import {
6
- getOrCreateCounter, getOrCreateValueRecorder, MetricType,
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 this.getOrCreateCounter(name, MetricType.Counter, options);
14
+ return getOrCreateCounter(name, options);
13
15
  }
14
16
 
15
17
  getUpDownCounter(name: string, options?: MetricOptions) {
16
- return this.getOrCreateCounter(name, MetricType.UpDownCounter, options);
18
+ return getOrCreateUpDownCounter(name, options);
17
19
  }
18
20
 
19
- getValueRecorder(name: string, options?: MetricOptions) {
20
- return this.getOrCreateValueRecorder(name, MetricType.ValueRecorder, options);
21
+ getHistogram(name: string, options?: MetricOptions) {
22
+ return getOrCreateHistogram(name, options);
21
23
  }
22
24
 
23
- private getOrCreateValueRecorder(
24
- name: string, type: MetricType, options: MetricOptions,
25
- ): ValueRecorder {
26
- return getOrCreateValueRecorder(name, type, options);
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
- private getOrCreateCounter(
30
- name: string, type: MetricType, options: MetricOptions,
31
- ): Counter | UpDownCounter {
32
- return getOrCreateCounter(name, type, options);
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, Labels, ValueRecorder } from '@opentelemetry/api-metrics';
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: ValueRecorder;
40
+ private requestDuration: Histogram;
41
41
 
42
- private requestSizeValueRecorder: ValueRecorder;
42
+ private requestSizeHistogram: Histogram;
43
43
 
44
- private responseSizeValueRecorder: ValueRecorder;
44
+ private responseSizeHistogram: Histogram;
45
45
 
46
- private defaultLabels: Labels;
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 = [], defaultLabels = {},
83
+ timeBuckets = [], requestSizeBuckets = [], responseSizeBuckets = [], defaultAttributes = {},
84
84
  ignoreUndefinedRoutes = false,
85
85
  } = options?.metrics?.apiMetrics;
86
86
 
87
- this.defaultLabels = defaultLabels;
87
+ this.defaultAttributes = defaultAttributes;
88
88
  this.ignoreUndefinedRoutes = ignoreUndefinedRoutes;
89
89
 
90
- this.requestDuration = this.metricService.getValueRecorder('http_request_duration_seconds', {
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.requestSizeValueRecorder = this.metricService.getValueRecorder('http_request_size_bytes', {
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.responseSizeValueRecorder = this.metricService.getValueRecorder('http_response_size_bytes', {
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.bind({ method, path }).add(1);
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 labels: Labels = {
129
- method, status, path, ...this.defaultLabels,
128
+ const attributes: Attributes = {
129
+ method, status, path, ...this.defaultAttributes,
130
130
  };
131
131
 
132
- this.requestSizeValueRecorder.bind(labels).record(requestLength);
133
- this.responseSizeValueRecorder.bind(labels).record(responseLength);
132
+ this.requestSizeHistogram.record(requestLength, attributes);
133
+ this.responseSizeHistogram.record(responseLength, attributes);
134
134
 
135
- this.responseTotal.bind(labels).add(1);
136
- this.requestDuration.bind(labels).record(time / 1000);
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
- // TODO: Review what should be appropriate for redirects.
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 { context, trace } from '@opentelemetry/api';
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 context.with(trace.setSpan(context.active(), currentSpan), () => {
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
- const tracer = trace.getTracer('default');
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;