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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-otel",
3
- "version": "3.0.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, MetricType,
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(name: string, options?: MetricOptions) {
26
- return getOrCreateObservableCounter(name, 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
- getObservableGauge(name: string, options?: MetricOptions) {
30
- return getOrCreateObservableGauge(name, options);
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(name: string, options?: MetricOptions) {
34
- return getOrCreateObservableUpDownCounter(name, options);
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 { context, trace } from '@opentelemetry/api';
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 context.with(trace.setSpan(context.active(), currentSpan), () => {
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.apply(this, args).finally(() => {
21
- span.end();
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
  };