@rsdk/nats.producer.request 5.15.0-next.7 → 5.15.0-next.9

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.
@@ -0,0 +1,7 @@
1
+ import type { NatsMiddleware } from './types';
2
+ /**
3
+ * Middleware для клиента Nats request: создаёт CLIENT-спан `nats.request <subject>`
4
+ * и пробрасывает его контекст в заголовки запроса.
5
+ * Если @opentelemetry/api не установлен — ничего не делает.
6
+ */
7
+ export declare const opentelemetryMiddleware: () => NatsMiddleware;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.opentelemetryMiddleware = void 0;
4
+ const nats_common_1 = require("@rsdk/nats.common");
5
+ /**
6
+ * Middleware для клиента Nats request: создаёт CLIENT-спан `nats.request <subject>`
7
+ * и пробрасывает его контекст в заголовки запроса.
8
+ * Если @opentelemetry/api не установлен — ничего не делает.
9
+ */
10
+ const opentelemetryMiddleware = () => {
11
+ const otel = new nats_common_1.NatsOtelInstrumentation('rsdk:nats.producer.request');
12
+ return async function* (call, options) {
13
+ const span = otel.startSpan(`nats.request ${call.subject}`, {
14
+ kind: 'CLIENT',
15
+ attributes: {
16
+ 'messaging.system': 'nats',
17
+ 'messaging.operation.type': 'send',
18
+ 'messaging.operation.name': 'request',
19
+ 'messaging.destination.name': call.subject,
20
+ },
21
+ });
22
+ const metadata = { ...options.metadata };
23
+ otel.inject(metadata, span);
24
+ try {
25
+ const result = yield* call.next(call.request, { ...options, metadata });
26
+ otel.endSpan(span);
27
+ return result;
28
+ }
29
+ catch (error) {
30
+ otel.endSpan(span, error);
31
+ throw error;
32
+ }
33
+ };
34
+ };
35
+ exports.opentelemetryMiddleware = opentelemetryMiddleware;
36
+ //# sourceMappingURL=opentelemetry.middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opentelemetry.middleware.js","sourceRoot":"","sources":["../../src/middleware/opentelemetry.middleware.ts"],"names":[],"mappings":";;;AAAA,mDAA4D;AAI5D;;;;GAIG;AACI,MAAM,uBAAuB,GAAG,GAAmB,EAAE;IAC1D,MAAM,IAAI,GAAG,IAAI,qCAAuB,CAAC,4BAA4B,CAAC,CAAC;IAEvE,OAAO,KAAK,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,IAAI,CAAC,OAAO,EAAE,EAAE;YAC1D,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,kBAAkB,EAAE,MAAM;gBAC1B,0BAA0B,EAAE,MAAM;gBAClC,0BAA0B,EAAE,SAAS;gBACrC,4BAA4B,EAAE,IAAI,CAAC,OAAO;aAC3C;SACF,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAEzC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAE5B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;YAExE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAEnB,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC1B,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AA5BW,QAAA,uBAAuB,2BA4BlC"}
@@ -16,6 +16,7 @@ exports.ClientsRepository = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const core_1 = require("@rsdk/core");
18
18
  const exception_mapper_middleware_1 = require("../middleware/exception-mapper.middleware");
19
+ const opentelemetry_middleware_1 = require("../middleware/opentelemetry.middleware");
19
20
  const tracing_middleware_1 = require("../middleware/tracing.middleware");
20
21
  const di_1 = require("../options/di");
21
22
  const request_client_factory_1 = require("../request-client.factory");
