@tramvai/module-opentelemetry 4.41.99

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/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # @tramvai/module-opentelemetry
2
+
3
+ ## Explanation
4
+
5
+ Telemetry and distributed tracing is a important part of complete application monitoring.
6
+
7
+ Tramvai provides a deep integration with [OpenTelemetry](https://opentelemetry.io/) Node.js SDK, with custom automatic instrumentation for internal Node.js modules and core Tramvai mechanisms.
8
+
9
+ ## Usage
10
+
11
+ ### Installation
12
+
13
+ You need to install `@tramvai/module-opentelemetry`:
14
+
15
+ ```bash npm2yarn
16
+ npm i --save @tramvai/module-module-opentelemetry
17
+ ```
18
+
19
+ Then connect to the project:
20
+
21
+ ```ts
22
+ import { createApp } from '@tramvai/core';
23
+ import { OpenTelemetryModule } from '@tramvai/module-opentelemetry';
24
+
25
+ createApp({
26
+ name: 'tincoin',
27
+ modules: [ OpenTelemetryModule ],
28
+ });
29
+ ```
30
+
31
+ This will enable OpenTelemetry with automatic instrumentation, and provide `Tracer` into the application.
32
+
33
+ ### Create spans
34
+
35
+ Simplest way to wrap operation in span is to use `Tracer.trace` method, e.g.:
36
+
37
+ ```ts
38
+ import { OPENTELEMETRY_TRACER_TOKEN } from '@tramvai/module-opentelemetry';
39
+
40
+ const provider = {
41
+ provide: commandLineListTokens.resolvePageDeps,
42
+ useFactory: ({ tracer, apiService }) => {
43
+ return async function getSmth() {
44
+ tracer?.trace('get-smth', async (span) => {
45
+ // set attribute to the span
46
+ span.setAttribute('key', 'value');
47
+
48
+ // span will be ended automatically after `apiService.get` method resolves or rejects
49
+ return apiService.get('/smth');
50
+ });
51
+ }
52
+ },
53
+ deps: {
54
+ // tracer exists only server-side
55
+ tracer: optional(OPENTELEMETRY_TRACER_TOKEN),
56
+ apiService: API_SERVICE_TOKEN,
57
+ },
58
+ };
59
+ ```
60
+
61
+ For more flexibility methods [Tracer.startActiveSpan](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Tracer.html#startActiveSpan) and [Tracer.startSpan](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api.Tracer.html#startSpan) is available.
62
+
63
+ `Tracer.trace` and other span creation methods calls can be nested, with [automatic](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-context-async-hooks) context propagation.
64
+
65
+ ### Export traces
66
+
67
+ For export traces to OpenTelemetry collector, you need to provide custom span processor and exporter with `OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN` token.
68
+
69
+ If you have a gRPC collector (e.g. Jaeger), library `@opentelemetry/exporter-trace-otlp-grpc` can be used:
70
+
71
+ ```ts
72
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
73
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
74
+ import { OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN } from '@tramvai/module-opentelemetry';
75
+
76
+ const provider = {
77
+ provide: OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN,
78
+ useFactory: ({ envManager }) => {
79
+ const url = envManager.get('OTEL_GRPC_COLLECTOR_ENDPOINT');
80
+ const exporter = new OTLPTraceExporter({ url });
81
+
82
+ return new BatchSpanProcessor(exporter);
83
+ },
84
+ deps: {
85
+ envManager: ENV_MANAGER_TOKEN,
86
+ },
87
+ };
88
+ ```
89
+
90
+ ### Resource attributes
91
+
92
+ If you need to extend [resource](https://opentelemetry.io/docs/concepts/resources/) attributes, use `OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN` token. Also, `@opentelemetry/semantic-conventions` library contains some of attribute names.
93
+
94
+ ```ts
95
+ import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
96
+ import { OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN } from '@tramvai/module-opentelemetry';
97
+
98
+ const provider = {
99
+ provide: OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN,
100
+ useFactory: ({ appInfo }) => {
101
+ return {
102
+ [ATTR_SERVICE_NAME]: appInfo.appName,
103
+ }
104
+ },
105
+ deps: {
106
+ appInfo: APP_INFO_TOKEN,
107
+ },
108
+ };
109
+ ```
110
+
111
+ ### Active span
112
+
113
+ If you need to set attributes to the active span, use `Tracer.getActiveSpan` method:
114
+
115
+ ```ts
116
+ // tracer from OPENTELEMETRY_TRACER_TOKEN
117
+ function doSmt({ tracer }) {
118
+ const span = tracer.getActiveSpan();
119
+
120
+ // span can be absent, for example in `init` or `listen` command line stages
121
+ span?.setAttribute('key', 'value');
122
+ }
123
+ ```
124
+
125
+ ## Automatic instrumentation
126
+
127
+ [OpenTelemetry instrumentation libraries](https://www.npmjs.com/package/@opentelemetry/instrumentation) is not supported, because of it limitations:
128
+ - instrumentations are registered before the module to instrument is require'ed
129
+ - modules are not included in a bundle
130
+
131
+ Instead, module provides custom instrumentation for all significant cases:
132
+
133
+ ### Server module
134
+
135
+ All incoming requests are automatically wrapped in **root span**, where current request and response parameters will be set (path, method, status code, error).
136
+
137
+ Naming and attributes follow [semantic conventions](https://opentelemetry.io/docs/specs/semconv/http/http-spans/).
138
+
139
+ ### Context propagation
140
+
141
+ `OpenTelemetryModule` provides [context propagation](https://opentelemetry.io/docs/languages/js/propagation/) for incoming and outgoing requests.
142
+
143
+ ### Logs correlation
144
+
145
+ `OpenTelemetryModule` inject context for [logs correlation](https://opentelemetry.io/docs/specs/otel/logs/#log-correlation).
146
+
147
+ All application logs will be extended with current span and trace ids in `spanId` and `traceId` properties.
148
+
149
+ ## Debug and testing
150
+
151
+ By default, in `development` mode [ConsoleSpanExporter](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_sdk_trace_base.ConsoleSpanExporter.html) is used, which prints all spans to the console.
152
+
153
+ For testing purposes, you can use [InMemorySpanExporter](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_sdk_trace_base.InMemorySpanExporter.html).
@@ -0,0 +1,4 @@
1
+ export * from './tokens';
2
+ export declare class OpenTelemetryModule {
3
+ }
4
+ //# sourceMappingURL=browser.d.ts.map
package/lib/browser.js ADDED
@@ -0,0 +1,15 @@
1
+ import { __decorate } from 'tslib';
2
+ import { Module } from '@tramvai/core';
3
+ export { OPENTELEMETRY_PROVIDER_CONFIG_TOKEN, OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN, OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN, OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN, OPENTELEMETRY_PROVIDER_TOKEN, OPENTELEMETRY_TRACER_TOKEN } from './tokens.browser.js';
4
+
5
+ let OpenTelemetryModule = class OpenTelemetryModule {
6
+ };
7
+ OpenTelemetryModule = __decorate([
8
+ Module({
9
+ imports: [],
10
+ providers: [],
11
+ })
12
+ ], OpenTelemetryModule);
13
+ // todo declareModule!
14
+
15
+ export { OpenTelemetryModule };
@@ -0,0 +1,11 @@
1
+ export declare const providers: import("@tramvai/core").Provider<{
2
+ tracer: import("../tokens").TramvaiTracer & {
3
+ __type?: "base token" | undefined;
4
+ };
5
+ metricsServicesRegistry: import("@tramvai/tokens-metrics").MetricsServicesRegistryInterface & {
6
+ __type?: "base token" | undefined;
7
+ };
8
+ }, import("@tramvai/http-client").HttpClientInterceptor & {
9
+ __type?: "multi token" | undefined;
10
+ }>[];
11
+ //# sourceMappingURL=httpClient.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { provide } from '@tramvai/core';
2
+ import { DEFAULT_HTTP_CLIENT_INTERCEPTORS } from '@tramvai/tokens-http-client';
3
+ import { METRICS_SERVICES_REGISTRY_TOKEN } from '@tramvai/tokens-metrics';
4
+ import { propagation, context, SpanKind } from '@opentelemetry/api';
5
+ import { ATTR_HTTP_REQUEST_METHOD, ATTR_SERVER_ADDRESS, ATTR_URL_PATH, ATTR_URL_QUERY, ATTR_URL_SCHEME, ATTR_URL_FULL, ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions';
6
+ import { OPENTELEMETRY_TRACER_TOKEN } from '../tokens.es.js';
7
+
8
+ const providers = [
9
+ provide({
10
+ provide: DEFAULT_HTTP_CLIENT_INTERCEPTORS,
11
+ useFactory: ({ tracer, metricsServicesRegistry }) => {
12
+ return (request, next) => {
13
+ var _a, _b, _c, _d;
14
+ const url = (_a = request.url) !== null && _a !== void 0 ? _a :
15
+ // todo add leading slash before path if needed
16
+ (request.baseUrl ? `${request.baseUrl}${request.path}` : (_b = request.path) !== null && _b !== void 0 ? _b : '');
17
+ const parsedUrl = new URL(request.query ? `${url}?${new URLSearchParams(request.query).toString()}` : url);
18
+ const serviceName = (_c = metricsServicesRegistry.getServiceName(url, request)) !== null && _c !== void 0 ? _c : 'unknown';
19
+ const method = (_d = request.method) !== null && _d !== void 0 ? _d : 'GET';
20
+ // propagate context from outgoing request
21
+ // https://opentelemetry.io/docs/languages/js/propagation/#manual-context-propagation
22
+ const output = {};
23
+ propagation.inject(context.active(), output);
24
+ if (output.traceparent) {
25
+ if (!request.headers) {
26
+ request.headers = {};
27
+ }
28
+ request.headers.traceparent = output.traceparent;
29
+ }
30
+ if (output.tracestate) {
31
+ if (!request.headers) {
32
+ request.headers = {};
33
+ }
34
+ request.headers.tracestate = output.tracestate;
35
+ }
36
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client
37
+ return tracer.trace(`${method} ${serviceName}`, { kind: SpanKind.CLIENT }, (span) => {
38
+ // todo: move custom tramvai attrs to constants
39
+ /**
40
+ * - `tramvai.http-client.transport` - internal HTTP client, `node-fetch` at server-side (possible `undici` in future), `fetch` at client-side
41
+ */
42
+ span.setAttribute('tramvai.scope', 'http-client');
43
+ span.setAttribute('tramvai.http-client.transport', 'node-fetch');
44
+ span.setAttribute(ATTR_HTTP_REQUEST_METHOD, method);
45
+ span.setAttribute(ATTR_SERVER_ADDRESS, parsedUrl.hostname);
46
+ span.setAttribute(ATTR_URL_PATH, parsedUrl.pathname);
47
+ span.setAttribute(ATTR_URL_QUERY, parsedUrl.search.replace('?', ''));
48
+ span.setAttribute(ATTR_URL_SCHEME, parsedUrl.protocol.replace(':', ''));
49
+ span.setAttribute(ATTR_URL_FULL, parsedUrl.href);
50
+ // todo req/res headers?
51
+ // todo http.request.resend_count?
52
+ return next(request)
53
+ .then((response) => {
54
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
55
+ const meta = response.__meta;
56
+ if (meta) {
57
+ writeMetaAttributes(span, meta);
58
+ }
59
+ return response;
60
+ })
61
+ .catch((error) => {
62
+ var _a;
63
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, (_a = error === null || error === void 0 ? void 0 : error.status) !== null && _a !== void 0 ? _a : 500);
64
+ const meta = error.__meta;
65
+ if (meta) {
66
+ writeMetaAttributes(span, meta);
67
+ }
68
+ throw error;
69
+ });
70
+ });
71
+ };
72
+ },
73
+ deps: {
74
+ tracer: OPENTELEMETRY_TRACER_TOKEN,
75
+ metricsServicesRegistry: METRICS_SERVICES_REGISTRY_TOKEN,
76
+ },
77
+ }),
78
+ ];
79
+ function writeMetaAttributes(span, meta) {
80
+ const { cache, CIRCUIT_BREAKER } = meta;
81
+ if (cache) {
82
+ span.setAttribute('tramvai.http-client.cache.forced', cache.forced);
83
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
84
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
85
+ span.setAttribute('tramvai.http-client.cache.memory.enabled', cache.memoryEnabled);
86
+ span.setAttribute('tramvai.http-client.cache.memory.force', cache.memoryForce);
87
+ span.setAttribute('tramvai.http-client.cache.memory.cache', cache.memoryCache);
88
+ span.setAttribute('tramvai.http-client.cache.memory.cache.outdated', cache.memoryCacheOutdated);
89
+ span.setAttribute('tramvai.http-client.cache.deduplicate.enabled', cache.deduplicateEnabled);
90
+ span.setAttribute('tramvai.http-client.cache.deduplicate.force', cache.deduplicateForce);
91
+ }
92
+ if (CIRCUIT_BREAKER) {
93
+ span.setAttribute('tramvai.http-client.circuit-breaker.open', CIRCUIT_BREAKER === null || CIRCUIT_BREAKER === void 0 ? void 0 : CIRCUIT_BREAKER.open);
94
+ }
95
+ }
96
+
97
+ export { providers };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var core = require('@tramvai/core');
6
+ var tokensHttpClient = require('@tramvai/tokens-http-client');
7
+ var tokensMetrics = require('@tramvai/tokens-metrics');
8
+ var api = require('@opentelemetry/api');
9
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
10
+ var tokens = require('../tokens.js');
11
+
12
+ const providers = [
13
+ core.provide({
14
+ provide: tokensHttpClient.DEFAULT_HTTP_CLIENT_INTERCEPTORS,
15
+ useFactory: ({ tracer, metricsServicesRegistry }) => {
16
+ return (request, next) => {
17
+ var _a, _b, _c, _d;
18
+ const url = (_a = request.url) !== null && _a !== void 0 ? _a :
19
+ // todo add leading slash before path if needed
20
+ (request.baseUrl ? `${request.baseUrl}${request.path}` : (_b = request.path) !== null && _b !== void 0 ? _b : '');
21
+ const parsedUrl = new URL(request.query ? `${url}?${new URLSearchParams(request.query).toString()}` : url);
22
+ const serviceName = (_c = metricsServicesRegistry.getServiceName(url, request)) !== null && _c !== void 0 ? _c : 'unknown';
23
+ const method = (_d = request.method) !== null && _d !== void 0 ? _d : 'GET';
24
+ // propagate context from outgoing request
25
+ // https://opentelemetry.io/docs/languages/js/propagation/#manual-context-propagation
26
+ const output = {};
27
+ api.propagation.inject(api.context.active(), output);
28
+ if (output.traceparent) {
29
+ if (!request.headers) {
30
+ request.headers = {};
31
+ }
32
+ request.headers.traceparent = output.traceparent;
33
+ }
34
+ if (output.tracestate) {
35
+ if (!request.headers) {
36
+ request.headers = {};
37
+ }
38
+ request.headers.tracestate = output.tracestate;
39
+ }
40
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client
41
+ return tracer.trace(`${method} ${serviceName}`, { kind: api.SpanKind.CLIENT }, (span) => {
42
+ // todo: move custom tramvai attrs to constants
43
+ /**
44
+ * - `tramvai.http-client.transport` - internal HTTP client, `node-fetch` at server-side (possible `undici` in future), `fetch` at client-side
45
+ */
46
+ span.setAttribute('tramvai.scope', 'http-client');
47
+ span.setAttribute('tramvai.http-client.transport', 'node-fetch');
48
+ span.setAttribute(semanticConventions.ATTR_HTTP_REQUEST_METHOD, method);
49
+ span.setAttribute(semanticConventions.ATTR_SERVER_ADDRESS, parsedUrl.hostname);
50
+ span.setAttribute(semanticConventions.ATTR_URL_PATH, parsedUrl.pathname);
51
+ span.setAttribute(semanticConventions.ATTR_URL_QUERY, parsedUrl.search.replace('?', ''));
52
+ span.setAttribute(semanticConventions.ATTR_URL_SCHEME, parsedUrl.protocol.replace(':', ''));
53
+ span.setAttribute(semanticConventions.ATTR_URL_FULL, parsedUrl.href);
54
+ // todo req/res headers?
55
+ // todo http.request.resend_count?
56
+ return next(request)
57
+ .then((response) => {
58
+ span.setAttribute(semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
59
+ const meta = response.__meta;
60
+ if (meta) {
61
+ writeMetaAttributes(span, meta);
62
+ }
63
+ return response;
64
+ })
65
+ .catch((error) => {
66
+ var _a;
67
+ span.setAttribute(semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE, (_a = error === null || error === void 0 ? void 0 : error.status) !== null && _a !== void 0 ? _a : 500);
68
+ const meta = error.__meta;
69
+ if (meta) {
70
+ writeMetaAttributes(span, meta);
71
+ }
72
+ throw error;
73
+ });
74
+ });
75
+ };
76
+ },
77
+ deps: {
78
+ tracer: tokens.OPENTELEMETRY_TRACER_TOKEN,
79
+ metricsServicesRegistry: tokensMetrics.METRICS_SERVICES_REGISTRY_TOKEN,
80
+ },
81
+ }),
82
+ ];
83
+ function writeMetaAttributes(span, meta) {
84
+ const { cache, CIRCUIT_BREAKER } = meta;
85
+ if (cache) {
86
+ span.setAttribute('tramvai.http-client.cache.forced', cache.forced);
87
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
88
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
89
+ span.setAttribute('tramvai.http-client.cache.memory.enabled', cache.memoryEnabled);
90
+ span.setAttribute('tramvai.http-client.cache.memory.force', cache.memoryForce);
91
+ span.setAttribute('tramvai.http-client.cache.memory.cache', cache.memoryCache);
92
+ span.setAttribute('tramvai.http-client.cache.memory.cache.outdated', cache.memoryCacheOutdated);
93
+ span.setAttribute('tramvai.http-client.cache.deduplicate.enabled', cache.deduplicateEnabled);
94
+ span.setAttribute('tramvai.http-client.cache.deduplicate.force', cache.deduplicateForce);
95
+ }
96
+ if (CIRCUIT_BREAKER) {
97
+ span.setAttribute('tramvai.http-client.circuit-breaker.open', CIRCUIT_BREAKER === null || CIRCUIT_BREAKER === void 0 ? void 0 : CIRCUIT_BREAKER.open);
98
+ }
99
+ }
100
+
101
+ exports.providers = providers;
@@ -0,0 +1,4 @@
1
+ export declare const providers: import("@tramvai/core").Provider<unknown, ((logger: import("@tramvai/tokens-common").LoggerFactory) => void) & {
2
+ __type?: "multi token" | undefined;
3
+ }>[];
4
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { trace, context } from '@opentelemetry/api';
2
+ import { provide } from '@tramvai/core';
3
+ import { LOGGER_INIT_HOOK } from '@tramvai/tokens-common';
4
+
5
+ const providers = [
6
+ provide({
7
+ provide: LOGGER_INIT_HOOK,
8
+ useValue: (loggerInstance) => {
9
+ loggerInstance.addExtension({
10
+ extend(logObj) {
11
+ var _a;
12
+ const activeSpanContext = (_a = trace.getSpan(context.active())) === null || _a === void 0 ? void 0 : _a.spanContext();
13
+ if (activeSpanContext) {
14
+ return {
15
+ ...logObj,
16
+ spanId: activeSpanContext.spanId,
17
+ traceId: activeSpanContext.traceId,
18
+ };
19
+ }
20
+ return logObj;
21
+ },
22
+ });
23
+ },
24
+ }),
25
+ ];
26
+
27
+ export { providers };
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var api = require('@opentelemetry/api');
6
+ var core = require('@tramvai/core');
7
+ var tokensCommon = require('@tramvai/tokens-common');
8
+
9
+ const providers = [
10
+ core.provide({
11
+ provide: tokensCommon.LOGGER_INIT_HOOK,
12
+ useValue: (loggerInstance) => {
13
+ loggerInstance.addExtension({
14
+ extend(logObj) {
15
+ var _a;
16
+ const activeSpanContext = (_a = api.trace.getSpan(api.context.active())) === null || _a === void 0 ? void 0 : _a.spanContext();
17
+ if (activeSpanContext) {
18
+ return {
19
+ ...logObj,
20
+ spanId: activeSpanContext.spanId,
21
+ traceId: activeSpanContext.traceId,
22
+ };
23
+ }
24
+ return logObj;
25
+ },
26
+ });
27
+ },
28
+ }),
29
+ ];
30
+
31
+ exports.providers = providers;
@@ -0,0 +1,27 @@
1
+ /// <reference types="node" />
2
+ import { type Span } from '@opentelemetry/api';
3
+ export declare const REQUEST_SPAN: unique symbol;
4
+ declare module 'fastify' {
5
+ interface FastifyRequest {
6
+ [REQUEST_SPAN]?: Span;
7
+ }
8
+ }
9
+ export declare const providers: (import("@tramvai/core").Provider<{
10
+ app: import("fastify").FastifyInstance<import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault> & {
11
+ __type?: "base token" | undefined;
12
+ };
13
+ tracer: import("../tokens").TramvaiTracer & {
14
+ __type?: "base token" | undefined;
15
+ };
16
+ tracesExcludePaths: string & {
17
+ __type?: "multi token" | undefined;
18
+ };
19
+ envManager: import("@tramvai/tokens-common").EnvironmentManager & {
20
+ __type?: "base token" | undefined;
21
+ };
22
+ }, import("@tramvai/tokens-server-private").FASTIFY_APP_INIT_HANDLER & {
23
+ __type?: "multi token" | undefined;
24
+ }> | import("@tramvai/core").Provider<{}, import("@tramvai/tokens-server-private").FASTIFY_APP_ERROR_HANDLER & {
25
+ __type?: "multi token" | undefined;
26
+ }>)[];
27
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,125 @@
1
+ import { ATTR_HTTP_REQUEST_METHOD, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_HTTP_ROUTE, ATTR_URL_PATH, ATTR_URL_QUERY, ATTR_URL_SCHEME, ATTR_URL_FULL, ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions';
2
+ import { propagation, ROOT_CONTEXT, SpanKind, SpanStatusCode } from '@opentelemetry/api';
3
+ import pathToRegexp from 'path-to-regexp';
4
+ import flatten from '@tinkoff/utils/array/flatten';
5
+ import { isNotFoundError, isHttpError } from '@tinkoff/errors';
6
+ import { provide } from '@tramvai/core';
7
+ import { UTILITY_SERVER_PATHS } from '@tramvai/tokens-server';
8
+ import { WEB_FASTIFY_APP_INIT_TOKEN, WEB_FASTIFY_APP_TOKEN, WEB_FASTIFY_APP_AFTER_ERROR_TOKEN } from '@tramvai/tokens-server-private';
9
+ import { ENV_MANAGER_TOKEN } from '@tramvai/tokens-common';
10
+ import { OPENTELEMETRY_TRACER_TOKEN } from '../tokens.es.js';
11
+
12
+ const REQUEST_SPAN = Symbol('opentelemetry.tramvai.server.request.span');
13
+ const providers = [
14
+ provide({
15
+ provide: WEB_FASTIFY_APP_INIT_TOKEN,
16
+ useFactory: ({ app, tracer, tracesExcludePaths, envManager }) => {
17
+ return () => {
18
+ // todo copypaste from @tinkoff/measure-fastify-requests
19
+ const excludePatterns = flatten(tracesExcludePaths).map((p) => pathToRegexp(p));
20
+ app.addHook('onRequest', (req, reply, done) => {
21
+ var _a;
22
+ // skip traces for healthchecks
23
+ if (excludePatterns.some((p) => p.test(req.url))) {
24
+ done();
25
+ return;
26
+ }
27
+ // propagate context from incoming request
28
+ // https://opentelemetry.io/docs/languages/js/propagation/#manual-context-propagation
29
+ const activeContext = propagation.extract(ROOT_CONTEXT, req.headers);
30
+ const httpMethod = req.method;
31
+ // todo useful because always get `*`, rewrite with tramvai router route?
32
+ const httpRoute = ((_a = req.routeOptions) === null || _a === void 0 ? void 0 : _a.url)
33
+ ? req.routeOptions.url // since fastify@4.10.0
34
+ : req.routerPath;
35
+ const parsedUrl = new URL(`http://localhost${req.url}`);
36
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#name
37
+ tracer.startActiveSpan(`${httpMethod} ${httpRoute === '*' ? 'APP' : httpRoute}`,
38
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server-semantic-conventions
39
+ { kind: SpanKind.SERVER }, activeContext, (span) => {
40
+ req[REQUEST_SPAN] = span;
41
+ // todo: move custom tramvai attrs to constants
42
+ /**
43
+ * let's add some conventions for tramvai attributes:
44
+ * - `tramvai` prefix for all specific attributes in tramvai instrumentation
45
+ * - `tramvai.scope` second prefix to define module, e.g. `tramvai.scope: server`
46
+ * - `tramvai.*` second prefix for specific modules, e.g. `tramvai.server`
47
+ * - `tramvai.server.handler` - `app` value for pages handler, another possible values - `papi`, `request-limiter`
48
+ * - `tramvai.server.framework` - reserved for future, if we will add support for other server frameworks
49
+ */
50
+ span.setAttribute('tramvai.scope', 'server');
51
+ span.setAttribute('tramvai.server.handler', 'app');
52
+ span.setAttribute('tramvai.server.framework', 'fastify');
53
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/http.md
54
+ span.setAttribute(ATTR_HTTP_REQUEST_METHOD, httpMethod);
55
+ span.setAttribute(ATTR_SERVER_ADDRESS, req.headers['x-original-host'] || req.headers.host || envManager.get('PORT'));
56
+ span.setAttribute(ATTR_SERVER_PORT, envManager.get('PORT'));
57
+ // route should have low-cardinality - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/http.md
58
+ span.setAttribute(ATTR_HTTP_ROUTE, httpRoute);
59
+ span.setAttribute(ATTR_URL_PATH, parsedUrl.pathname);
60
+ span.setAttribute(ATTR_URL_QUERY, parsedUrl.search.replace('?', ''));
61
+ span.setAttribute(ATTR_URL_SCHEME, parsedUrl.protocol.replace(':', ''));
62
+ span.setAttribute(ATTR_URL_FULL, parsedUrl.href);
63
+ done();
64
+ });
65
+ });
66
+ app.addHook('onResponse', (req, reply) => {
67
+ if (req[REQUEST_SPAN]) {
68
+ const span = req[REQUEST_SPAN];
69
+ if (reply.statusCode >= 400) ;
70
+ else {
71
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, reply.statusCode);
72
+ // todo req/res headers?
73
+ span.end();
74
+ }
75
+ }
76
+ });
77
+ };
78
+ },
79
+ deps: {
80
+ app: WEB_FASTIFY_APP_TOKEN,
81
+ tracer: OPENTELEMETRY_TRACER_TOKEN,
82
+ tracesExcludePaths: UTILITY_SERVER_PATHS,
83
+ envManager: ENV_MANAGER_TOKEN,
84
+ },
85
+ }),
86
+ provide({
87
+ provide: WEB_FASTIFY_APP_AFTER_ERROR_TOKEN,
88
+ useFactory: (deps) => {
89
+ return (error, req, reply) => {
90
+ var _a;
91
+ if (req[REQUEST_SPAN]) {
92
+ const span = req[REQUEST_SPAN];
93
+ let httpStatus;
94
+ // todo duplicated logic from packages/modules/server/src/server/error/prepareLogsForError.ts
95
+ if (isNotFoundError(error)) {
96
+ httpStatus = error.httpStatus || 404;
97
+ }
98
+ else if (isHttpError(error)) {
99
+ httpStatus = error.httpStatus || 500;
100
+ }
101
+ else {
102
+ httpStatus = error.statusCode || 500;
103
+ }
104
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatus);
105
+ // todo req/res headers?
106
+ // todo "error.type" attribute?
107
+ // do not set error status for incoming requests with 4xx status
108
+ // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
109
+ if (httpStatus >= 500) {
110
+ span.recordException(error);
111
+ span.setStatus({
112
+ code: SpanStatusCode.ERROR,
113
+ message: (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : 'Unknown error',
114
+ });
115
+ }
116
+ span.end();
117
+ // todo RootErrorBoundary is out of scope, because it is rendered after `WEB_FASTIFY_APP_AFTER_ERROR_TOKEN` hooks
118
+ }
119
+ };
120
+ },
121
+ deps: {},
122
+ }),
123
+ ];
124
+
125
+ export { REQUEST_SPAN, providers };