@rsdk/core 5.0.0-next.2 → 5.0.0-next.4

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 (134) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/config/metadata/decorators/inject-property.decorator.d.ts +1 -1
  3. package/dist/config/metadata/decorators/property.decorator.d.ts +1 -1
  4. package/dist/config/parsers/url/exception.d.ts +0 -1
  5. package/dist/config/parsers/url/url.parser.d.ts +0 -1
  6. package/dist/config/reload/config-reload.events.d.ts +0 -1
  7. package/dist/exceptions.handling/global-exceptions.filter.d.ts +1 -1
  8. package/dist/exceptions.handling/global-exceptions.filter.js +2 -5
  9. package/dist/exceptions.handling/global-exceptions.filter.js.map +1 -1
  10. package/dist/health/indicators.abstract/ping.indicator.d.ts +0 -1
  11. package/dist/index.d.ts +2 -3
  12. package/dist/index.js +4 -7
  13. package/dist/index.js.map +1 -1
  14. package/dist/logging/logger-initializing.module.d.ts +1 -1
  15. package/dist/logging/logger-initializing.module.js +1 -1
  16. package/dist/logging/logger-initializing.module.js.map +1 -1
  17. package/dist/metrics/metadata/decorators/metric.decorator.js +1 -2
  18. package/dist/metrics/metadata/decorators/metric.decorator.js.map +1 -1
  19. package/dist/metrics/metric-initializing.module.d.ts +1 -1
  20. package/dist/metrics/metric-initializing.module.js +1 -1
  21. package/dist/metrics/metric-initializing.module.js.map +1 -1
  22. package/dist/platform.context.d.ts +5 -5
  23. package/dist/platform.context.js +28 -24
  24. package/dist/platform.context.js.map +1 -1
  25. package/dist/platform.module.d.ts +1 -1
  26. package/dist/platform.module.js +10 -9
  27. package/dist/platform.module.js.map +1 -1
  28. package/dist/tracing/constants.d.ts +12 -9
  29. package/dist/tracing/constants.js +13 -11
  30. package/dist/tracing/constants.js.map +1 -1
  31. package/dist/tracing/index.d.ts +3 -3
  32. package/dist/tracing/index.js +3 -3
  33. package/dist/tracing/index.js.map +1 -1
  34. package/dist/tracing/opentelemetry/decorators/index.js.map +1 -0
  35. package/dist/tracing/{decorators → opentelemetry/decorators}/no-span.decorator.d.ts +1 -1
  36. package/dist/tracing/{decorators → opentelemetry/decorators}/no-span.decorator.js +3 -3
  37. package/dist/tracing/opentelemetry/decorators/no-span.decorator.js.map +1 -0
  38. package/dist/tracing/{decorators → opentelemetry/decorators}/span.decorator.js +5 -4
  39. package/dist/tracing/opentelemetry/decorators/span.decorator.js.map +1 -0
  40. package/dist/tracing/opentelemetry/index.d.ts +1 -0
  41. package/dist/tracing/{services → opentelemetry}/index.js +1 -1
  42. package/dist/tracing/opentelemetry/index.js.map +1 -0
  43. package/dist/tracing/opentelemetry/opentelemetry.helpers.d.ts +28 -0
  44. package/dist/tracing/opentelemetry/opentelemetry.helpers.js +77 -0
  45. package/dist/tracing/opentelemetry/opentelemetry.helpers.js.map +1 -0
  46. package/dist/tracing/opentelemetry/opentelemetry.metadata.d.ts +7 -0
  47. package/dist/tracing/opentelemetry/opentelemetry.metadata.js +27 -0
  48. package/dist/tracing/opentelemetry/opentelemetry.metadata.js.map +1 -0
  49. package/dist/tracing/opentelemetry/opentelemetry.wrapper.d.ts +6 -0
  50. package/dist/tracing/opentelemetry/opentelemetry.wrapper.js +71 -0
  51. package/dist/tracing/opentelemetry/opentelemetry.wrapper.js.map +1 -0
  52. package/dist/tracing/tracing.actx.d.ts +1 -0
  53. package/dist/tracing/tracing.actx.js +9 -0
  54. package/dist/tracing/tracing.actx.js.map +1 -0
  55. package/dist/tracing/tracing.interceptor.d.ts +20 -4
  56. package/dist/tracing/tracing.interceptor.js +59 -9
  57. package/dist/tracing/tracing.interceptor.js.map +1 -1
  58. package/dist/tracing/tracing.module.d.ts +10 -4
  59. package/dist/tracing/tracing.module.js +63 -21
  60. package/dist/tracing/tracing.module.js.map +1 -1
  61. package/dist/tracing/types.d.ts +9 -11
  62. package/dist/tracing/types.js +0 -20
  63. package/dist/tracing/types.js.map +1 -1
  64. package/dist/transport/protocol.detector.d.ts +13 -1
  65. package/dist/transport/protocol.detector.js +17 -24
  66. package/dist/transport/protocol.detector.js.map +1 -1
  67. package/dist/types/index.d.ts +0 -1
  68. package/dist/types/index.js +1 -4
  69. package/dist/types/index.js.map +1 -1
  70. package/dist/types/options.d.ts +2 -0
  71. package/dist/types/plugins.d.ts +8 -3
  72. package/dist/types/plugins.js.map +1 -1
  73. package/dist/types/transports.d.ts +19 -9
  74. package/dist/types/transports.js +3 -1
  75. package/dist/types/transports.js.map +1 -1
  76. package/package.json +9 -4
  77. package/src/exceptions.handling/global-exceptions.filter.ts +5 -6
  78. package/src/index.ts +3 -3
  79. package/src/logging/logger-initializing.module.ts +2 -2
  80. package/src/metrics/metric-initializing.module.ts +2 -2
  81. package/src/platform.context.ts +41 -32
  82. package/src/platform.module.ts +9 -8
  83. package/src/tracing/constants.ts +15 -9
  84. package/src/tracing/index.ts +3 -3
  85. package/src/tracing/opentelemetry/decorators/no-span.decorator.ts +10 -0
  86. package/src/tracing/{decorators → opentelemetry/decorators}/span.decorator.ts +8 -4
  87. package/src/tracing/opentelemetry/index.ts +1 -0
  88. package/src/tracing/opentelemetry/opentelemetry.helpers.ts +89 -0
  89. package/src/tracing/opentelemetry/opentelemetry.metadata.ts +24 -0
  90. package/src/tracing/opentelemetry/opentelemetry.wrapper.ts +90 -0
  91. package/src/tracing/tracing.actx.ts +8 -0
  92. package/src/tracing/tracing.interceptor.ts +65 -8
  93. package/src/tracing/tracing.module.ts +64 -23
  94. package/src/tracing/types.ts +9 -28
  95. package/src/transport/protocol.detector.ts +19 -25
  96. package/src/types/index.ts +0 -2
  97. package/src/types/options.ts +3 -0
  98. package/src/types/plugins.ts +9 -2
  99. package/src/types/transports.ts +27 -11
  100. package/dist/tracing/decorators/index.js.map +0 -1
  101. package/dist/tracing/decorators/no-span.decorator.js.map +0 -1
  102. package/dist/tracing/decorators/span.decorator.js.map +0 -1
  103. package/dist/tracing/request-metadata.module.d.ts +0 -5
  104. package/dist/tracing/request-metadata.module.js +0 -21
  105. package/dist/tracing/request-metadata.module.js.map +0 -1
  106. package/dist/tracing/services/index.d.ts +0 -1
  107. package/dist/tracing/services/index.js.map +0 -1
  108. package/dist/tracing/services/trace.injector.d.ts +0 -12
  109. package/dist/tracing/services/trace.injector.js +0 -154
  110. package/dist/tracing/services/trace.injector.js.map +0 -1
  111. package/dist/tracing/tracing.config.d.ts +0 -6
  112. package/dist/tracing/tracing.config.js +0 -39
  113. package/dist/tracing/tracing.config.js.map +0 -1
  114. package/dist/tracing/utils/create-span.d.ts +0 -10
  115. package/dist/tracing/utils/create-span.js +0 -20
  116. package/dist/tracing/utils/create-span.js.map +0 -1
  117. package/dist/transport/get-transport-id.d.ts +0 -5
  118. package/dist/transport/get-transport-id.js +0 -14
  119. package/dist/transport/get-transport-id.js.map +0 -1
  120. package/dist/types/tracing.headers-extractor.d.ts +0 -15
  121. package/dist/types/tracing.headers-extractor.js +0 -67
  122. package/dist/types/tracing.headers-extractor.js.map +0 -1
  123. package/src/tracing/decorators/no-span.decorator.ts +0 -10
  124. package/src/tracing/request-metadata.module.ts +0 -8
  125. package/src/tracing/services/index.ts +0 -1
  126. package/src/tracing/services/trace.injector.ts +0 -190
  127. package/src/tracing/tracing.config.ts +0 -25
  128. package/src/tracing/utils/create-span.ts +0 -20
  129. package/src/transport/get-transport-id.ts +0 -20
  130. package/src/types/tracing.headers-extractor.ts +0 -51
  131. /package/dist/tracing/{decorators → opentelemetry/decorators}/index.d.ts +0 -0
  132. /package/dist/tracing/{decorators → opentelemetry/decorators}/index.js +0 -0
  133. /package/dist/tracing/{decorators → opentelemetry/decorators}/span.decorator.d.ts +0 -0
  134. /package/src/tracing/{decorators → opentelemetry/decorators}/index.ts +0 -0
