@rsdk/nats.producer.request 5.11.5 → 5.11.6-next.0

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"}
@@ -1,4 +1,5 @@
1
1
  import { ConfigContext, PropertyToken } from '@rsdk/core';
2
+ import { NatsMetricsConfig } from '@rsdk/nats.common';
2
3
  import { NatsConnection } from 'nats';
3
4
  import { NatsRequestClientsOptions } from '../options/types';
4
5
  import { NatsRequestClientFactory } from '../request-client.factory';
@@ -12,8 +13,9 @@ import { NatsRequestClient, ServiceDefinition } from '../servers/types';
12
13
  export declare class ClientsRepository {
13
14
  private readonly factory;
14
15
  private readonly configContext;
16
+ private readonly metricsConfig;
15
17
  private readonly clients;
16
- constructor(factory: NatsRequestClientFactory, configContext: ConfigContext, options?: NatsRequestClientsOptions);
18
+ constructor(factory: NatsRequestClientFactory, configContext: ConfigContext, metricsConfig: NatsMetricsConfig, options?: NatsRequestClientsOptions);
17
19
  resolveClient(definition: ServiceDefinition, nc: NatsConnection, clientToken: string, { payloadFormatKey, responsePayloadFormatKey, }: {
18
20
  payloadFormatKey: PropertyToken;
19
21
  responsePayloadFormatKey: PropertyToken;
@@ -15,7 +15,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.ClientsRepository = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const core_1 = require("@rsdk/core");
18
+ const nats_common_1 = require("@rsdk/nats.common");
18
19
  const exception_mapper_middleware_1 = require("../middleware/exception-mapper.middleware");
20
+ const opentelemetry_middleware_1 = require("../middleware/opentelemetry.middleware");
19
21
  const tracing_middleware_1 = require("../middleware/tracing.middleware");
20
22
  const di_1 = require("../options/di");
21
23
  const request_client_factory_1 = require("../request-client.factory");
@@ -28,12 +30,17 @@ const request_client_factory_1 = require("../request-client.factory");
28
30
  let ClientsRepository = class ClientsRepository {
29
31
  factory;
30
32
  configContext;
33
+ metricsConfig;
31
34
  clients = new Map();
32
- constructor(factory, configContext, options) {
35
+ constructor(factory, configContext, metricsConfig, options) {
33
36
  this.factory = factory;
34
37
  this.configContext = configContext;
38
+ this.metricsConfig = metricsConfig;
35
39
  this.factory.use((0, exception_mapper_middleware_1.exceptionMapperMiddleware)());
36
40
  this.factory.use((0, tracing_middleware_1.tracingMiddleware)());
41
+ // Подключается последним, поэтому оборачивает остальные встроенные middleware
42
+ // и видит уже смапленные RSDK-исключения
43
+ this.factory.use((0, opentelemetry_middleware_1.opentelemetryMiddleware)());
37
44
  const { middleware } = options || {};
38
45
  if (middleware) {
39
46
  for (const x of middleware) {
@@ -50,6 +57,7 @@ let ClientsRepository = class ClientsRepository {
50
57
  const client = this.factory.create(definition, nc, {
51
58
  payloadFormat,
52
59
  responsePayloadFormat,
60
+ metrics: this.metricsConfig.toOptions(),
53
61
  });
54
62
  this.clients.set(clientToken, client);
55
63
  return client;
@@ -58,9 +66,10 @@ let ClientsRepository = class ClientsRepository {
58
66
  exports.ClientsRepository = ClientsRepository;
59
67
  exports.ClientsRepository = ClientsRepository = __decorate([
60
68
  (0, common_1.Injectable)(),
61
- __param(2, (0, common_1.Optional)()),
62
- __param(2, (0, common_1.Inject)(di_1.NATS_REQUEST_CLIENTS_OPTIONS)),
69
+ __param(3, (0, common_1.Optional)()),
70
+ __param(3, (0, common_1.Inject)(di_1.NATS_REQUEST_CLIENTS_OPTIONS)),
63
71
  __metadata("design:paramtypes", [request_client_factory_1.NatsRequestClientFactory,
64
- core_1.ConfigContext, Object])
72
+ core_1.ConfigContext,
73
+ nats_common_1.NatsMetricsConfig, Object])
65
74
  ], ClientsRepository);
66
75
  //# sourceMappingURL=clients.repository.js.map
@@ -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;AAC1D,mDAAsD;AAGtD,2FAAsF;AACtF,qFAAiF;AACjF,yEAAqE;AACrE,sCAA6D;AAE7D,sEAAqE;AAGrE;;;;;GAKG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAOT;IACA;IACA;IARF,OAAO,GAAG,IAAI,GAAG,EAG/B,CAAC;IAEJ,YACmB,OAAiC,EACjC,aAA4B,EAC5B,aAAgC,EAGjD,OAAmC;QALlB,YAAO,GAAP,OAAO,CAA0B;QACjC,kBAAa,GAAb,aAAa,CAAe;QAC5B,kBAAa,GAAb,aAAa,CAAmB;QAKjD,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;YACrB,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAEtC,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAA;AA/DY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;IAWR,WAAA,IAAA,iBAAQ,GAAE,CAAA;IACV,WAAA,IAAA,eAAM,EAAC,iCAA4B,CAAC,CAAA;qCAJX,iDAAwB;QAClB,oBAAa;QACb,+BAAiB;GATxC,iBAAiB,CA+D7B"}
@@ -7,6 +7,7 @@ export declare class RequestProducerFactory<T extends MessageType, R extends Mes
7
7
  private readonly requestType;
8
8
  private readonly responseType;
9
9
  private readonly factory;
10
+ private readonly metrics;
10
11
  constructor(client: NatsConnection, requestType: T, responseType: R, config: RequestProducerOptions);
11
12
  createRequestMethod<R>(middleware?: NatsMiddleware): (payload: MessagePayload<T>, options?: CallOptions) => Promise<R>;
12
13
  private getResponseDecoder;
@@ -10,6 +10,7 @@ class RequestProducerFactory {
10
10
  requestType;
11
11
  responseType;
12
12
  factory;
13
+ metrics;
13
14
  constructor(client, requestType, responseType, config) {
14
15
  this.client = client;
15
16
  this.requestType = requestType;
@@ -17,18 +18,20 @@ class RequestProducerFactory {
17
18
  this.client = client;
18
19
  this.requestType = requestType;
19
20
  this.factory = new request_message_factory_1.EventsFactoryRequest(requestType, config);
21
+ this.metrics = new nats_common_1.NatsPublishMetrics(nats_common_1.NatsClientNames.get(client), nats_common_1.NatsMetricsRegistry.from(config.metrics));
20
22
  }
21
23
  createRequestMethod(middleware) {
22
24
  const factory = this.factory;
23
25
  const client = this.client;
26
+ const metrics = this.metrics;
24
27
  const getResponseDecoder = this.getResponseDecoder.bind(this);
25
28
  const subject = this.requestType.$type;
26
29
  const unaryMethod = async function* (request, options) {
27
30
  const message = factory.create(request, options?.metadata);
28
- const res = await client.request(subject, message.payload, {
31
+ const res = await metrics.observe('core', message.payload, () => client.request(subject, message.payload, {
29
32
  headers: message.headers,
30
33
  timeout: options?.timeout ?? 10000,
31
- });
34
+ }));
32
35
  if (options.onTrailer && res.headers) {
33
36
  options.onTrailer(res.headers);
34
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"request-producer.factory.js","sourceRoot":"","sources":["../src/request-producer.factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"request-producer.factory.js","sourceRoot":"","sources":["../src/request-producer.factory.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import type { PayloadFormat } from '@rsdk/events.common';
2
+ import type { NatsMetricsSelection } from '@rsdk/nats.common';
2
3
  import type { EventMetadata } from '@rsdk/nats.producer';
3
4
  export type RequestProducerOptions = {
4
5
  payloadFormat: PayloadFormat;
5
6
  responsePayloadFormat: PayloadFormat;
7
+ metrics?: NatsMetricsSelection;
6
8
  };
7
9
  export type PublishOptions = {
8
10
  metadata?: EventMetadata;
@@ -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.11.5",
3
+ "version": "5.11.6-next.0",
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": "6f45710bc0d9713d061184beeb06afb17db8fa6f"
33
+ "gitHead": "36ecf2006c7e322bc53eb12b81de37ac3719180c"
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
+ };
@@ -1,8 +1,10 @@
1
1
  import { Inject, Injectable, Optional } from '@nestjs/common';
2
2
  import { ConfigContext, PropertyToken } from '@rsdk/core';
3
+ import { NatsMetricsConfig } from '@rsdk/nats.common';
3
4
  import { NatsConnection } from 'nats';
4
5
 
5
6
  import { exceptionMapperMiddleware } from '../middleware/exception-mapper.middleware';
7
+ import { opentelemetryMiddleware } from '../middleware/opentelemetry.middleware';
6
8
  import { tracingMiddleware } from '../middleware/tracing.middleware';
7
9
  import { NATS_REQUEST_CLIENTS_OPTIONS } from '../options/di';
8
10
  import { NatsRequestClientsOptions } from '../options/types';
@@ -25,6 +27,7 @@ export class ClientsRepository {
25
27
  constructor(
26
28
  private readonly factory: NatsRequestClientFactory,
27
29
  private readonly configContext: ConfigContext,
30
+ private readonly metricsConfig: NatsMetricsConfig,
28
31
  @Optional()
29
32
  @Inject(NATS_REQUEST_CLIENTS_OPTIONS)
30
33
  options?: NatsRequestClientsOptions,
@@ -32,6 +35,10 @@ export class ClientsRepository {
32
35
  this.factory.use(exceptionMapperMiddleware());
33
36
  this.factory.use(tracingMiddleware());
34
37
 
38
+ // Подключается последним, поэтому оборачивает остальные встроенные middleware
39
+ // и видит уже смапленные RSDK-исключения
40
+ this.factory.use(opentelemetryMiddleware());
41
+
35
42
  const { middleware } = options || {};
36
43
 
37
44
  if (middleware) {
@@ -67,6 +74,7 @@ export class ClientsRepository {
67
74
  const client = this.factory.create(definition, nc, {
68
75
  payloadFormat,
69
76
  responsePayloadFormat,
77
+ metrics: this.metricsConfig.toOptions(),
70
78
  });
71
79
 
72
80
  this.clients.set(clientToken, client);
@@ -9,7 +9,13 @@ import type {
9
9
  MessagePayload,
10
10
  MessageType,
11
11
  } from '@rsdk/nats.common';
12
- import { JSONMessageCodec, ProtoMessageCodec } from '@rsdk/nats.common';
12
+ import {
13
+ JSONMessageCodec,
14
+ NatsClientNames,
15
+ NatsMetricsRegistry,
16
+ NatsPublishMetrics,
17
+ ProtoMessageCodec,
18
+ } from '@rsdk/nats.common';
13
19
  import { type NatsConnection } from 'nats';
14
20
 
15
21
  import type { CallOptions, NatsMiddleware } from './middleware/types';
@@ -21,6 +27,7 @@ export class RequestProducerFactory<
21
27
  R extends MessageType,
22
28
  > {
23
29
  private readonly factory: EventsFactoryRequest<T>;
30
+ private readonly metrics: NatsPublishMetrics;
24
31
 
25
32
  constructor(
26
33
  private readonly client: NatsConnection,
@@ -31,6 +38,10 @@ export class RequestProducerFactory<
31
38
  this.client = client;
32
39
  this.requestType = requestType;
33
40
  this.factory = new EventsFactoryRequest(requestType, config);
41
+ this.metrics = new NatsPublishMetrics(
42
+ NatsClientNames.get(client),
43
+ NatsMetricsRegistry.from(config.metrics),
44
+ );
34
45
  }
35
46
 
36
47
  createRequestMethod<R>(
@@ -38,6 +49,7 @@ export class RequestProducerFactory<
38
49
  ): (payload: MessagePayload<T>, options?: CallOptions) => Promise<R> {
39
50
  const factory = this.factory;
40
51
  const client = this.client;
52
+ const metrics = this.metrics;
41
53
  const getResponseDecoder = this.getResponseDecoder.bind(this);
42
54
  const subject = this.requestType.$type;
43
55
 
@@ -47,10 +59,12 @@ export class RequestProducerFactory<
47
59
  ): AsyncGenerator<never, R, undefined> {
48
60
  const message = factory.create(request, options?.metadata);
49
61
 
50
- const res = await client.request(subject, message.payload, {
51
- headers: message.headers,
52
- timeout: options?.timeout ?? 10000,
53
- });
62
+ const res = await metrics.observe('core', message.payload, () =>
63
+ client.request(subject, message.payload, {
64
+ headers: message.headers,
65
+ timeout: options?.timeout ?? 10000,
66
+ }),
67
+ );
54
68
 
55
69
  if (options.onTrailer && res.headers) {
56
70
  options.onTrailer(res.headers);
@@ -1,9 +1,11 @@
1
1
  import type { PayloadFormat } from '@rsdk/events.common';
2
+ import type { NatsMetricsSelection } from '@rsdk/nats.common';
2
3
  import type { EventMetadata } from '@rsdk/nats.producer';
3
4
 
4
5
  export type RequestProducerOptions = {
5
6
  payloadFormat: PayloadFormat;
6
7
  responsePayloadFormat: PayloadFormat;
8
+ metrics?: NatsMetricsSelection;
7
9
  };
8
10
 
9
11
  export type PublishOptions = {