@rsdk/core 5.0.0-next.1 → 5.0.0-next.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/CHANGELOG.md +14 -0
- package/dist/config/metadata/decorators/inject-property.decorator.d.ts +1 -1
- package/dist/config/metadata/decorators/property.decorator.d.ts +1 -1
- package/dist/config/parsers/url/exception.d.ts +0 -1
- package/dist/config/parsers/url/url.parser.d.ts +0 -1
- package/dist/config/reload/config-reload.events.d.ts +0 -1
- package/dist/exceptions.handling/global-exceptions.filter.d.ts +1 -1
- package/dist/exceptions.handling/global-exceptions.filter.js +2 -5
- package/dist/exceptions.handling/global-exceptions.filter.js.map +1 -1
- package/dist/health/indicators.abstract/ping.indicator.d.ts +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +4 -7
- package/dist/index.js.map +1 -1
- package/dist/logging/logger-initializing.module.d.ts +1 -1
- package/dist/logging/logger-initializing.module.js +1 -1
- package/dist/logging/logger-initializing.module.js.map +1 -1
- package/dist/metrics/metadata/decorators/metric.decorator.js +1 -2
- package/dist/metrics/metadata/decorators/metric.decorator.js.map +1 -1
- package/dist/metrics/metric-initializing.module.d.ts +1 -1
- package/dist/metrics/metric-initializing.module.js +1 -1
- package/dist/metrics/metric-initializing.module.js.map +1 -1
- package/dist/platform.context.d.ts +5 -5
- package/dist/platform.context.js +28 -24
- package/dist/platform.context.js.map +1 -1
- package/dist/platform.module.d.ts +1 -1
- package/dist/platform.module.js +10 -9
- package/dist/platform.module.js.map +1 -1
- package/dist/tracing/constants.d.ts +12 -9
- package/dist/tracing/constants.js +13 -11
- package/dist/tracing/constants.js.map +1 -1
- package/dist/tracing/index.d.ts +3 -3
- package/dist/tracing/index.js +3 -3
- package/dist/tracing/index.js.map +1 -1
- package/dist/tracing/opentelemetry/decorators/index.js.map +1 -0
- package/dist/tracing/{decorators → opentelemetry/decorators}/no-span.decorator.d.ts +1 -1
- package/dist/tracing/{decorators → opentelemetry/decorators}/no-span.decorator.js +3 -3
- package/dist/tracing/opentelemetry/decorators/no-span.decorator.js.map +1 -0
- package/dist/tracing/{decorators → opentelemetry/decorators}/span.decorator.js +5 -4
- package/dist/tracing/opentelemetry/decorators/span.decorator.js.map +1 -0
- package/dist/tracing/opentelemetry/index.d.ts +1 -0
- package/dist/tracing/{services → opentelemetry}/index.js +1 -1
- package/dist/tracing/opentelemetry/index.js.map +1 -0
- package/dist/tracing/opentelemetry/opentelemetry.helpers.d.ts +28 -0
- package/dist/tracing/opentelemetry/opentelemetry.helpers.js +77 -0
- package/dist/tracing/opentelemetry/opentelemetry.helpers.js.map +1 -0
- package/dist/tracing/opentelemetry/opentelemetry.metadata.d.ts +7 -0
- package/dist/tracing/opentelemetry/opentelemetry.metadata.js +27 -0
- package/dist/tracing/opentelemetry/opentelemetry.metadata.js.map +1 -0
- package/dist/tracing/opentelemetry/opentelemetry.wrapper.d.ts +6 -0
- package/dist/tracing/opentelemetry/opentelemetry.wrapper.js +71 -0
- package/dist/tracing/opentelemetry/opentelemetry.wrapper.js.map +1 -0
- package/dist/tracing/tracing.actx.d.ts +1 -0
- package/dist/tracing/tracing.actx.js +9 -0
- package/dist/tracing/tracing.actx.js.map +1 -0
- package/dist/tracing/tracing.interceptor.d.ts +20 -4
- package/dist/tracing/tracing.interceptor.js +59 -9
- package/dist/tracing/tracing.interceptor.js.map +1 -1
- package/dist/tracing/tracing.module.d.ts +10 -4
- package/dist/tracing/tracing.module.js +63 -21
- package/dist/tracing/tracing.module.js.map +1 -1
- package/dist/tracing/types.d.ts +9 -11
- package/dist/tracing/types.js +0 -20
- package/dist/tracing/types.js.map +1 -1
- package/dist/transport/protocol.detector.d.ts +13 -1
- package/dist/transport/protocol.detector.js +17 -24
- package/dist/transport/protocol.detector.js.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +1 -4
- package/dist/types/index.js.map +1 -1
- package/dist/types/options.d.ts +2 -0
- package/dist/types/plugins.d.ts +8 -3
- package/dist/types/plugins.js.map +1 -1
- package/dist/types/transports.d.ts +19 -9
- package/dist/types/transports.js +3 -1
- package/dist/types/transports.js.map +1 -1
- package/package.json +9 -4
- package/src/exceptions.handling/global-exceptions.filter.ts +5 -6
- package/src/index.ts +3 -3
- package/src/logging/logger-initializing.module.ts +2 -2
- package/src/metrics/metric-initializing.module.ts +2 -2
- package/src/platform.context.ts +41 -32
- package/src/platform.module.ts +9 -8
- package/src/tracing/constants.ts +15 -9
- package/src/tracing/index.ts +3 -3
- package/src/tracing/opentelemetry/decorators/no-span.decorator.ts +10 -0
- package/src/tracing/{decorators → opentelemetry/decorators}/span.decorator.ts +8 -4
- package/src/tracing/opentelemetry/index.ts +1 -0
- package/src/tracing/opentelemetry/opentelemetry.helpers.ts +89 -0
- package/src/tracing/opentelemetry/opentelemetry.metadata.ts +24 -0
- package/src/tracing/opentelemetry/opentelemetry.wrapper.ts +90 -0
- package/src/tracing/tracing.actx.ts +8 -0
- package/src/tracing/tracing.interceptor.ts +65 -8
- package/src/tracing/tracing.module.ts +64 -23
- package/src/tracing/types.ts +9 -28
- package/src/transport/protocol.detector.ts +19 -25
- package/src/types/index.ts +0 -2
- package/src/types/options.ts +3 -0
- package/src/types/plugins.ts +9 -2
- package/src/types/transports.ts +27 -11
- package/dist/tracing/decorators/index.js.map +0 -1
- package/dist/tracing/decorators/no-span.decorator.js.map +0 -1
- package/dist/tracing/decorators/span.decorator.js.map +0 -1
- package/dist/tracing/request-metadata.module.d.ts +0 -5
- package/dist/tracing/request-metadata.module.js +0 -21
- package/dist/tracing/request-metadata.module.js.map +0 -1
- package/dist/tracing/services/index.d.ts +0 -1
- package/dist/tracing/services/index.js.map +0 -1
- package/dist/tracing/services/trace.injector.d.ts +0 -12
- package/dist/tracing/services/trace.injector.js +0 -154
- package/dist/tracing/services/trace.injector.js.map +0 -1
- package/dist/tracing/tracing.config.d.ts +0 -6
- package/dist/tracing/tracing.config.js +0 -39
- package/dist/tracing/tracing.config.js.map +0 -1
- package/dist/tracing/utils/create-span.d.ts +0 -10
- package/dist/tracing/utils/create-span.js +0 -20
- package/dist/tracing/utils/create-span.js.map +0 -1
- package/dist/transport/get-transport-id.d.ts +0 -5
- package/dist/transport/get-transport-id.js +0 -14
- package/dist/transport/get-transport-id.js.map +0 -1
- package/dist/types/tracing.headers-extractor.d.ts +0 -15
- package/dist/types/tracing.headers-extractor.js +0 -67
- package/dist/types/tracing.headers-extractor.js.map +0 -1
- package/src/tracing/decorators/no-span.decorator.ts +0 -10
- package/src/tracing/request-metadata.module.ts +0 -8
- package/src/tracing/services/index.ts +0 -1
- package/src/tracing/services/trace.injector.ts +0 -190
- package/src/tracing/tracing.config.ts +0 -25
- package/src/tracing/utils/create-span.ts +0 -20
- package/src/transport/get-transport-id.ts +0 -20
- package/src/types/tracing.headers-extractor.ts +0 -51
- /package/dist/tracing/{decorators → opentelemetry/decorators}/index.d.ts +0 -0
- /package/dist/tracing/{decorators → opentelemetry/decorators}/index.js +0 -0
- /package/dist/tracing/{decorators → opentelemetry/decorators}/span.decorator.d.ts +0 -0
- /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
|
+
}
|
|
@@ -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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
5
|
-
import {
|
|
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 {
|
|
9
|
+
import type { PlatformOptions } from '../types';
|
|
11
10
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
53
|
+
{ provide: HEADER_MAPPINGS, useValue: mappings },
|
|
30
54
|
],
|
|
31
55
|
};
|
|
32
56
|
}
|
|
33
57
|
|
|
34
58
|
// Можно добавить включение и выключение при изменении конфига.
|
|
35
59
|
async onModuleInit(): Promise<void> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
90
|
+
if (this.trace && this.context) {
|
|
91
|
+
const span = this.trace.getSpan(this.context.active());
|
|
52
92
|
|
|
53
|
-
|
|
93
|
+
span?.addEvent('log', { ...msg, level });
|
|
94
|
+
}
|
|
54
95
|
});
|
|
55
96
|
}
|
|
56
97
|
}
|
package/src/tracing/types.ts
CHANGED
|
@@ -1,28 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
}
|
package/src/types/index.ts
CHANGED
package/src/types/options.ts
CHANGED
|
@@ -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<
|
package/src/types/plugins.ts
CHANGED
|
@@ -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 {
|
|
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
|
}
|
package/src/types/transports.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type { ArgumentsHost
|
|
2
|
-
import type {
|
|
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
|
-
* @
|
|
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"}
|