@@ -0,0 +1,89 @@
1
+ import type { AttributeValue, Span } from '@opentelemetry/api';
2
+ import { SpanStatusCode } from '@opentelemetry/api';
3
+ import { isPrimitive, isRecord, normalizer, Size } from '@rsdk/common';
4
+ import { catchError, mergeMap, type Observable, of, throwError } from 'rxjs';
5
+
6
+ /**
7
+ * Maximum span attribute size
8
+ */
9
+ export const MAX_ATTRIBUTE_SIZE = new Size(100, 'kb');
10
+
11
+ /**
12
+ * Makes correct opentelemetry span attribute from any value
13
+ * @param data unknown value of any type
14
+ * @returns AttributeValue
15
+ */
16
+ export const toAttribute = (data: unknown): AttributeValue => {
17
+ if (isPrimitive(data)) {
18
+ return data;
19
+ }
20
+
21
+ if (isRecord(data) || Array.isArray(data)) {
22
+ const serialized = JSON.stringify(normalizer()(data));
23
+
24
+ return serialized.length <= MAX_ATTRIBUTE_SIZE.bytes()
25
+ ? serialized
26
+ : `data exceeds limit of ${MAX_ATTRIBUTE_SIZE.bytes()} bytes`;
27
+ }
28
+
29
+ return 'data is undefined';
30
+ };
31
+
32
+ /**
33
+ * Adds data to span with key "result" and ends it.
34
+ * @param span Span
35
+ * @param data Any piece of data
36
+ */
37
+ export const endWithResult = (span: Span, data: unknown): void => {
38
+ span.setAttribute('result', toAttribute(data));
39
+ span.setStatus({ code: SpanStatusCode.OK });
40
+ span.end();
41
+ };
42
+
43
+ /**
44
+ * Adds error object to span and ends it
45
+ * @param span Span
46
+ * @param error Error
47
+ */
48
+ export const endWithError = (span: Span, error: Error): void => {
49
+ span.recordException(error);
50
+ span.setStatus({
51
+ code: SpanStatusCode.ERROR,
52
+ message: error.message,
53
+ });
54
+ span.end();
55
+ };
56
+
57
+ export const processSyncResult = <T = unknown>(result: T, span: Span): T => {
58
+ endWithResult(span, result);
59
+ return result;
60
+ };
61
+
62
+ export const processObservableResult = <T = unknown>(
63
+ result: Observable<T>,
64
+ span: Span,
65
+ ): Observable<T> =>
66
+ result.pipe(
67
+ mergeMap((result) => {
68
+ endWithResult(span, result);
69
+ return of(result);
70
+ }),
71
+ catchError((error) => {
72
+ endWithError(span, error);
73
+ return throwError(() => error);
74
+ }),
75
+ );
76
+
77
+ export const processPromiseResult = <T = unknown>(
78
+ result: Promise<T>,
79
+ span: Span,
80
+ ): Promise<T> =>
81
+ result
82
+ .then((result) => {
83
+ endWithResult(span, result);
84
+ return result;
85
+ })
86
+ .catch((error) => {
87
+ endWithError(span, error);
88
+ throw error;
89
+ });
@@ -0,0 +1,24 @@
1
+ import assert from 'node:assert';
2
+
3
+ export const OTEL_METADATA = {
4
+ NO_SPAN: 'NO_SPAN_METADATA',
5
+ IS_WRAPPED: 'OPEN_TELEMETRY_TRACE_METADATA_ACTIVE',
6
+ };
7
+
8
+ export const isNoSpan = (prototype: object): boolean => {
9
+ assert.ok(prototype);
10
+
11
+ return Reflect.hasMetadata(OTEL_METADATA.NO_SPAN, prototype);
12
+ };
13
+
14
+ export const isWrapped = (prototype: object): boolean => {
15
+ assert.ok(prototype);
16
+
17
+ return Reflect.hasMetadata(OTEL_METADATA.IS_WRAPPED, prototype);
18
+ };
19
+
20
+ export const setWrapped = (prototype: object): void => {
21
+ assert.ok(prototype);
22
+
23
+ Reflect.defineMetadata(OTEL_METADATA.IS_WRAPPED, true, prototype);
24
+ };
@@ -0,0 +1,90 @@
1
+ import type { TraceAPI } from '@opentelemetry/api';
2
+ import { Assert, text } from '@rsdk/common';
3
+ import { redecorate } from '@rsdk/decorators';
4
+ import type { ILogger } from '@rsdk/logging';
5
+ import { LoggerFactory } from '@rsdk/logging';
6
+ import { Observable } from 'rxjs';
7
+
8
+ import {
9
+ endWithError,
10
+ processObservableResult,
11
+ processPromiseResult,
12
+ processSyncResult,
13
+ toAttribute,
14
+ } from './opentelemetry.helpers';
15
+ import { isNoSpan, isWrapped, setWrapped } from './opentelemetry.metadata';
16
+
17
+ export class OpentelemetryWrapper {
18
+ private readonly logger: ILogger = LoggerFactory.create(OpentelemetryWrapper);
19
+ private readonly trace: TraceAPI | null;
20
+
21
+ constructor() {
22
+ try {
23
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
24
+ const { trace } = require('@opentelemetry/api');
25
+
26
+ this.trace = trace;
27
+ } catch {
28
+ this.trace = null;
29
+ this.logger.debug(text`
30
+ @opentelemetry/api is not installed.
31
+ @Span() decorator will not make any effect.`);
32
+ }
33
+ }
34
+
35
+ wrap(
36
+ cls: any,
37
+ // eslint-disable-next-line @typescript-eslint/ban-types
38
+ original: Function,
39
+ descriptor?: TypedPropertyDescriptor<any>,
40
+ ): void {
41
+ if (!this.trace || isWrapped(original) || isNoSpan(original)) {
42
+ return;
43
+ }
44
+
45
+ const spanName = `${cls.constructor.name} -> ${original.name}`;
46
+
47
+ this.logger.debug(`Wrapping method: ${spanName}`);
48
+
49
+ const tracer = this.trace.getTracer(`rsdk:${cls.constructor.name}`);
50
+
51
+ const wrapped = {
52
+ [original.name](...args: unknown[]): unknown {
53
+ return tracer.startActiveSpan(spanName, (span) => {
54
+ span.setAttribute('args', toAttribute(args));
55
+
56
+ try {
57
+ const result = original.apply(this, args);
58
+
59
+ if (result instanceof Promise) {
60
+ return processPromiseResult(result, span);
61
+ }
62
+
63
+ if (result instanceof Observable) {
64
+ return processObservableResult(result, span);
65
+ }
66
+
67
+ return processSyncResult(result, span);
68
+ } catch (error) {
69
+ Assert.isError(error);
70
+ endWithError(span, error);
71
+ throw error;
72
+ }
73
+ });
74
+ },
75
+ }[original.name];
76
+
77
+ redecorate(original, wrapped);
78
+
79
+ if (descriptor) {
80
+ descriptor.value = wrapped;
81
+ } else {
82
+ cls[original.name] = wrapped;
83
+ }
84
+
85
+ /**
86
+ * Отмечает метод, как обёрнутый (чтобы избежать повторного оборачивания)
87
+ */
88
+ setWrapped(wrapped);
89
+ }
90
+ }
@@ -0,0 +1,8 @@
1
+ import { createAsyncContextProvider } from '@rsdk/actx';
2
+
3
+ export const TracingHeaders = createAsyncContextProvider<
4
+ Record<string, string>
5
+ >({
6
+ name: 'TracingHeaders',
7
+ rewritable: false,
8
+ });
@@ -3,22 +3,79 @@ import type {
3
3
  ExecutionContext,
4
4
  NestInterceptor,
5
5
  } from '@nestjs/common';
