@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 +153 -0
- package/lib/browser.d.ts +4 -0
- package/lib/browser.js +15 -0
- package/lib/instrumentation/httpClient.d.ts +11 -0
- package/lib/instrumentation/httpClient.es.js +97 -0
- package/lib/instrumentation/httpClient.js +101 -0
- package/lib/instrumentation/logs.d.ts +4 -0
- package/lib/instrumentation/logs.es.js +27 -0
- package/lib/instrumentation/logs.js +31 -0
- package/lib/instrumentation/server.d.ts +27 -0
- package/lib/instrumentation/server.es.js +125 -0
- package/lib/instrumentation/server.js +135 -0
- package/lib/server.d.ts +4 -0
- package/lib/server.es.js +127 -0
- package/lib/server.js +135 -0
- package/lib/tokens.browser.js +10 -0
- package/lib/tokens.d.ts +41 -0
- package/lib/tokens.es.js +10 -0
- package/lib/tokens.js +19 -0
- package/lib/tracer/tracer.d.ts +18 -0
- package/lib/tracer/tracer.es.js +78 -0
- package/lib/tracer/tracer.js +86 -0
- package/package.json +44 -0
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).
|
package/lib/browser.d.ts
ADDED
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,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 };
|