@tramvai/module-opentelemetry 5.14.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @tramvai/module-opentelemetry
2
+
3
+ [Actual documentation](03-features/014-monitoring/03-telemetry.md)
@@ -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,95 @@
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
+ const url = request.url ??
14
+ // todo add leading slash before path if needed
15
+ (request.baseUrl ? `${request.baseUrl}${request.path}` : request.path ?? '');
16
+ const parsedUrl = new URL(request.query ? `${url}?${new URLSearchParams(request.query).toString()}` : url);
17
+ const serviceName = metricsServicesRegistry.getServiceName(url, request) ?? 'unknown';
18
+ const method = request.method ?? 'GET';
19
+ // propagate context from outgoing request
20
+ // https://opentelemetry.io/docs/languages/js/propagation/#manual-context-propagation
21
+ const output = {};
22
+ propagation.inject(context.active(), output);
23
+ if (output.traceparent) {
24
+ if (!request.headers) {
25
+ request.headers = {};
26
+ }
27
+ request.headers.traceparent = output.traceparent;
28
+ }
29
+ if (output.tracestate) {
30
+ if (!request.headers) {
31
+ request.headers = {};
32
+ }
33
+ request.headers.tracestate = output.tracestate;
34
+ }
35
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client
36
+ return tracer.trace(`${method} ${serviceName}`, { kind: SpanKind.CLIENT }, (span) => {
37
+ // todo: move custom tramvai attrs to constants
38
+ /**
39
+ * - `tramvai.http-client.transport` - internal HTTP client, `node-fetch` at server-side (possible `undici` in future), `fetch` at client-side
40
+ */
41
+ span.setAttribute('tramvai.scope', 'http-client');
42
+ span.setAttribute('tramvai.http-client.transport', 'node-fetch');
43
+ span.setAttribute(ATTR_HTTP_REQUEST_METHOD, method);
44
+ span.setAttribute(ATTR_SERVER_ADDRESS, parsedUrl.hostname);
45
+ span.setAttribute(ATTR_URL_PATH, parsedUrl.pathname);
46
+ span.setAttribute(ATTR_URL_QUERY, parsedUrl.search.replace('?', ''));
47
+ span.setAttribute(ATTR_URL_SCHEME, parsedUrl.protocol.replace(':', ''));
48
+ span.setAttribute(ATTR_URL_FULL, parsedUrl.href);
49
+ // todo req/res headers?
50
+ // todo http.request.resend_count?
51
+ return next(request)
52
+ .then((response) => {
53
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
54
+ const meta = response.__meta;
55
+ if (meta) {
56
+ writeMetaAttributes(span, meta);
57
+ }
58
+ return response;
59
+ })
60
+ .catch((error) => {
61
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, error?.status ?? 500);
62
+ const meta = error.__meta;
63
+ if (meta) {
64
+ writeMetaAttributes(span, meta);
65
+ }
66
+ throw error;
67
+ });
68
+ });
69
+ };
70
+ },
71
+ deps: {
72
+ tracer: OPENTELEMETRY_TRACER_TOKEN,
73
+ metricsServicesRegistry: METRICS_SERVICES_REGISTRY_TOKEN,
74
+ },
75
+ }),
76
+ ];
77
+ function writeMetaAttributes(span, meta) {
78
+ const { cache, CIRCUIT_BREAKER } = meta;
79
+ if (cache) {
80
+ span.setAttribute('tramvai.http-client.cache.forced', cache.forced);
81
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
82
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
83
+ span.setAttribute('tramvai.http-client.cache.memory.enabled', cache.memoryEnabled);
84
+ span.setAttribute('tramvai.http-client.cache.memory.force', cache.memoryForce);
85
+ span.setAttribute('tramvai.http-client.cache.memory.cache', cache.memoryCache);
86
+ span.setAttribute('tramvai.http-client.cache.memory.cache.outdated', cache.memoryCacheOutdated);
87
+ span.setAttribute('tramvai.http-client.cache.deduplicate.enabled', cache.deduplicateEnabled);
88
+ span.setAttribute('tramvai.http-client.cache.deduplicate.force', cache.deduplicateForce);
89
+ }
90
+ if (CIRCUIT_BREAKER) {
91
+ span.setAttribute('tramvai.http-client.circuit-breaker.open', CIRCUIT_BREAKER?.open);
92
+ }
93
+ }
94
+
95
+ export { providers };
@@ -0,0 +1,99 @@
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
+ const url = request.url ??
18
+ // todo add leading slash before path if needed
19
+ (request.baseUrl ? `${request.baseUrl}${request.path}` : request.path ?? '');
20
+ const parsedUrl = new URL(request.query ? `${url}?${new URLSearchParams(request.query).toString()}` : url);
21
+ const serviceName = metricsServicesRegistry.getServiceName(url, request) ?? 'unknown';
22
+ const method = request.method ?? 'GET';
23
+ // propagate context from outgoing request
24
+ // https://opentelemetry.io/docs/languages/js/propagation/#manual-context-propagation
25
+ const output = {};
26
+ api.propagation.inject(api.context.active(), output);
27
+ if (output.traceparent) {
28
+ if (!request.headers) {
29
+ request.headers = {};
30
+ }
31
+ request.headers.traceparent = output.traceparent;
32
+ }
33
+ if (output.tracestate) {
34
+ if (!request.headers) {
35
+ request.headers = {};
36
+ }
37
+ request.headers.tracestate = output.tracestate;
38
+ }
39
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client
40
+ return tracer.trace(`${method} ${serviceName}`, { kind: api.SpanKind.CLIENT }, (span) => {
41
+ // todo: move custom tramvai attrs to constants
42
+ /**
43
+ * - `tramvai.http-client.transport` - internal HTTP client, `node-fetch` at server-side (possible `undici` in future), `fetch` at client-side
44
+ */
45
+ span.setAttribute('tramvai.scope', 'http-client');
46
+ span.setAttribute('tramvai.http-client.transport', 'node-fetch');
47
+ span.setAttribute(semanticConventions.ATTR_HTTP_REQUEST_METHOD, method);
48
+ span.setAttribute(semanticConventions.ATTR_SERVER_ADDRESS, parsedUrl.hostname);
49
+ span.setAttribute(semanticConventions.ATTR_URL_PATH, parsedUrl.pathname);
50
+ span.setAttribute(semanticConventions.ATTR_URL_QUERY, parsedUrl.search.replace('?', ''));
51
+ span.setAttribute(semanticConventions.ATTR_URL_SCHEME, parsedUrl.protocol.replace(':', ''));
52
+ span.setAttribute(semanticConventions.ATTR_URL_FULL, parsedUrl.href);
53
+ // todo req/res headers?
54
+ // todo http.request.resend_count?
55
+ return next(request)
56
+ .then((response) => {
57
+ span.setAttribute(semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
58
+ const meta = response.__meta;
59
+ if (meta) {
60
+ writeMetaAttributes(span, meta);
61
+ }
62
+ return response;
63
+ })
64
+ .catch((error) => {
65
+ span.setAttribute(semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE, error?.status ?? 500);
66
+ const meta = error.__meta;
67
+ if (meta) {
68
+ writeMetaAttributes(span, meta);
69
+ }
70
+ throw error;
71
+ });
72
+ });
73
+ };
74
+ },
75
+ deps: {
76
+ tracer: tokens.OPENTELEMETRY_TRACER_TOKEN,
77
+ metricsServicesRegistry: tokensMetrics.METRICS_SERVICES_REGISTRY_TOKEN,
78
+ },
79
+ }),
80
+ ];
81
+ function writeMetaAttributes(span, meta) {
82
+ const { cache, CIRCUIT_BREAKER } = meta;
83
+ if (cache) {
84
+ span.setAttribute('tramvai.http-client.cache.forced', cache.forced);
85
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
86
+ span.setAttribute('tramvai.http-client.cache.enabled', cache.enabled);
87
+ span.setAttribute('tramvai.http-client.cache.memory.enabled', cache.memoryEnabled);
88
+ span.setAttribute('tramvai.http-client.cache.memory.force', cache.memoryForce);
89
+ span.setAttribute('tramvai.http-client.cache.memory.cache', cache.memoryCache);
90
+ span.setAttribute('tramvai.http-client.cache.memory.cache.outdated', cache.memoryCacheOutdated);
91
+ span.setAttribute('tramvai.http-client.cache.deduplicate.enabled', cache.deduplicateEnabled);
92
+ span.setAttribute('tramvai.http-client.cache.deduplicate.force', cache.deduplicateForce);
93
+ }
94
+ if (CIRCUIT_BREAKER) {
95
+ span.setAttribute('tramvai.http-client.circuit-breaker.open', CIRCUIT_BREAKER?.open);
96
+ }
97
+ }
98
+
99
+ 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,26 @@
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
+ const activeSpanContext = trace.getSpan(context.active())?.spanContext();
12
+ if (activeSpanContext) {
13
+ return {
14
+ ...logObj,
15
+ spanId: activeSpanContext.spanId,
16
+ traceId: activeSpanContext.traceId,
17
+ };
18
+ }
19
+ return logObj;
20
+ },
21
+ });
22
+ },
23
+ }),
24
+ ];
25
+
26
+ export { providers };
@@ -0,0 +1,30 @@
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
+ const activeSpanContext = api.trace.getSpan(api.context.active())?.spanContext();
16
+ if (activeSpanContext) {
17
+ return {
18
+ ...logObj,
19
+ spanId: activeSpanContext.spanId,
20
+ traceId: activeSpanContext.traceId,
21
+ };
22
+ }
23
+ return logObj;
24
+ },
25
+ });
26
+ },
27
+ }),
28
+ ];
29
+
30
+ exports.providers = providers;
@@ -0,0 +1,25 @@
1
+ /// <reference types="node" />
2
+ import { type Span } from '@opentelemetry/api';
3
+ 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
+ }, import("@tramvai/tokens-server-private").FASTIFY_APP_INIT_HANDLER & {
20
+ __type?: "multi token" | undefined;
21
+ }> | import("@tramvai/core").Provider<{}, import("@tramvai/tokens-server-private").FASTIFY_APP_ERROR_HANDLER & {
22
+ __type?: "multi token" | undefined;
23
+ }>)[];
24
+ export {};
25
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,119 @@
1
+ import { ATTR_HTTP_REQUEST_METHOD, 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 { OPENTELEMETRY_TRACER_TOKEN } from '../tokens.es.js';
10
+
11
+ const REQUEST_SPAN = Symbol('opentelemetry.tramvai.server.request.span');
12
+ const providers = [
13
+ provide({
14
+ provide: WEB_FASTIFY_APP_INIT_TOKEN,
15
+ useFactory: ({ app, tracer, tracesExcludePaths }) => {
16
+ return () => {
17
+ // todo copypaste from @tinkoff/measure-fastify-requests
18
+ const excludePatterns = flatten(tracesExcludePaths).map((p) => pathToRegexp(p));
19
+ app.addHook('onRequest', (req, reply, done) => {
20
+ // skip traces for healthchecks
21
+ if (excludePatterns.some((p) => p.test(req.url))) {
22
+ done();
23
+ return;
24
+ }
25
+ // propagate context from incoming request
26
+ // https://opentelemetry.io/docs/languages/js/propagation/#manual-context-propagation
27
+ const activeContext = propagation.extract(ROOT_CONTEXT, req.headers);
28
+ const httpMethod = req.method;
29
+ // todo useful because always get `*`, rewrite with tramvai router route?
30
+ const httpRoute = req.routeOptions?.url
31
+ ? req.routeOptions.url // since fastify@4.10.0
32
+ : req.routerPath;
33
+ const parsedUrl = new URL(`http://localhost${req.url}`);
34
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#name
35
+ tracer.startActiveSpan(`${httpMethod} ${httpRoute === '*' ? 'APP' : httpRoute}`,
36
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server-semantic-conventions
37
+ { kind: SpanKind.SERVER }, activeContext, (span) => {
38
+ req[REQUEST_SPAN] = span;
39
+ // todo: move custom tramvai attrs to constants
40
+ /**
41
+ * let's add some conventions for tramvai attributes:
42
+ * - `tramvai` prefix for all specific attributes in tramvai instrumentation
43
+ * - `tramvai.scope` second prefix to define module, e.g. `tramvai.scope: server`
44
+ * - `tramvai.*` second prefix for specific modules, e.g. `tramvai.server`
45
+ * - `tramvai.server.handler` - `app` value for pages handler, another possible values - `papi`, `request-limiter`
46
+ * - `tramvai.server.framework` - reserved for future, if we will add support for other server frameworks
47
+ */
48
+ span.setAttribute('tramvai.scope', 'server');
49
+ span.setAttribute('tramvai.server.handler', 'app');
50
+ span.setAttribute('tramvai.server.framework', 'fastify');
51
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/http.md
52
+ span.setAttribute(ATTR_HTTP_REQUEST_METHOD, httpMethod);
53
+ // route should have low-cardinality - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/http.md
54
+ span.setAttribute(ATTR_HTTP_ROUTE, httpRoute);
55
+ span.setAttribute(ATTR_URL_PATH, parsedUrl.pathname);
56
+ span.setAttribute(ATTR_URL_QUERY, parsedUrl.search.replace('?', ''));
57
+ span.setAttribute(ATTR_URL_SCHEME, parsedUrl.protocol.replace(':', ''));
58
+ span.setAttribute(ATTR_URL_FULL, parsedUrl.href);
59
+ done();
60
+ });
61
+ });
62
+ app.addHook('onResponse', (req, reply) => {
63
+ if (req[REQUEST_SPAN]) {
64
+ const span = req[REQUEST_SPAN];
65
+ if (reply.statusCode >= 400) ;
66
+ else {
67
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, reply.statusCode);
68
+ // todo req/res headers?
69
+ span.end();
70
+ }
71
+ }
72
+ });
73
+ };
74
+ },
75
+ deps: {
76
+ app: WEB_FASTIFY_APP_TOKEN,
77
+ tracer: OPENTELEMETRY_TRACER_TOKEN,
78
+ tracesExcludePaths: UTILITY_SERVER_PATHS,
79
+ },
80
+ }),
81
+ provide({
82
+ provide: WEB_FASTIFY_APP_AFTER_ERROR_TOKEN,
83
+ useFactory: (deps) => {
84
+ return (error, req, reply) => {
85
+ if (req[REQUEST_SPAN]) {
86
+ const span = req[REQUEST_SPAN];
87
+ let httpStatus;
88
+ // todo duplicated logic from packages/modules/server/src/server/error/prepareLogsForError.ts
89
+ if (isNotFoundError(error)) {
90
+ httpStatus = error.httpStatus || 404;
91
+ }
92
+ else if (isHttpError(error)) {
93
+ httpStatus = error.httpStatus || 500;
94
+ }
95
+ else {
96
+ httpStatus = error.statusCode || 500;
97
+ }
98
+ span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatus);
99
+ // todo req/res headers?
100
+ // todo "error.type" attribute?
101
+ // do not set error status for incoming requests with 4xx status
102
+ // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
103
+ if (httpStatus >= 500) {
104
+ span.recordException(error);
105
+ span.setStatus({
106
+ code: SpanStatusCode.ERROR,
107
+ message: error?.message ?? 'Unknown error',
108
+ });
109
+ }
110
+ span.end();
111
+ // todo RootErrorBoundary is out of scope, because it is rendered after `WEB_FASTIFY_APP_AFTER_ERROR_TOKEN` hooks
112
+ }
113
+ };
114
+ },
115
+ deps: {},
116
+ }),
117
+ ];
118
+
119
+ export { providers };
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
6
+ var api = require('@opentelemetry/api');
7
+ var pathToRegexp = require('path-to-regexp');
8
+ var flatten = require('@tinkoff/utils/array/flatten');
9
+ var errors = require('@tinkoff/errors');
10
+ var core = require('@tramvai/core');
11
+ var tokensServer = require('@tramvai/tokens-server');
12
+ var tokensServerPrivate = require('@tramvai/tokens-server-private');
13
+ var tokens = require('../tokens.js');
14
+
15
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
16
+
17
+ var pathToRegexp__default = /*#__PURE__*/_interopDefaultLegacy(pathToRegexp);
18
+ var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
19
+
20
+ const REQUEST_SPAN = Symbol('opentelemetry.tramvai.server.request.span');
21
+ const providers = [
22
+ core.provide({
23
+ provide: tokensServerPrivate.WEB_FASTIFY_APP_INIT_TOKEN,
24
+ useFactory: ({ app, tracer, tracesExcludePaths }) => {
25
+ return () => {
26
+ // todo copypaste from @tinkoff/measure-fastify-requests
27
+ const excludePatterns = flatten__default["default"](tracesExcludePaths).map((p) => pathToRegexp__default["default"](p));
28
+ app.addHook('onRequest', (req, reply, done) => {
29
+ // skip traces for healthchecks
30
+ if (excludePatterns.some((p) => p.test(req.url))) {
31
+ done();
32
+ return;
33
+ }
34
+ // propagate context from incoming request
35
+ // https://opentelemetry.io/docs/languages/js/propagation/#manual-context-propagation
36
+ const activeContext = api.propagation.extract(api.ROOT_CONTEXT, req.headers);
37
+ const httpMethod = req.method;
38
+ // todo useful because always get `*`, rewrite with tramvai router route?
39
+ const httpRoute = req.routeOptions?.url
40
+ ? req.routeOptions.url // since fastify@4.10.0
41
+ : req.routerPath;
42
+ const parsedUrl = new URL(`http://localhost${req.url}`);
43
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#name
44
+ tracer.startActiveSpan(`${httpMethod} ${httpRoute === '*' ? 'APP' : httpRoute}`,
45
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server-semantic-conventions
46
+ { kind: api.SpanKind.SERVER }, activeContext, (span) => {
47
+ req[REQUEST_SPAN] = span;
48
+ // todo: move custom tramvai attrs to constants
49
+ /**
50
+ * let's add some conventions for tramvai attributes:
51
+ * - `tramvai` prefix for all specific attributes in tramvai instrumentation
52
+ * - `tramvai.scope` second prefix to define module, e.g. `tramvai.scope: server`
53
+ * - `tramvai.*` second prefix for specific modules, e.g. `tramvai.server`
54
+ * - `tramvai.server.handler` - `app` value for pages handler, another possible values - `papi`, `request-limiter`
55
+ * - `tramvai.server.framework` - reserved for future, if we will add support for other server frameworks
56
+ */
57
+ span.setAttribute('tramvai.scope', 'server');
58
+ span.setAttribute('tramvai.server.handler', 'app');
59
+ span.setAttribute('tramvai.server.framework', 'fastify');
60
+ // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/http.md
61
+ span.setAttribute(semanticConventions.ATTR_HTTP_REQUEST_METHOD, httpMethod);
62
+ // route should have low-cardinality - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/http.md
63
+ span.setAttribute(semanticConventions.ATTR_HTTP_ROUTE, httpRoute);
64
+ span.setAttribute(semanticConventions.ATTR_URL_PATH, parsedUrl.pathname);
65
+ span.setAttribute(semanticConventions.ATTR_URL_QUERY, parsedUrl.search.replace('?', ''));
66
+ span.setAttribute(semanticConventions.ATTR_URL_SCHEME, parsedUrl.protocol.replace(':', ''));
67
+ span.setAttribute(semanticConventions.ATTR_URL_FULL, parsedUrl.href);
68
+ done();
69
+ });
70
+ });
71
+ app.addHook('onResponse', (req, reply) => {
72
+ if (req[REQUEST_SPAN]) {
73
+ const span = req[REQUEST_SPAN];
74
+ if (reply.statusCode >= 400) ;
75
+ else {
76
+ span.setAttribute(semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE, reply.statusCode);
77
+ // todo req/res headers?
78
+ span.end();
79
+ }
80
+ }
81
+ });
82
+ };
83
+ },
84
+ deps: {
85
+ app: tokensServerPrivate.WEB_FASTIFY_APP_TOKEN,
86
+ tracer: tokens.OPENTELEMETRY_TRACER_TOKEN,
87
+ tracesExcludePaths: tokensServer.UTILITY_SERVER_PATHS,
88
+ },
89
+ }),
90
+ core.provide({
91
+ provide: tokensServerPrivate.WEB_FASTIFY_APP_AFTER_ERROR_TOKEN,
92
+ useFactory: (deps) => {
93
+ return (error, req, reply) => {
94
+ if (req[REQUEST_SPAN]) {
95
+ const span = req[REQUEST_SPAN];
96
+ let httpStatus;
97
+ // todo duplicated logic from packages/modules/server/src/server/error/prepareLogsForError.ts
98
+ if (errors.isNotFoundError(error)) {
99
+ httpStatus = error.httpStatus || 404;
100
+ }
101
+ else if (errors.isHttpError(error)) {
102
+ httpStatus = error.httpStatus || 500;
103
+ }
104
+ else {
105
+ httpStatus = error.statusCode || 500;
106
+ }
107
+ span.setAttribute(semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatus);
108
+ // todo req/res headers?
109
+ // todo "error.type" attribute?
110
+ // do not set error status for incoming requests with 4xx status
111
+ // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
112
+ if (httpStatus >= 500) {
113
+ span.recordException(error);
114
+ span.setStatus({
115
+ code: api.SpanStatusCode.ERROR,
116
+ message: error?.message ?? 'Unknown error',
117
+ });
118
+ }
119
+ span.end();
120
+ // todo RootErrorBoundary is out of scope, because it is rendered after `WEB_FASTIFY_APP_AFTER_ERROR_TOKEN` hooks
121
+ }
122
+ };
123
+ },
124
+ deps: {},
125
+ }),
126
+ ];
127
+
128
+ exports.providers = providers;
@@ -0,0 +1,4 @@
1
+ export * from './tokens';
2
+ export declare class OpenTelemetryModule {
3
+ }
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,125 @@
1
+ import { __decorate } from 'tslib';
2
+ import { Module, provide, commandLineListTokens, optional, APP_INFO_TOKEN } from '@tramvai/core';
3
+ import { NodeTracerProvider, SimpleSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
4
+ import { Resource } from '@opentelemetry/resources';
5
+ import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
6
+ import { LOGGER_TOKEN } from '@tramvai/tokens-common';
7
+ import { OPENTELEMETRY_PROVIDER_TOKEN, OPENTELEMETRY_PROVIDER_CONFIG_TOKEN, OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN, OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN, OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN, OPENTELEMETRY_TRACER_TOKEN } from './tokens.es.js';
8
+ 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.es.js';
9
+ import { TramvaiTracerImpl } from './tracer/tracer.es.js';
10
+ import { providers } from './instrumentation/server.es.js';
11
+ import { providers as providers$1 } from './instrumentation/httpClient.es.js';
12
+ import { providers as providers$2 } from './instrumentation/logs.es.js';
13
+
14
+ let OpenTelemetryModule = class OpenTelemetryModule {
15
+ };
16
+ OpenTelemetryModule = __decorate([
17
+ Module({
18
+ imports: [],
19
+ providers: [
20
+ ...providers,
21
+ ...providers$1,
22
+ ...providers$2,
23
+ provide({
24
+ provide: commandLineListTokens.init,
25
+ useFactory: ({ provider }) => {
26
+ return function initializeOpenTelemetryProvider() {
27
+ provider.register();
28
+ };
29
+ },
30
+ deps: {
31
+ provider: OPENTELEMETRY_PROVIDER_TOKEN,
32
+ },
33
+ }),
34
+ provide({
35
+ provide: commandLineListTokens.close,
36
+ useFactory: ({ provider, logger }) => {
37
+ return function shutdownOpenTelemetryProvider() {
38
+ const log = logger('opentelemetry');
39
+ provider.shutdown().catch((error) => {
40
+ log.error({
41
+ event: 'provider-shutdown-error',
42
+ error,
43
+ });
44
+ });
45
+ };
46
+ },
47
+ deps: {
48
+ provider: OPENTELEMETRY_PROVIDER_TOKEN,
49
+ logger: LOGGER_TOKEN,
50
+ },
51
+ }),
52
+ provide({
53
+ provide: OPENTELEMETRY_PROVIDER_TOKEN,
54
+ useFactory: ({ config }) => {
55
+ return new NodeTracerProvider(config);
56
+ },
57
+ deps: {
58
+ config: OPENTELEMETRY_PROVIDER_CONFIG_TOKEN,
59
+ },
60
+ }),
61
+ // todo maybe not needed?
62
+ provide({
63
+ provide: OPENTELEMETRY_PROVIDER_CONFIG_TOKEN,
64
+ useFactory: ({ resource, spanProcessors }) => {
65
+ return { resource, spanProcessors: spanProcessors ?? [] };
66
+ },
67
+ deps: {
68
+ resource: OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN,
69
+ spanProcessors: optional(OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN),
70
+ },
71
+ }),
72
+ provide({
73
+ provide: OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN,
74
+ useFactory: ({ attributesList }) => {
75
+ const attributes = attributesList.reduce((acc, attribute) => {
76
+ return {
77
+ ...acc,
78
+ ...attribute,
79
+ };
80
+ }, {});
81
+ // todo async attributes
82
+ return new Resource(attributes);
83
+ },
84
+ deps: {
85
+ attributesList: OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN,
86
+ },
87
+ }),
88
+ provide({
89
+ provide: OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN,
90
+ useFactory: ({ appInfo }) => {
91
+ return {
92
+ [ATTR_SERVICE_NAME]: appInfo.appName,
93
+ };
94
+ },
95
+ deps: {
96
+ appInfo: APP_INFO_TOKEN,
97
+ },
98
+ }),
99
+ provide({
100
+ provide: OPENTELEMETRY_TRACER_TOKEN,
101
+ useFactory: ({ provider }) => {
102
+ const tracer = provider.getTracer('tramvai', '1.0.0');
103
+ return new TramvaiTracerImpl(tracer);
104
+ },
105
+ deps: {
106
+ provider: OPENTELEMETRY_PROVIDER_TOKEN,
107
+ },
108
+ }),
109
+ // todo open telemetry debug flag or wrap ConsoleSpanExporter in logger
110
+ ...(process.env.NODE_ENV === 'development'
111
+ ? [
112
+ provide({
113
+ provide: OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN,
114
+ useFactory: () => {
115
+ return new SimpleSpanProcessor(new ConsoleSpanExporter());
116
+ },
117
+ }),
118
+ ]
119
+ : []),
120
+ ],
121
+ })
122
+ ], OpenTelemetryModule);
123
+ // todo declareModule!
124
+
125
+ export { OpenTelemetryModule };
package/lib/server.js ADDED
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var core = require('@tramvai/core');
7
+ var sdkTraceNode = require('@opentelemetry/sdk-trace-node');
8
+ var resources = require('@opentelemetry/resources');
9
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
10
+ var tokensCommon = require('@tramvai/tokens-common');
11
+ var tokens = require('./tokens.js');
12
+ var tracer = require('./tracer/tracer.js');
13
+ var server = require('./instrumentation/server.js');
14
+ var httpClient = require('./instrumentation/httpClient.js');
15
+ var logs = require('./instrumentation/logs.js');
16
+
17
+ exports.OpenTelemetryModule = class OpenTelemetryModule {
18
+ };
19
+ exports.OpenTelemetryModule = tslib.__decorate([
20
+ core.Module({
21
+ imports: [],
22
+ providers: [
23
+ ...server.providers,
24
+ ...httpClient.providers,
25
+ ...logs.providers,
26
+ core.provide({
27
+ provide: core.commandLineListTokens.init,
28
+ useFactory: ({ provider }) => {
29
+ return function initializeOpenTelemetryProvider() {
30
+ provider.register();
31
+ };
32
+ },
33
+ deps: {
34
+ provider: tokens.OPENTELEMETRY_PROVIDER_TOKEN,
35
+ },
36
+ }),
37
+ core.provide({
38
+ provide: core.commandLineListTokens.close,
39
+ useFactory: ({ provider, logger }) => {
40
+ return function shutdownOpenTelemetryProvider() {
41
+ const log = logger('opentelemetry');
42
+ provider.shutdown().catch((error) => {
43
+ log.error({
44
+ event: 'provider-shutdown-error',
45
+ error,
46
+ });
47
+ });
48
+ };
49
+ },
50
+ deps: {
51
+ provider: tokens.OPENTELEMETRY_PROVIDER_TOKEN,
52
+ logger: tokensCommon.LOGGER_TOKEN,
53
+ },
54
+ }),
55
+ core.provide({
56
+ provide: tokens.OPENTELEMETRY_PROVIDER_TOKEN,
57
+ useFactory: ({ config }) => {
58
+ return new sdkTraceNode.NodeTracerProvider(config);
59
+ },
60
+ deps: {
61
+ config: tokens.OPENTELEMETRY_PROVIDER_CONFIG_TOKEN,
62
+ },
63
+ }),
64
+ // todo maybe not needed?
65
+ core.provide({
66
+ provide: tokens.OPENTELEMETRY_PROVIDER_CONFIG_TOKEN,
67
+ useFactory: ({ resource, spanProcessors }) => {
68
+ return { resource, spanProcessors: spanProcessors ?? [] };
69
+ },
70
+ deps: {
71
+ resource: tokens.OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN,
72
+ spanProcessors: core.optional(tokens.OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN),
73
+ },
74
+ }),
75
+ core.provide({
76
+ provide: tokens.OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN,
77
+ useFactory: ({ attributesList }) => {
78
+ const attributes = attributesList.reduce((acc, attribute) => {
79
+ return {
80
+ ...acc,
81
+ ...attribute,
82
+ };
83
+ }, {});
84
+ // todo async attributes
85
+ return new resources.Resource(attributes);
86
+ },
87
+ deps: {
88
+ attributesList: tokens.OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN,
89
+ },
90
+ }),
91
+ core.provide({
92
+ provide: tokens.OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN,
93
+ useFactory: ({ appInfo }) => {
94
+ return {
95
+ [semanticConventions.ATTR_SERVICE_NAME]: appInfo.appName,
96
+ };
97
+ },
98
+ deps: {
99
+ appInfo: core.APP_INFO_TOKEN,
100
+ },
101
+ }),
102
+ core.provide({
103
+ provide: tokens.OPENTELEMETRY_TRACER_TOKEN,
104
+ useFactory: ({ provider }) => {
105
+ const tracer$1 = provider.getTracer('tramvai', '1.0.0');
106
+ return new tracer.TramvaiTracerImpl(tracer$1);
107
+ },
108
+ deps: {
109
+ provider: tokens.OPENTELEMETRY_PROVIDER_TOKEN,
110
+ },
111
+ }),
112
+ // todo open telemetry debug flag or wrap ConsoleSpanExporter in logger
113
+ ...(process.env.NODE_ENV === 'development'
114
+ ? [
115
+ core.provide({
116
+ provide: tokens.OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN,
117
+ useFactory: () => {
118
+ return new sdkTraceNode.SimpleSpanProcessor(new sdkTraceNode.ConsoleSpanExporter());
119
+ },
120
+ }),
121
+ ]
122
+ : []),
123
+ ],
124
+ })
125
+ ], exports.OpenTelemetryModule);
126
+ // todo declareModule!
127
+
128
+ exports.OPENTELEMETRY_PROVIDER_CONFIG_TOKEN = tokens.OPENTELEMETRY_PROVIDER_CONFIG_TOKEN;
129
+ exports.OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN = tokens.OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN;
130
+ exports.OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN = tokens.OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN;
131
+ exports.OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN = tokens.OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN;
132
+ exports.OPENTELEMETRY_PROVIDER_TOKEN = tokens.OPENTELEMETRY_PROVIDER_TOKEN;
133
+ exports.OPENTELEMETRY_TRACER_TOKEN = tokens.OPENTELEMETRY_TRACER_TOKEN;
@@ -0,0 +1,10 @@
1
+ import { createToken, Scope } from '@tinkoff/dippy';
2
+
3
+ const OPENTELEMETRY_PROVIDER_TOKEN = createToken('tramvai opentelemetry provider', { scope: Scope.SINGLETON });
4
+ const OPENTELEMETRY_PROVIDER_CONFIG_TOKEN = createToken('tramvai opentelemetry provider config', { scope: Scope.SINGLETON });
5
+ const OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN = createToken('tramvai opentelemetry provider span processor', { multi: true, scope: Scope.SINGLETON });
6
+ const OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN = createToken('tramvai opentelemetry provider resource', { scope: Scope.SINGLETON });
7
+ const OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN = createToken('tramvai opentelemetry provider resource attributes', { multi: true, scope: Scope.SINGLETON });
8
+ const OPENTELEMETRY_TRACER_TOKEN = createToken('tramvai opentelemetry tracer', { scope: Scope.SINGLETON });
9
+
10
+ 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 };
@@ -0,0 +1,38 @@
1
+ import type { Context, Span, SpanOptions } from '@opentelemetry/api';
2
+ import type { SpanProcessor, TracerConfig, BasicTracerProvider } from '@opentelemetry/sdk-trace-node';
3
+ import type { Resource } from '@opentelemetry/resources';
4
+ /**
5
+ * API inspired by:
6
+ * - https://github.com/DataDog/dd-trace-js/blob/59e9a2a75f4256755b4e6c9951a0bdf8d39b4015/index.d.ts#L9
7
+ * - https://github.com/vercel/next.js/blob/9a1cd356dbafbfcf23d1b9ec05f772f766d05580/packages/next/src/server/lib/trace/tracer.ts#L74
8
+ */
9
+ export interface TramvaiTracer {
10
+ startSpan(name: string, options?: SpanOptions, context?: Context): Span;
11
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
12
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
13
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, context: Context, fn: F): ReturnType<F>;
14
+ getActiveSpan(): Span | undefined;
15
+ trace<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
16
+ trace<T>(name: string, fn: (span: Span) => T): T;
17
+ trace<T>(name: string, options: SpanOptions, fn: (span: Span) => Promise<T>): Promise<T>;
18
+ trace<T>(name: string, options: SpanOptions, fn: (span: Span) => T): T;
19
+ }
20
+ export declare const OPENTELEMETRY_PROVIDER_TOKEN: BasicTracerProvider & {
21
+ __type?: "base token" | undefined;
22
+ };
23
+ export declare const OPENTELEMETRY_PROVIDER_CONFIG_TOKEN: TracerConfig & {
24
+ __type?: "base token" | undefined;
25
+ };
26
+ export declare const OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN: SpanProcessor & {
27
+ __type?: "multi token" | undefined;
28
+ };
29
+ export declare const OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN: Resource & {
30
+ __type?: "base token" | undefined;
31
+ };
32
+ export declare const OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN: import("@opentelemetry/api").Attributes & {
33
+ __type?: "multi token" | undefined;
34
+ };
35
+ export declare const OPENTELEMETRY_TRACER_TOKEN: TramvaiTracer & {
36
+ __type?: "base token" | undefined;
37
+ };
38
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { createToken, Scope } from '@tinkoff/dippy';
2
+
3
+ const OPENTELEMETRY_PROVIDER_TOKEN = createToken('tramvai opentelemetry provider', { scope: Scope.SINGLETON });
4
+ const OPENTELEMETRY_PROVIDER_CONFIG_TOKEN = createToken('tramvai opentelemetry provider config', { scope: Scope.SINGLETON });
5
+ const OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN = createToken('tramvai opentelemetry provider span processor', { multi: true, scope: Scope.SINGLETON });
6
+ const OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN = createToken('tramvai opentelemetry provider resource', { scope: Scope.SINGLETON });
7
+ const OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN = createToken('tramvai opentelemetry provider resource attributes', { multi: true, scope: Scope.SINGLETON });
8
+ const OPENTELEMETRY_TRACER_TOKEN = createToken('tramvai opentelemetry tracer', { scope: Scope.SINGLETON });
9
+
10
+ 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 };
package/lib/tokens.js ADDED
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var dippy = require('@tinkoff/dippy');
6
+
7
+ const OPENTELEMETRY_PROVIDER_TOKEN = dippy.createToken('tramvai opentelemetry provider', { scope: dippy.Scope.SINGLETON });
8
+ const OPENTELEMETRY_PROVIDER_CONFIG_TOKEN = dippy.createToken('tramvai opentelemetry provider config', { scope: dippy.Scope.SINGLETON });
9
+ const OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN = dippy.createToken('tramvai opentelemetry provider span processor', { multi: true, scope: dippy.Scope.SINGLETON });
10
+ const OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN = dippy.createToken('tramvai opentelemetry provider resource', { scope: dippy.Scope.SINGLETON });
11
+ const OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN = dippy.createToken('tramvai opentelemetry provider resource attributes', { multi: true, scope: dippy.Scope.SINGLETON });
12
+ const OPENTELEMETRY_TRACER_TOKEN = dippy.createToken('tramvai opentelemetry tracer', { scope: dippy.Scope.SINGLETON });
13
+
14
+ exports.OPENTELEMETRY_PROVIDER_CONFIG_TOKEN = OPENTELEMETRY_PROVIDER_CONFIG_TOKEN;
15
+ exports.OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN = OPENTELEMETRY_PROVIDER_RESOURCE_ATTRIBUTES_TOKEN;
16
+ exports.OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN = OPENTELEMETRY_PROVIDER_RESOURCE_TOKEN;
17
+ exports.OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN = OPENTELEMETRY_PROVIDER_SPAN_PROCESSOR_TOKEN;
18
+ exports.OPENTELEMETRY_PROVIDER_TOKEN = OPENTELEMETRY_PROVIDER_TOKEN;
19
+ exports.OPENTELEMETRY_TRACER_TOKEN = OPENTELEMETRY_TRACER_TOKEN;
@@ -0,0 +1,16 @@
1
+ import type { Context, Span, SpanOptions, Tracer } from '@opentelemetry/api';
2
+ import type { TramvaiTracer } from '../tokens';
3
+ export declare class TramvaiTracerImpl implements TramvaiTracer {
4
+ private tracer;
5
+ constructor(tracer: Tracer);
6
+ startSpan(...args: any[]): Span;
7
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
8
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
9
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, ctx: Context, fn: F): ReturnType<F>;
10
+ getActiveSpan(): Span | undefined;
11
+ trace<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
12
+ trace<T>(name: string, fn: (span: Span) => T): T;
13
+ trace<T>(name: string, options: SpanOptions, fn: (span: Span) => Promise<T>): Promise<T>;
14
+ trace<T>(name: string, options: SpanOptions, fn: (span: Span) => T): T;
15
+ }
16
+ //# sourceMappingURL=tracer.d.ts.map
@@ -0,0 +1,70 @@
1
+ import isPromise from '@tinkoff/utils/is/promise';
2
+ import { trace, context, ROOT_CONTEXT, SpanStatusCode } from '@opentelemetry/api';
3
+
4
+ /* eslint-disable prefer-destructuring */
5
+ function recordAndThrowError(span, error) {
6
+ span.recordException(error);
7
+ span.setStatus({
8
+ code: SpanStatusCode.ERROR,
9
+ message: error?.message ?? 'Unknown error',
10
+ });
11
+ span.end();
12
+ throw error;
13
+ }
14
+ class TramvaiTracerImpl {
15
+ constructor(tracer) {
16
+ this.tracer = tracer;
17
+ }
18
+ startSpan(...args) {
19
+ // @ts-expect-error
20
+ return this.tracer.startSpan(...args);
21
+ }
22
+ startActiveSpan(...args) {
23
+ // @ts-expect-error
24
+ return this.tracer.startActiveSpan(...args);
25
+ }
26
+ getActiveSpan() {
27
+ return trace.getSpan(context.active());
28
+ }
29
+ trace(...args) {
30
+ const name = args[0];
31
+ let fn;
32
+ let options;
33
+ if (args.length === 2) {
34
+ fn = args[1];
35
+ options = {};
36
+ }
37
+ else {
38
+ fn = args[2];
39
+ options = args[1];
40
+ }
41
+ const activeSpan = trace.getSpan(context.active());
42
+ const spanContext = activeSpan ? trace.setSpan(context.active(), activeSpan) : undefined;
43
+ const ctx = spanContext ?? context.active() ?? ROOT_CONTEXT;
44
+ return this.startActiveSpan(name, options, ctx, (span) => {
45
+ try {
46
+ const result = fn(span);
47
+ // wrap promise fn, end span if promise is resolved or rejected
48
+ if (isPromise(result)) {
49
+ return result
50
+ .then((res) => {
51
+ span.end();
52
+ return res;
53
+ })
54
+ .catch((error) => {
55
+ recordAndThrowError(span, error);
56
+ });
57
+ }
58
+ // otherwise, end span immediately
59
+ span.end();
60
+ return result;
61
+ }
62
+ catch (error) {
63
+ recordAndThrowError(span, error);
64
+ }
65
+ });
66
+ }
67
+ }
68
+ /* eslint-enable prefer-destructuring */
69
+
70
+ export { TramvaiTracerImpl };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var isPromise = require('@tinkoff/utils/is/promise');
6
+ var api = require('@opentelemetry/api');
7
+
8
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
+
10
+ var isPromise__default = /*#__PURE__*/_interopDefaultLegacy(isPromise);
11
+
12
+ /* eslint-disable prefer-destructuring */
13
+ function recordAndThrowError(span, error) {
14
+ span.recordException(error);
15
+ span.setStatus({
16
+ code: api.SpanStatusCode.ERROR,
17
+ message: error?.message ?? 'Unknown error',
18
+ });
19
+ span.end();
20
+ throw error;
21
+ }
22
+ class TramvaiTracerImpl {
23
+ constructor(tracer) {
24
+ this.tracer = tracer;
25
+ }
26
+ startSpan(...args) {
27
+ // @ts-expect-error
28
+ return this.tracer.startSpan(...args);
29
+ }
30
+ startActiveSpan(...args) {
31
+ // @ts-expect-error
32
+ return this.tracer.startActiveSpan(...args);
33
+ }
34
+ getActiveSpan() {
35
+ return api.trace.getSpan(api.context.active());
36
+ }
37
+ trace(...args) {
38
+ const name = args[0];
39
+ let fn;
40
+ let options;
41
+ if (args.length === 2) {
42
+ fn = args[1];
43
+ options = {};
44
+ }
45
+ else {
46
+ fn = args[2];
47
+ options = args[1];
48
+ }
49
+ const activeSpan = api.trace.getSpan(api.context.active());
50
+ const spanContext = activeSpan ? api.trace.setSpan(api.context.active(), activeSpan) : undefined;
51
+ const ctx = spanContext ?? api.context.active() ?? api.ROOT_CONTEXT;
52
+ return this.startActiveSpan(name, options, ctx, (span) => {
53
+ try {
54
+ const result = fn(span);
55
+ // wrap promise fn, end span if promise is resolved or rejected
56
+ if (isPromise__default["default"](result)) {
57
+ return result
58
+ .then((res) => {
59
+ span.end();
60
+ return res;
61
+ })
62
+ .catch((error) => {
63
+ recordAndThrowError(span, error);
64
+ });
65
+ }
66
+ // otherwise, end span immediately
67
+ span.end();
68
+ return result;
69
+ }
70
+ catch (error) {
71
+ recordAndThrowError(span, error);
72
+ }
73
+ });
74
+ }
75
+ }
76
+ /* eslint-enable prefer-destructuring */
77
+
78
+ exports.TramvaiTracerImpl = TramvaiTracerImpl;
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@tramvai/module-opentelemetry",
3
+ "version": "5.14.9",
4
+ "description": "Интеграция OpenTelemetry",
5
+ "browser": "lib/browser.js",
6
+ "main": "lib/server.js",
7
+ "module": "lib/server.es.js",
8
+ "typings": "lib/server.d.d.ts",
9
+ "files": [
10
+ "lib"
11
+ ],
12
+ "sideEffects": false,
13
+ "license": "Apache-2.0",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+ssh://git@github.com/tramvaijs/tramvai.git"
17
+ },
18
+ "scripts": {
19
+ "build": "tramvai-build --forPublish --preserveModules",
20
+ "watch": "tsc -w"
21
+ },
22
+ "publishConfig": {
23
+ "registry": "https://registry.npmjs.org/"
24
+ },
25
+ "dependencies": {
26
+ "@opentelemetry/api": "^1.9.0",
27
+ "@opentelemetry/resources": "^1.28.0",
28
+ "@opentelemetry/sdk-trace-node": "^1.28.0",
29
+ "@opentelemetry/semantic-conventions": "^1.28.0",
30
+ "path-to-regexp": "0.1.7",
31
+ "@tramvai/tokens-common": "5.14.9",
32
+ "@tramvai/tokens-metrics": "5.14.9",
33
+ "@tramvai/tokens-http-client": "5.14.9",
34
+ "@tramvai/tokens-server": "5.14.9",
35
+ "@tramvai/tokens-server-private": "5.14.9",
36
+ "@tinkoff/utils": "^2.1.2",
37
+ "@tinkoff/errors": "0.6.2"
38
+ },
39
+ "devDependencies": {},
40
+ "peerDependencies": {
41
+ "@tinkoff/dippy": "0.11.3",
42
+ "@tramvai/core": "5.14.9",
43
+ "tslib": "^2.4.0"
44
+ }
45
+ }