6
- import { Injectable } from '@nestjs/common';
7
- import { RequestIdProvider } from '@rsdk/tracing';
6
+ import { Inject, Injectable } from '@nestjs/common';
8
7
  import type { Observable } from 'rxjs';
9
8
 
10
- import { TracingHeadersExtractor } from '../types/tracing.headers-extractor';
9
+ import { InternalException } from '../exceptions';
10
+ import { ILogger, InjectLogger } from '../logging';
11
+ import { ProtocolDetector } from '../transport/protocol.detector';
12
+ import type {
13
+ ITransport,
14
+ PlatformAppPlugin,
15
+ WithGenericHeaders,
16
+ } from '../types';
17
+ import { APP_PLUGINS, APP_TRANSPORTS } from '../types';
11
18
 
12
- import { X_REQUEST_ID } from './constants';
19
+ import { HEADER_MAPPINGS } from './constants';
20
+ import { TracingHeaders } from './tracing.actx';
21
+ import type { TracingHeaderMappings } from './types';
13
22
 
23
+ /**
24
+ * Extracts headers that should be passed to all logging messages
25
+ * and external calls
26
+ */
14
27
  @Injectable()
15
28
  export class TracingInterceptor implements NestInterceptor {
16
- constructor(private headersExtractor: TracingHeadersExtractor) {}
29
+ /**
30
+ * Extractors can be either transports or additional strategies
31
+ * supplied by plugins
32
+ */
33
+ private readonly extractors = new Map<string, WithGenericHeaders>();
34
+
35
+ constructor(
36
+ @InjectLogger(TracingInterceptor) private logger: ILogger,
37
+ @Inject(APP_TRANSPORTS) private transports: Set<ITransport>,
38
+ @Inject(APP_PLUGINS) private plugins: Set<PlatformAppPlugin>,
39
+ @Inject(HEADER_MAPPINGS) private mappings: TracingHeaderMappings[],
40
+ private detector: ProtocolDetector,
41
+ ) {
42
+ for (const transport of this.transports) {
43
+ this.extractors.set(transport.getProtocol(), transport);
44
+ }
45
+
46
+ for (const plugin of this.plugins) {
47
+ for (const x of plugin.tracingHeadersExtractors?.() ?? []) {
48
+ this.extractors.set(x.protocol, x.extractor);
49
+ }
50
+ }
51
+ }
52
+
53
+ intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> {
54
+ const protocol = this.detector.getProtocol(ctx) ?? ctx.getType();
55
+
56
+ const extractor = this.extractors.get(protocol);
57
+ if (!extractor) {
58
+ throw new InternalException(
59
+ `No headers extractor for protocol: ${protocol}`,
60
+ );
61
+ }
62
+
63
+ const headers: Record<string, string> = {};
64
+
65
+ for (const aliases of this.mappings) {
66
+ const key = aliases[0];
67
+ const value = aliases
68
+ .map((x) => extractor.extractHeaders(ctx).get(x))
69
+ .filter((x) => x)
70
+ .at(0);
71
+
72
+ headers[key] = value || '[missing]';
73
+ }
74
+
75
+ this.logger.trace('Extracted traceable headers', { headers });
17
76
 
18
- intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
19
- const h = this.headersExtractor.extract(context).getHeaders();
77
+ TracingHeaders.set(headers);
20
78
 
21
- RequestIdProvider.set(h[X_REQUEST_ID]);
22
79
  return next.handle();
23
80
  }
24
81
  }