@@ -34,6 +35,9 @@ let ClientsRepository = class ClientsRepository {
34
35
  this.configContext = configContext;
35
36
  this.factory.use((0, exception_mapper_middleware_1.exceptionMapperMiddleware)());
36
37
  this.factory.use((0, tracing_middleware_1.tracingMiddleware)());
38
+ // Подключается последним, поэтому оборачивает остальные встроенные middleware
39
+ // и видит уже смапленные RSDK-исключения
40
+ this.factory.use((0, opentelemetry_middleware_1.opentelemetryMiddleware)());
37
41
  const { middleware } = options || {};
38
42
  if (middleware) {
39
43
  for (const x of middleware) {
@@ -1 +1 @@
1
- {"version":3,"file":"clients.repository.js","sourceRoot":"","sources":["../../src/repository/clients.repository.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA8D;AAC9D,qCAA0D;AAG1D,2FAAsF;AACtF,yEAAqE;AACrE,sCAA6D;AAE7D,sEAAqE;AAGrE;;;;;GAKG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAOT;IACA;IAPF,OAAO,GAAG,IAAI,GAAG,EAG/B,CAAC;IAEJ,YACmB,OAAiC,EACjC,aAA4B,EAG7C,OAAmC;QAJlB,YAAO,GAAP,OAAO,CAA0B;QACjC,kBAAa,GAAb,aAAa,CAAe;QAK7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,uDAAyB,GAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,sCAAiB,GAAE,CAAC,CAAC;QAEtC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;QAErC,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,aAAa,CACX,UAA6B,EAC7B,EAAkB,EAClB,WAAmB,EACnB,EACE,gBAAgB,EAChB,wBAAwB,GAIzB;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CACrB,WAAW,CAC4B,CAAC;QAC5C,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC;QAC3E,MAAM,qBAAqB,GAAG,IAAI,CAAC,aAAa,CAAC,eAAe,CAC9D,wBAAwB,CACzB,CAAC;QAEF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE;YACjD,aAAa;YACb,qBAAqB;SACtB,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAEtC,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAA;AAzDY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;IAUR,WAAA,IAAA,iBAAQ,GAAE,CAAA;IACV,WAAA,IAAA,eAAM,EAAC,iCAA4B,CAAC,CAAA;qCAHX,iDAAwB;QAClB,oBAAa;GARpC,iBAAiB,CAyD7B"}
1
+ {"version":3,"file":"clients.repository.js","sourceRoot":"","sources":["../../src/repository/clients.repository.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA8D;AAC9D,qCAA0D;AAG1D,2FAAsF;AACtF,qFAAiF;AACjF,yEAAqE;AACrE,sCAA6D;AAE7D,sEAAqE;AAGrE;;;;;GAKG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAOT;IACA;IAPF,OAAO,GAAG,IAAI,GAAG,EAG/B,CAAC;IAEJ,YACmB,OAAiC,EACjC,aAA4B,EAG7C,OAAmC;QAJlB,YAAO,GAAP,OAAO,CAA0B;QACjC,kBAAa,GAAb,aAAa,CAAe;QAK7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,uDAAyB,GAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,sCAAiB,GAAE,CAAC,CAAC;QAEtC,8EAA8E;QAC9E,yCAAyC;QACzC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,kDAAuB,GAAE,CAAC,CAAC;QAE5C,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;QAErC,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,aAAa,CACX,UAA6B,EAC7B,EAAkB,EAClB,WAAmB,EACnB,EACE,gBAAgB,EAChB,wBAAwB,GAIzB;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CACrB,WAAW,CAC4B,CAAC;QAC5C,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC;QAC3E,MAAM,qBAAqB,GAAG,IAAI,CAAC,aAAa,CAAC,eAAe,CAC9D,wBAAwB,CACzB,CAAC;QAEF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE;YACjD,aAAa;YACb,qBAAqB;SACtB,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAEtC,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAA;AA7DY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;IAUR,WAAA,IAAA,iBAAQ,GAAE,CAAA;IACV,WAAA,IAAA,eAAM,EAAC,iCAA4B,CAAC,CAAA;qCAHX,iDAAwB;QAClB,oBAAa;GARpC,iBAAiB,CA6D7B"}
@@ -0,0 +1 @@
1
+ module.exports = require('@rsdk/jest/jest.config.unit');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsdk/nats.producer.request",
3
- "version": "5.15.0-next.7",
3
+ "version": "5.15.0-next.9",
4
4
  "description": "Nats request/reply producer",
5
5
  "license": "Apache License 2.0",
6
6
  "publishConfig": {
@@ -30,5 +30,5 @@
30
30
  "reflect-metadata": "^0.1.12 || ^0.2.0",
31
31
  "rxjs": "^7.1.0"
32
32
  },
33
- "gitHead": "2178d5a4b05b6d2663202712ab364e83f5177cbd"
33
+ "gitHead": "f8da4799d92dd62e259de4c774792cf2ae2788a5"
34
34
  }
@@ -0,0 +1,120 @@
1
+ import {
2
+ context,
3
+ propagation,
4
+ SpanKind,
5
+ SpanStatusCode,
6
+ trace,
7
+ } from '@opentelemetry/api';
8
+ import { B3InjectEncoding, B3Propagator } from '@opentelemetry/propagator-b3';
9
+ import {
10
+ InMemorySpanExporter,
11
+ NodeTracerProvider,
12
+ SimpleSpanProcessor,
13
+ } from '@opentelemetry/sdk-trace-node';
14
+ import type { EventMetadata } from '@rsdk/nats.producer';
15
+
16
+ import { opentelemetryMiddleware } from './opentelemetry.middleware';
17
+ import type { CallOptions, NatsMiddlewareCall } from './types';
18
+
19
+ const SUBJECT = 'test.service.method';
20
+
21
+ const runMiddleware = async (
22
+ next: NatsMiddlewareCall<unknown, string>['next'],
23
+ options: CallOptions = {},
24
+ ): Promise<string | void> => {
25
+ const iterator = opentelemetryMiddleware()<unknown, string>(
26
+ { subject: SUBJECT, request: { id: 1 }, next },
27
+ options,
28
+ );
29
+ const { value } = await iterator.next();
30
+
31
+ return value;
32
+ };
33
+
34
+ describe('opentelemetryMiddleware', () => {
35
+ const exporter = new InMemorySpanExporter();
36
+ const provider = new NodeTracerProvider({
37
+ spanProcessors: [new SimpleSpanProcessor(exporter)],
38
+ });
39
+ const tracer = trace.getTracer('test');
40
+
41
+ beforeAll(() => {
42
+ provider.register({
43
+ propagator: new B3Propagator({
44
+ injectEncoding: B3InjectEncoding.MULTI_HEADER,
45
+ }),
46
+ });
47
+ });
48
+
49
+ afterEach(() => {
50
+ exporter.reset();
51
+ });
52
+
53
+ afterAll(async () => {
54
+ await provider.shutdown();
55
+ trace.disable();
56
+ context.disable();
57
+ propagation.disable();
58
+ });
59
+
60
+ it('creates client span and injects its context into metadata', async () => {
61
+ const callerMetadata: EventMetadata = { requestId: 'r-1' };
62
+ let sentMetadata: EventMetadata | undefined;
63
+
64
+ const parent = tracer.startSpan('parent');
65
+ const result = await context.with(
66
+ trace.setSpan(context.active(), parent),
67
+ () =>
68
+ runMiddleware(
69
+ // eslint-disable-next-line require-yield
70
+ async function* (_request, options) {
71
+ sentMetadata = options.metadata;
72
+
73
+ return 'response';
74
+ },
75
+ { metadata: callerMetadata },
76
+ ),
77
+ );
78
+
79
+ parent.end();
80
+
81
+ const span = exporter
82
+ .getFinishedSpans()
83
+ .find((x) => x.name === `nats.request ${SUBJECT}`);
84
+
85
+ expect(result).toBe('response');
86
+ expect(span?.kind).toBe(SpanKind.CLIENT);
87
+ expect(span?.parentSpanId).toBe(parent.spanContext().spanId);
88
+ expect(span?.status.code).toBe(SpanStatusCode.OK);
89
+ expect(span?.attributes).toMatchObject({
90
+ 'messaging.system': 'nats',
91
+ 'messaging.operation.type': 'send',
92
+ 'messaging.operation.name': 'request',
93
+ 'messaging.destination.name': SUBJECT,
94
+ });
95
+ expect(sentMetadata).toMatchObject({
96
+ requestId: 'r-1',
97
+ 'x-b3-traceid': parent.spanContext().traceId,
98
+ 'x-b3-spanid': span?.spanContext().spanId,
99
+ });
100
+
101
+ // метаданные вызывающего кода не мутируются
102
+ expect(callerMetadata).toEqual({ requestId: 'r-1' });
103
+ });
104
+
105
+ it('marks span as error and rethrows', async () => {
106
+ await expect(
107
+ // eslint-disable-next-line require-yield
108
+ runMiddleware(async function* () {
109
+ throw new Error('request failed');
110
+ }),
111
+ ).rejects.toThrow('request failed');
112
+
113
+ const [span] = exporter.getFinishedSpans();
114
+
115
+ expect(span.status).toEqual({
116
+ code: SpanStatusCode.ERROR,
117
+ message: 'request failed',
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,38 @@
1
+ import { NatsOtelInstrumentation } from '@rsdk/nats.common';
2
+
3
+ import type { NatsMiddleware } from './types';
4
+
5
+ /**
6
+ * Middleware для клиента Nats request: создаёт CLIENT-спан `nats.request <subject>`
7
+ * и пробрасывает его контекст в заголовки запроса.
8
+ * Если @opentelemetry/api не установлен — ничего не делает.
9
+ */
10
+ export const opentelemetryMiddleware = (): NatsMiddleware => {
11
+ const otel = new NatsOtelInstrumentation('rsdk:nats.producer.request');
12
+
13
+ return async function* (call, options) {
14
+ const span = otel.startSpan(`nats.request ${call.subject}`, {
15
+ kind: 'CLIENT',
16
+ attributes: {
17
+ 'messaging.system': 'nats',
18
+ 'messaging.operation.type': 'send',
19
+ 'messaging.operation.name': 'request',
20
+ 'messaging.destination.name': call.subject,
21
+ },
22
+ });
23
+ const metadata = { ...options.metadata };
24
+
25
+ otel.inject(metadata, span);
26
+
27
+ try {
28
+ const result = yield* call.next(call.request, { ...options, metadata });
29
+
30
+ otel.endSpan(span);
31
+
32
+ return result;
33
+ } catch (error) {
34
+ otel.endSpan(span, error);
35
+ throw error;
36
+ }
37
+ };
38
+ };
@@ -3,6 +3,7 @@ import { ConfigContext, PropertyToken } from '@rsdk/core';
3
3
  import { NatsConnection } from 'nats';
4
4
 
5
5
  import { exceptionMapperMiddleware } from '../middleware/exception-mapper.middleware';
6
+ import { opentelemetryMiddleware } from '../middleware/opentelemetry.middleware';
6
7
  import { tracingMiddleware } from '../middleware/tracing.middleware';
7
8
  import { NATS_REQUEST_CLIENTS_OPTIONS } from '../options/di';
8
9
  import { NatsRequestClientsOptions } from '../options/types';
@@ -32,6 +33,10 @@ export class ClientsRepository {
32
33
  this.factory.use(exceptionMapperMiddleware());
33
34
  this.factory.use(tracingMiddleware());
34
35
 
36
+ // Подключается последним, поэтому оборачивает остальные встроенные middleware
37
+ // и видит уже смапленные RSDK-исключения
38
+ this.factory.use(opentelemetryMiddleware());
39
+
35
40
  const { middleware } = options || {};
36
41
 
37
42
  if (middleware) {