@@ -1,56 +1,97 @@
1
1
  import type { DynamicModule, OnModuleInit } from '@nestjs/common';
2
2
  import { Module } from '@nestjs/common';
3
3
  import { APP_INTERCEPTOR } from '@nestjs/core';
4
- import type { SpanContext } from '@opentelemetry/api';
5
- import { context, trace } from '@opentelemetry/api';
4
+ import type { ContextAPI, TraceAPI } from '@opentelemetry/api';
5
+ import { text } from '@rsdk/common';
6
6
  import { ILogger, LoggerFactory } from '@rsdk/logging';
7
- import { RequestIdProvider } from '@rsdk/tracing';
8
7
 
9
8
  import { InjectLogger } from '../logging';
10
- import { TracingHeadersExtractor } from '../types/tracing.headers-extractor';
9
+ import type { PlatformOptions } from '../types';
11
10
 
12
- import { RequestMetadataModule } from './request-metadata.module';
13
- import { TracingModuleConfig } from './tracing.config';
11
+ import {
12
+ DEFAULT_TRACING_HEADERS,
13
+ DEFAULT_TRACING_OPTIONS,
14
+ HEADER_MAPPINGS,
15
+ } from './constants';
16
+ import { TracingHeaders } from './tracing.actx';
14
17
  import { TracingInterceptor } from './tracing.interceptor';
18
+ import type { TracingHeaderMappings } from './types';
19
+
20
+ export interface TracingOptions {
21
+ includeDefault: boolean;
22
+ additionalMappings?: TracingHeaderMappings[];
23
+ }
15
24
 
16
25
  @Module({})
17
26
  export class TracingModule implements OnModuleInit {
18
- constructor(
19
- @InjectLogger(TracingModule) private readonly logger: ILogger,
20
- private readonly config: TracingModuleConfig,
21
- ) {}
27
+ private trace: TraceAPI | null = null;
28
+ private context: ContextAPI | null = null;
29
+
30
+ constructor(@InjectLogger(TracingModule) private readonly logger: ILogger) {}
31
+
32
+ static forRoot({
33
+ tracing = DEFAULT_TRACING_OPTIONS,
34
+ plugins = [],
35
+ }: PlatformOptions): DynamicModule {
36
+ const mappings: TracingHeaderMappings[] = [];
37
+
38
+ if (tracing?.includeDefault) {
39
+ mappings.push(...DEFAULT_TRACING_HEADERS);
40
+ }
41
+
42
+ for (const plugin of plugins) {
43
+ mappings.push(...(plugin.additionalTracingHeaders?.() ?? []));
44
+ }
45
+
46
+ mappings.push(...(tracing.additionalMappings ?? []));
22
47
 
23
- static forRoot(): DynamicModule {
24
48
  return {
49
+ global: true,
25
50
  module: TracingModule,
26
- imports: [RequestMetadataModule],
27
51
  providers: [
28
52
  { provide: APP_INTERCEPTOR, useClass: TracingInterceptor },
29
- TracingHeadersExtractor,
53
+ { provide: HEADER_MAPPINGS, useValue: mappings },
30
54
  ],
31
55
  };
32
56
  }
33
57
 
34
58
  // Можно добавить включение и выключение при изменении конфига.
35
59
  async onModuleInit(): Promise<void> {
36
- LoggerFactory.applyInstrumentations((record) => {
37
- record['request_id'] = RequestIdProvider.get();
38
- const span = trace.getSpan(context.active());
39
- if (!span) {
40
- return;
60
+ if (!this.context || !this.trace) {
61
+ try {
62
+ const { context, trace } = await import('@opentelemetry/api');
63
+
64
+ this.context = context;
65
+ this.trace = trace;
66
+ } catch {
67
+ this.logger.debug(text`
68
+ @opentelemetry/api is not installed. Opentelemetry metadata will
69
+ not appear in log messages.
70
+ `);
41
71
  }
42
- const spanContext: SpanContext = span.spanContext();
72
+ }
73
+
74
+ LoggerFactory.applyInstrumentations((record) => {
75
+ Object.assign(record, TracingHeaders.get() || {});
76
+
77
+ if (this.trace && this.context) {
78
+ const { traceId, spanId } =
79
+ this.trace.getSpan(this.context.active())?.spanContext() ?? {};
43
80
 
44
- record['trace_id'] = spanContext.traceId;
45
- record['span_id'] = spanContext.spanId;
81
+ record['trace_id'] = traceId;
82
+ record['span_id'] = spanId;
83
+ }
46
84
  });
85
+
47
86
  this.logger.info('Tracing is enabled');
48
87
 
49
88
  this.logger.debug('Attaching log messages to spans...');
50
89
  LoggerFactory.onMessage((level, msg) => {
51
- const span = trace.getSpan(context.active());
90
+ if (this.trace && this.context) {
91
+ const span = this.trace.getSpan(this.context.active());
52
92
 
53
- span?.addEvent('log', { ...msg, level });
93
+ span?.addEvent('log', { ...msg, level });
94
+ }
54
95
  });
55
96
  }
56
97
  }
@@ -1,28 +1,9 @@
1
- import { randomUUID } from 'node:crypto';
2
-
3
- import { X_REQUEST_ID } from './constants';
4
-
5
- export type Headers = {
6
- [X_REQUEST_ID]: string;
7
- };
8
-
9
- export const tracingHeaders = [X_REQUEST_ID] as const;
10
-
11
- export class TracingHeaders {
12
- constructor(headers: Partial<Headers>) {
13
- this.headers = {
14
- ...headers,
15
- [X_REQUEST_ID]: headers[X_REQUEST_ID] ?? this.createRequestId(),
16
- } as Headers;
17
- }
18
-
19
- getHeaders(): Headers {
20
- return this.headers;
21
- }
22
-
23
- private createRequestId(): string {
24
- return randomUUID();
25
- }
26
-
27
- private headers: Headers;
28
- }
1
+ /**
2
+ * The idea of this mappings is to enable aliases. Ex:
3
+ * ['requestId', 'X-Request-Id', 'X-Requestid] means that first of this
4
+ * headers found will be taken. And then the first one will be used.
5
+ *
6
+ * In other way, the first element is main header name, and other are
7
+ * possible aliases.
8
+ */
9
+ export type TracingHeaderMappings = [string, ...string[]];
@@ -1,36 +1,30 @@
1
1
  import type { ArgumentsHost } from '@nestjs/common';
2
2
 
3
- import { InternalException } from '../exceptions';
4
3
  import type { ITransport } from '../types';
5
4
 
5
+ /**
6
+ * This class is used to detect current protocol (transport type).
7
+ * Mostly used in global enhancers to decide: "to work of not to work"
8
+ */
6
9
  export class ProtocolDetector {
7
10
  constructor(private transports: ITransport[]) {}
8
11
 
9
- getTransport(context: ArgumentsHost): ITransport | undefined {
10
- if (this.transports.length === 0) {
11
- return;
12
- }
13
- const matchers = this.transports.map((tr) => ({
14
- matched: tr.matchByContext(context),
15
- tr,
16
- }));
17
- const matched = matchers.filter((matched) => matched.matched);
18
- if (matched.length > 1) {
19
- throw new InternalException('So many matchers for transports', {
20
- cause: {
21
- matchers,
22
- matched,
23
- context,
24
- },
25
- });
26
- }
27
- if (matched.length === 0) {
28
- return;
29
- }
30
- return matched[0].tr;
31
- }
32
-
12
+ /**
13
+ * Detects transport's protocol by ArgumentHost (context)
14
+ */
33
15
  getProtocol(context: ArgumentsHost): string | undefined {
34
16
  return this.getTransport(context)?.getProtocol();
35
17
  }
18
+
19
+ /**
20
+ * Checks that transport's protocol matches the provided one.
21
+ * (just convenience method)
22
+ */
23
+ matchProtocol(context: ArgumentsHost, protocol: string): boolean {
24
+ return this.getProtocol(context) === protocol;
25
+ }
26
+
27
+ private getTransport(context: ArgumentsHost): ITransport | undefined {
28
+ return this.transports.filter((x) => x.matchByContext(context)).pop();
29
+ }
36
30
  }
@@ -1,5 +1,3 @@
1
- export { getTransportId, TransportId } from '../transport/get-transport-id';
2
-
3
1
  export * from './metadata';
4
2
  export * from './options';
5
3
  export * from './transports';
@@ -2,6 +2,7 @@ import type { RequiredFields } from '@rsdk/common';
2
2
  import type { NestModuleDefinition } from '@rsdk/common.nestjs';
3
3
 
4
4
  import type { ConfigModuleOptions } from '../config';
5
+ import type { TracingOptions } from '../tracing';
5
6
 
6
7
  import type { PlatformAppPlugin } from './plugins';
7
8
  import type { IPrimaryTransport, ITransport } from './transports';
@@ -77,6 +78,8 @@ export interface PlatformOptions extends PlatformManifestPathOptions {
77
78
  * Plugins extend application functionality.
78
79
  */
79
80
  plugins?: PlatformAppPlugin[];
81
+
82
+ tracing?: TracingOptions;
80
83
  }
81
84
 
82
85
  export type PlatformAppOptions = RequiredFields<
@@ -7,8 +7,9 @@ import type {
7
7
  IErrorsSender,
8
8
  IErrorsTransformer,
9
9
  } from '../exceptions.handling';
10
+ import type { TracingHeaderMappings } from '../tracing';
10
11
 
11
- import type { ProtocolTracingHeadersExtractor } from './transports';
12
+ import type { WithGenericHeaders } from './transports';
12
13
 
13
14
  export type SpecifiedTransports = Readonly<string[]>;
14
15
  export type AppropriateTransports = SpecifiedTransports | 'any';
@@ -34,9 +35,10 @@ export interface PlatformAppPlugin<
34
35
  extractors?(): ResourceExtractor[];
35
36
 
36
37
  tracingHeadersExtractors?(): {
37
- extractor: ProtocolTracingHeadersExtractor;
38
38
  protocol: string;
39
+ extractor: WithGenericHeaders;
39
40
  }[];
41
+
40
42
  /**
41
43
  * Returns list of appropriate transports.
42
44
  *
@@ -79,4 +81,9 @@ export interface PlatformAppPlugin<
79
81
  * @param consumer MiddlewareConsumer
80
82
  */
81
83
  configureMiddleware?(consumer: MiddlewareConsumer): void;
84
+
85
+ /**
86
+ * Additional header mappings
87
+ */
88
+ additionalTracingHeaders?(): TracingHeaderMappings[];
82
89
  }
@@ -1,5 +1,9 @@
1
- import type { ArgumentsHost, ExecutionContext } from '@nestjs/common';
2
- import type { Controller, INestApplication } from '@nestjs/common/interfaces';
1
+ import type { ArgumentsHost } from '@nestjs/common';
2
+ import type {
3
+ Controller,
4
+ ExecutionContext,
5
+ INestApplication,
6
+ } from '@nestjs/common/interfaces';
3
7
  import type { AbstractHttpAdapter } from '@nestjs/core';
4
8
  import type { MicroserviceOptions } from '@nestjs/microservices';
5
9
  import type { Constructor } from '@rsdk/common';
@@ -10,14 +14,32 @@ import type {
10
14
  IErrorsSender,
11
15
  IErrorsTransformer,
12
16
  } from '../exceptions.handling';
13
- import type { TracingHeaders } from '../tracing/types';
14
17
 
15
18
  import type { NestModuleDefinitions } from './options';
16
19
 
20
+ export interface GenericHeaders {
21
+ get(key: string): string | undefined;
22
+ }
23
+
24
+ /**
25
+ * Every transport has generic some sort of headers/metadata
26
+ * that can be represented as key/value pairs. But implementation
27
+ * differs. This interface is made to generalize this headers
28
+ *
29
+ * NOTE: Not only transports but also plugins can implement
30
+ * this interface
31
+ */
32
+ export interface WithGenericHeaders {
33
+ extractHeaders(context: ExecutionContext): GenericHeaders;
34
+ }
35
+
36
+ export const hasGenericHeaders = (x: unknown): x is WithGenericHeaders =>
37
+ typeof (x as any)?.getHeaders === 'function';
38
+
17
39
  /**
18
40
  * Base functionality of HTTP or microservice transport
19
41
  */
20
- export interface ITransport {
42
+ export interface ITransport extends WithGenericHeaders {
21
43
  /**
22
44
  * @returns protocol in text format
23
45
  */
@@ -54,8 +76,6 @@ export interface ITransport {
54
76
  * Modules for register metrics, healthchecks, configs and supporting tools, such as clients
55
77
  */
56
78
  modules?(): NestModuleDefinitions;
57
-
58
- getHeaderExtractor(): ProtocolTracingHeadersExtractor;
59
79
  }
60
80
 
61
81
  /**
@@ -75,7 +95,7 @@ export interface IPrimaryTransport extends ITransport {
75
95
  */
76
96
  export interface IMicroserviceTransport extends ITransport {
77
97
  /**
78
- * @retures options ready to be passed to
98
+ * @returns options ready to be passed to
79
99
  * NestFactory.createMicroservice() or
80
100
  * app.connectMicroservice()
81
101
  */
@@ -137,7 +157,3 @@ export const isMicroserviceTransport = (
137
157
  t: ITransport,
138
158
  ): t is IMicroserviceTransport =>
139
159
  typeof (t as IMicroserviceTransport).createMicroserviceOptions === 'function';
140
-
141
- export interface ProtocolTracingHeadersExtractor {
142
- extract(ctx: ExecutionContext): TracingHeaders;
143
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tracing/decorators/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,mDAAiC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"no-span.decorator.js","sourceRoot":"","sources":["../../../src/tracing/decorators/no-span.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAE7C,4CAAyC;AAEzC;;;GAGG;AACI,MAAM,MAAM,GAAG,GAAoB,EAAE,CAC1C,IAAA,oBAAW,EAAC,qBAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;AADnC,QAAA,MAAM,UAC6B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"span.decorator.js","sourceRoot":"","sources":["../../../src/tracing/decorators/span.decorator.ts"],"names":[],"mappings":";;;AAAA,uCAA+C;AAE/C,iDAAgE;AAChE,0CAA4C;AAE5C,MAAM,eAAe,GAAG,IAAI,sBAAe,EAAE,CAAC;AAE9C;;;;GAIG;AACI,MAAM,IAAI,GAAG,GAAqC,EAAE;AACzD,+DAA+D;AAC/D,UACE,MAAc,EACd,UAA4B,EAC5B,UAAyC;IAEzC,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,IAAI,yCAA4B,EAAE,CAAC;IAC3C,CAAC;IACD,IAAI,UAAU,IAAI,UAAU,EAAE,CAAC;QAC7B,wBAAa,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IACD,MAAM,SAAS,GAAI,MAAc,CAAC,SAAS,IAAI,MAAM,CAAC;IAEtD,KAAK,MAAM,IAAI,IAAI,eAAe,CAAC,iBAAiB,CAAC,SAAS,CAAC,EAAE,CAAC;QAChE,IAAI,IAAI,EAAE,CAAC;YACT,wBAAa,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AArBS,QAAA,IAAI,QAqBb"}
@@ -1,5 +0,0 @@
1
- /**
2
- * Модуль для хранения и получения данных из аскин локал стораджа
3
- */
4
- export declare class RequestMetadataModule {
5
- }