@application-kit/observability 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +36 -0
- package/dist/cjs/health/cli/health-cli-commands.d.ts +10 -0
- package/dist/cjs/health/cli/health-cli-commands.js +46 -0
- package/dist/cjs/health/cli/health-cli.d.ts +1 -10
- package/dist/cjs/health/cli/health-cli.js +17 -41
- package/dist/cjs/health/cli/index.d.ts +1 -0
- package/dist/cjs/health/cli/index.js +1 -0
- package/dist/esm/health/cli/health-cli-commands.d.ts +10 -0
- package/dist/esm/health/cli/health-cli-commands.js +43 -0
- package/dist/esm/health/cli/health-cli.d.ts +1 -10
- package/dist/esm/health/cli/health-cli.js +16 -40
- package/dist/esm/health/cli/index.d.ts +1 -0
- package/dist/esm/health/cli/index.js +1 -0
- package/dist/esm/tracing/index.d.ts +2 -0
- package/dist/esm/tracing/index.js +2 -0
- package/dist/esm/tracing/start-tracing.d.ts +3 -0
- package/dist/esm/tracing/start-tracing.js +55 -0
- package/dist/esm/tracing/tracing-options.interface.d.ts +8 -0
- package/dist/esm/tracing/tracing-options.interface.js +1 -0
- package/package.json +38 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @application-kit/observability
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 29b4378: Вход `/tracing` (только ESM): `startTracing({ esmHookedModules, instrumentations, resolveClientIp })` — OpenTelemetry
|
|
8
|
+
NodeSDK для `node --import` с OTLP gRPC-экспортёром, спанами HTTP-сервера без служебных путей и с `client.address`,
|
|
9
|
+
ESM-хуком строго по списку модулей и флашем на `beforeExit`; без endpoint'а коллектора не делает ничего.
|
|
10
|
+
|
|
11
|
+
## 0.3.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 3ed0384: `HealthCliModule`: команда `health` снова регистрируется в группе потребителя. В 0.3.0 она была унаследована подклассом-группой,
|
|
16
|
+
а сканер nestjs-console видит только собственные методы класса — CLI отвечал `too many arguments for '<группа>'`, и проверка
|
|
17
|
+
по крону не выполнялась.
|
|
18
|
+
|
|
3
19
|
## 0.3.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Peer-зависимости: `@nestjs/common`, `@prometheus-io/client`; оста
|
|
|
24
24
|
| `@application-kit/observability/http` | `resolveClientIp` — IP клиента за прокси | ничего |
|
|
25
25
|
| `@application-kit/observability/logging` | опции nestjs-pino, request-id, support-id, служебные пути, токен логгера | `@opentelemetry/api`, `class-validator`, `class-transformer`; типы — `nestjs-pino`, `pino` |
|
|
26
26
|
| `@application-kit/observability/metrics` | реестр, гистограммы HTTP и GraphQL, `GET /metrics`, хуки Fastify, сервер `/metrics` | `@nestjs/common`, `@prometheus-io/client`; типы — `fastify`, `@nestjs/platform-fastify` |
|
|
27
|
+
| `@application-kit/observability/tracing` | `startTracing` — OpenTelemetry NodeSDK для `node --import`, только ESM | `@opentelemetry/sdk-node`, `@opentelemetry/instrumentation`, `@opentelemetry/instrumentation-http`, `@opentelemetry/exporter-trace-otlp-grpc`, `@opentelemetry/semantic-conventions` |
|
|
27
28
|
|
|
28
29
|
Корневого входа нет: каждый вход несёт свои peer-зависимости, и лишнего потребитель не ставит.
|
|
29
30
|
|
|
@@ -137,3 +138,38 @@ export class WebModule {}
|
|
|
137
138
|
```ts
|
|
138
139
|
const clientIp = resolveClientIp(request, { trustedHeaders: ['cf-connecting-ip'] })
|
|
139
140
|
```
|
|
141
|
+
|
|
142
|
+
## Трейсинг (`/tracing`)
|
|
143
|
+
|
|
144
|
+
`startTracing({ esmHookedModules, instrumentations, resolveClientIp })` поднимает OpenTelemetry NodeSDK: OTLP gRPC-экспортёр,
|
|
145
|
+
спаны HTTP-сервера без служебных путей (`isInfrastructurePath` входа `/logging`) и с `client.address` корневого спана,
|
|
146
|
+
хук import-in-the-middle строго по списку модулей, флаш спанов на `beforeExit`. Endpoint, sampling, `service.name` и
|
|
147
|
+
атрибуты ресурса SDK читает из стандартных `OTEL_*`; без `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` (или
|
|
148
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT`) функция не делает ничего — ни хука, ни инструментаций — и отдаёт `undefined`.
|
|
149
|
+
|
|
150
|
+
Вызов — в отдельном файле, который бандлер собирает в свой выход и который процесс грузит флагом рантайма
|
|
151
|
+
`node --import ./tracing.mjs`: инструментации патчат модули в момент загрузки, а ESM выполняет все `import` бандла до
|
|
152
|
+
его первой строки, поэтому импорт из кода приложения опоздал бы. Вход только ESM — `module.register` есть лишь у
|
|
153
|
+
ESM-загрузчика.
|
|
154
|
+
|
|
155
|
+
- `esmHookedModules` — пакеты, которые бандл импортирует как ESM (например `kafkajs`): до них не дотягивается
|
|
156
|
+
require-in-the-middle. Хук без списка обернул бы и чанки самого бандла и порвал бы им live-binding.
|
|
157
|
+
- `instrumentations` — фабрика инструментаций продукта (фреймворк, драйвер БД, брокер): конструктор инструментации
|
|
158
|
+
сразу патчит модуль, поэтому вызывается фабрика, только когда трейсинг включён.
|
|
159
|
+
- `resolveClientIp` — резолвер адреса клиента; дефолт — `resolveClientIp` входа `/http` без доверенных заголовков.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// src/tracing.ts → vite build --ssr src/tracing.ts → node --import ./tracing.mjs ./web.mjs
|
|
163
|
+
startTracing({
|
|
164
|
+
esmHookedModules: ['kafkajs'],
|
|
165
|
+
instrumentations: () => [
|
|
166
|
+
new FastifyOtelInstrumentation({ registerOnInitialization: true }),
|
|
167
|
+
new PgInstrumentation(),
|
|
168
|
+
new KafkaJsInstrumentation(),
|
|
169
|
+
],
|
|
170
|
+
resolveClientIp: (request) => resolveClientIp(request, { trustedHeaders: ['cf-connecting-ip'] }),
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Версии `@opentelemetry/*` с номером `0.x` выходят одним релизом и между собой совместимы только в пределах одного минора:
|
|
175
|
+
`sdk-node`, `instrumentation`, `instrumentation-http` и `exporter-trace-otlp-grpc` ставятся одной версией `0.221.x`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HealthCheckService } from '@nestjs/terminus';
|
|
2
|
+
import type { ObservabilityLoggerInterface } from '../../logging/observability-logger.interface.js';
|
|
3
|
+
import { HealthChecks } from '../health-checks.js';
|
|
4
|
+
export declare class HealthCliCommands {
|
|
5
|
+
private readonly health;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
private readonly healthChecks;
|
|
8
|
+
constructor(health: HealthCheckService, logger: ObservabilityLoggerInterface, healthChecks: HealthChecks);
|
|
9
|
+
getHealth(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.HealthCliCommands = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const terminus_1 = require("@nestjs/terminus");
|
|
18
|
+
const constants_js_1 = require("../../logging/constants.js");
|
|
19
|
+
const health_checks_js_1 = require("../health-checks.js");
|
|
20
|
+
let HealthCliCommands = class HealthCliCommands {
|
|
21
|
+
health;
|
|
22
|
+
logger;
|
|
23
|
+
healthChecks;
|
|
24
|
+
constructor(health, logger, healthChecks) {
|
|
25
|
+
this.health = health;
|
|
26
|
+
this.logger = logger;
|
|
27
|
+
this.healthChecks = healthChecks;
|
|
28
|
+
}
|
|
29
|
+
async getHealth() {
|
|
30
|
+
try {
|
|
31
|
+
const healthResult = await this.health.check(this.healthChecks.build());
|
|
32
|
+
this.logger.info({ healthResult }, healthResult.status);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const healthResult = error instanceof common_1.ServiceUnavailableException ? error.getResponse() : 'unknown';
|
|
36
|
+
this.logger.error({ healthResult }, 'error');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.HealthCliCommands = HealthCliCommands;
|
|
41
|
+
exports.HealthCliCommands = HealthCliCommands = __decorate([
|
|
42
|
+
__param(0, (0, common_1.Inject)(terminus_1.HealthCheckService)),
|
|
43
|
+
__param(1, (0, common_1.Inject)(constants_js_1.OBSERVABILITY_LOGGER)),
|
|
44
|
+
__param(2, (0, common_1.Inject)(health_checks_js_1.HealthChecks)),
|
|
45
|
+
__metadata("design:paramtypes", [terminus_1.HealthCheckService, Object, health_checks_js_1.HealthChecks])
|
|
46
|
+
], HealthCliCommands);
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import type { Type } from '@nestjs/common';
|
|
2
|
-
import { HealthCheckService } from '@nestjs/terminus';
|
|
3
|
-
import type { ObservabilityLoggerInterface } from '../../logging/observability-logger.interface.js';
|
|
4
2
|
import type { HealthCliOptionsInterface } from './health-cli-options.interface.js';
|
|
5
|
-
import {
|
|
6
|
-
export declare class HealthCliCommands {
|
|
7
|
-
private readonly health;
|
|
8
|
-
private readonly logger;
|
|
9
|
-
private readonly healthChecks;
|
|
10
|
-
constructor(health: HealthCheckService, logger: ObservabilityLoggerInterface, healthChecks: HealthChecks);
|
|
11
|
-
getHealth(): Promise<void>;
|
|
12
|
-
}
|
|
3
|
+
import { HealthCliCommands } from './health-cli-commands.js';
|
|
13
4
|
export declare const createHealthCli: (options: HealthCliOptionsInterface) => Type<HealthCliCommands>;
|
|
@@ -8,49 +8,25 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.createHealthCli =
|
|
16
|
-
const common_1 = require("@nestjs/common");
|
|
17
|
-
const terminus_1 = require("@nestjs/terminus");
|
|
12
|
+
exports.createHealthCli = void 0;
|
|
18
13
|
const nestjs_console_1 = require("nestjs-console");
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
logger;
|
|
25
|
-
healthChecks;
|
|
26
|
-
constructor(health, logger, healthChecks) {
|
|
27
|
-
this.health = health;
|
|
28
|
-
this.logger = logger;
|
|
29
|
-
this.healthChecks = healthChecks;
|
|
30
|
-
}
|
|
31
|
-
async getHealth() {
|
|
32
|
-
try {
|
|
33
|
-
const healthResult = await this.health.check(this.healthChecks.build());
|
|
34
|
-
this.logger.info({ healthResult }, healthResult.status);
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
const healthResult = error instanceof common_1.ServiceUnavailableException ? error.getResponse() : 'unknown';
|
|
38
|
-
this.logger.error({ healthResult }, 'error');
|
|
14
|
+
const health_cli_commands_js_1 = require("./health-cli-commands.js");
|
|
15
|
+
const createHealthCli = (options) => {
|
|
16
|
+
let HealthCli = class HealthCli extends health_cli_commands_js_1.HealthCliCommands {
|
|
17
|
+
async getHealth() {
|
|
18
|
+
await super.getHealth();
|
|
39
19
|
}
|
|
40
|
-
}
|
|
20
|
+
};
|
|
21
|
+
__decorate([
|
|
22
|
+
(0, nestjs_console_1.Command)({ command: 'health' }),
|
|
23
|
+
__metadata("design:type", Function),
|
|
24
|
+
__metadata("design:paramtypes", []),
|
|
25
|
+
__metadata("design:returntype", Promise)
|
|
26
|
+
], HealthCli.prototype, "getHealth", null);
|
|
27
|
+
HealthCli = __decorate([
|
|
28
|
+
(0, nestjs_console_1.Console)({ command: options.command })
|
|
29
|
+
], HealthCli);
|
|
30
|
+
return HealthCli;
|
|
41
31
|
};
|
|
42
|
-
exports.HealthCliCommands = HealthCliCommands;
|
|
43
|
-
__decorate([
|
|
44
|
-
(0, nestjs_console_1.Command)({ command: 'health' }),
|
|
45
|
-
__metadata("design:type", Function),
|
|
46
|
-
__metadata("design:paramtypes", []),
|
|
47
|
-
__metadata("design:returntype", Promise)
|
|
48
|
-
], HealthCliCommands.prototype, "getHealth", null);
|
|
49
|
-
exports.HealthCliCommands = HealthCliCommands = __decorate([
|
|
50
|
-
__param(0, (0, common_1.Inject)(terminus_1.HealthCheckService)),
|
|
51
|
-
__param(1, (0, common_1.Inject)(constants_js_1.OBSERVABILITY_LOGGER)),
|
|
52
|
-
__param(2, (0, common_1.Inject)(health_checks_js_1.HealthChecks)),
|
|
53
|
-
__metadata("design:paramtypes", [terminus_1.HealthCheckService, Object, health_checks_js_1.HealthChecks])
|
|
54
|
-
], HealthCliCommands);
|
|
55
|
-
const createHealthCli = (options) => (0, with_class_decorator_js_1.withClassDecorator)(HealthCliCommands, (0, nestjs_console_1.Console)({ command: options.command }));
|
|
56
32
|
exports.createHealthCli = createHealthCli;
|
|
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./health-cli.js"), exports);
|
|
18
|
+
__exportStar(require("./health-cli-commands.js"), exports);
|
|
18
19
|
__exportStar(require("./health-cli-options.interface.js"), exports);
|
|
19
20
|
__exportStar(require("./health-cli.module.js"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HealthCheckService } from '@nestjs/terminus';
|
|
2
|
+
import type { ObservabilityLoggerInterface } from '../../logging/observability-logger.interface.js';
|
|
3
|
+
import { HealthChecks } from '../health-checks.js';
|
|
4
|
+
export declare class HealthCliCommands {
|
|
5
|
+
private readonly health;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
private readonly healthChecks;
|
|
8
|
+
constructor(health: HealthCheckService, logger: ObservabilityLoggerInterface, healthChecks: HealthChecks);
|
|
9
|
+
getHealth(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { Inject, ServiceUnavailableException } from '@nestjs/common';
|
|
14
|
+
import { HealthCheckService } from '@nestjs/terminus';
|
|
15
|
+
import { OBSERVABILITY_LOGGER } from '../../logging/constants.js';
|
|
16
|
+
import { HealthChecks } from '../health-checks.js';
|
|
17
|
+
let HealthCliCommands = class HealthCliCommands {
|
|
18
|
+
health;
|
|
19
|
+
logger;
|
|
20
|
+
healthChecks;
|
|
21
|
+
constructor(health, logger, healthChecks) {
|
|
22
|
+
this.health = health;
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.healthChecks = healthChecks;
|
|
25
|
+
}
|
|
26
|
+
async getHealth() {
|
|
27
|
+
try {
|
|
28
|
+
const healthResult = await this.health.check(this.healthChecks.build());
|
|
29
|
+
this.logger.info({ healthResult }, healthResult.status);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const healthResult = error instanceof ServiceUnavailableException ? error.getResponse() : 'unknown';
|
|
33
|
+
this.logger.error({ healthResult }, 'error');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
HealthCliCommands = __decorate([
|
|
38
|
+
__param(0, Inject(HealthCheckService)),
|
|
39
|
+
__param(1, Inject(OBSERVABILITY_LOGGER)),
|
|
40
|
+
__param(2, Inject(HealthChecks)),
|
|
41
|
+
__metadata("design:paramtypes", [HealthCheckService, Object, HealthChecks])
|
|
42
|
+
], HealthCliCommands);
|
|
43
|
+
export { HealthCliCommands };
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import type { Type } from '@nestjs/common';
|
|
2
|
-
import { HealthCheckService } from '@nestjs/terminus';
|
|
3
|
-
import type { ObservabilityLoggerInterface } from '../../logging/observability-logger.interface.js';
|
|
4
2
|
import type { HealthCliOptionsInterface } from './health-cli-options.interface.js';
|
|
5
|
-
import {
|
|
6
|
-
export declare class HealthCliCommands {
|
|
7
|
-
private readonly health;
|
|
8
|
-
private readonly logger;
|
|
9
|
-
private readonly healthChecks;
|
|
10
|
-
constructor(health: HealthCheckService, logger: ObservabilityLoggerInterface, healthChecks: HealthChecks);
|
|
11
|
-
getHealth(): Promise<void>;
|
|
12
|
-
}
|
|
3
|
+
import { HealthCliCommands } from './health-cli-commands.js';
|
|
13
4
|
export declare const createHealthCli: (options: HealthCliOptionsInterface) => Type<HealthCliCommands>;
|
|
@@ -7,46 +7,22 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
-
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
-
};
|
|
13
|
-
import { Inject, ServiceUnavailableException } from '@nestjs/common';
|
|
14
|
-
import { HealthCheckService } from '@nestjs/terminus';
|
|
15
10
|
import { Command, Console } from 'nestjs-console';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
logger;
|
|
22
|
-
healthChecks;
|
|
23
|
-
constructor(health, logger, healthChecks) {
|
|
24
|
-
this.health = health;
|
|
25
|
-
this.logger = logger;
|
|
26
|
-
this.healthChecks = healthChecks;
|
|
27
|
-
}
|
|
28
|
-
async getHealth() {
|
|
29
|
-
try {
|
|
30
|
-
const healthResult = await this.health.check(this.healthChecks.build());
|
|
31
|
-
this.logger.info({ healthResult }, healthResult.status);
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
const healthResult = error instanceof ServiceUnavailableException ? error.getResponse() : 'unknown';
|
|
35
|
-
this.logger.error({ healthResult }, 'error');
|
|
11
|
+
import { HealthCliCommands } from './health-cli-commands.js';
|
|
12
|
+
export const createHealthCli = (options) => {
|
|
13
|
+
let HealthCli = class HealthCli extends HealthCliCommands {
|
|
14
|
+
async getHealth() {
|
|
15
|
+
await super.getHealth();
|
|
36
16
|
}
|
|
37
|
-
}
|
|
17
|
+
};
|
|
18
|
+
__decorate([
|
|
19
|
+
Command({ command: 'health' }),
|
|
20
|
+
__metadata("design:type", Function),
|
|
21
|
+
__metadata("design:paramtypes", []),
|
|
22
|
+
__metadata("design:returntype", Promise)
|
|
23
|
+
], HealthCli.prototype, "getHealth", null);
|
|
24
|
+
HealthCli = __decorate([
|
|
25
|
+
Console({ command: options.command })
|
|
26
|
+
], HealthCli);
|
|
27
|
+
return HealthCli;
|
|
38
28
|
};
|
|
39
|
-
__decorate([
|
|
40
|
-
Command({ command: 'health' }),
|
|
41
|
-
__metadata("design:type", Function),
|
|
42
|
-
__metadata("design:paramtypes", []),
|
|
43
|
-
__metadata("design:returntype", Promise)
|
|
44
|
-
], HealthCliCommands.prototype, "getHealth", null);
|
|
45
|
-
HealthCliCommands = __decorate([
|
|
46
|
-
__param(0, Inject(HealthCheckService)),
|
|
47
|
-
__param(1, Inject(OBSERVABILITY_LOGGER)),
|
|
48
|
-
__param(2, Inject(HealthChecks)),
|
|
49
|
-
__metadata("design:paramtypes", [HealthCheckService, Object, HealthChecks])
|
|
50
|
-
], HealthCliCommands);
|
|
51
|
-
export { HealthCliCommands };
|
|
52
|
-
export const createHealthCli = (options) => withClassDecorator(HealthCliCommands, Console({ command: options.command }));
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
|
|
2
|
+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
3
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
4
|
+
import { ATTR_CLIENT_ADDRESS } from '@opentelemetry/semantic-conventions';
|
|
5
|
+
import { register } from 'node:module';
|
|
6
|
+
import nodeProcess from 'node:process';
|
|
7
|
+
import { resolveClientIp } from '../http/resolve-client-ip.js';
|
|
8
|
+
import { isInfrastructurePath } from '../logging/infrastructure-paths.js';
|
|
9
|
+
const withoutQuery = (url) => {
|
|
10
|
+
const queryStart = url.indexOf('?');
|
|
11
|
+
return queryStart === -1 ? url : url.slice(0, queryStart);
|
|
12
|
+
};
|
|
13
|
+
const isServiceRequest = (request) => request.url !== undefined
|
|
14
|
+
&& isInfrastructurePath(withoutQuery(request.url));
|
|
15
|
+
const createHttpInstrumentation = (clientIp = resolveClientIp) => {
|
|
16
|
+
const clientAddressAttributes = (request) => {
|
|
17
|
+
const address = clientIp(request);
|
|
18
|
+
return address === '' ? {} : { [ATTR_CLIENT_ADDRESS]: address };
|
|
19
|
+
};
|
|
20
|
+
return new HttpInstrumentation({
|
|
21
|
+
ignoreIncomingRequestHook: isServiceRequest,
|
|
22
|
+
startIncomingSpanHook: clientAddressAttributes,
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const flushSpans = async (sdk) => {
|
|
26
|
+
try {
|
|
27
|
+
await sdk.shutdown();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const tracesEndpoint = () => {
|
|
33
|
+
const { env } = nodeProcess;
|
|
34
|
+
return (env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ?? env.OTEL_EXPORTER_OTLP_ENDPOINT ?? '').trim();
|
|
35
|
+
};
|
|
36
|
+
export const startTracing = (options) => {
|
|
37
|
+
if (tracesEndpoint() === '') {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
register('@opentelemetry/instrumentation/hook.mjs', import.meta.url, {
|
|
41
|
+
data: { include: options.esmHookedModules },
|
|
42
|
+
});
|
|
43
|
+
const sdk = new NodeSDK({
|
|
44
|
+
instrumentations: [
|
|
45
|
+
createHttpInstrumentation(options.resolveClientIp),
|
|
46
|
+
...options.instrumentations?.() ?? [],
|
|
47
|
+
],
|
|
48
|
+
traceExporter: new OTLPTraceExporter(),
|
|
49
|
+
});
|
|
50
|
+
sdk.start();
|
|
51
|
+
nodeProcess.once('beforeExit', () => {
|
|
52
|
+
void flushSpans(sdk);
|
|
53
|
+
});
|
|
54
|
+
return sdk;
|
|
55
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Instrumentation } from '@opentelemetry/instrumentation';
|
|
2
|
+
import type { ClientIpResolver } from '../logging/pino-logger-options.interface.js';
|
|
3
|
+
export type InstrumentationsFactory = () => Instrumentation[];
|
|
4
|
+
export interface TracingOptionsInterface {
|
|
5
|
+
readonly esmHookedModules: readonly string[];
|
|
6
|
+
readonly instrumentations?: InstrumentationsFactory;
|
|
7
|
+
readonly resolveClientIp?: ClientIpResolver;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@application-kit/observability",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Наблюдаемость NestJS-процесса: метрики Prometheus и RED-гистограммы, health-чеки и пробы, опции pino с request-id и trace-контекстом, логирование CQRS-шин, реальный IP клиента за
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Наблюдаемость NestJS-процесса: метрики Prometheus и RED-гистограммы, health-чеки и пробы, опции pino с request-id и trace-контекстом, логирование CQRS-шин, реальный IP клиента за прокси, трейсинг OpenTelemetry",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cqrs",
|
|
7
7
|
"health",
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
"metrics",
|
|
10
10
|
"nestjs",
|
|
11
11
|
"observability",
|
|
12
|
+
"opentelemetry",
|
|
12
13
|
"pino",
|
|
13
14
|
"prometheus",
|
|
14
|
-
"terminus"
|
|
15
|
+
"terminus",
|
|
16
|
+
"tracing"
|
|
15
17
|
],
|
|
16
18
|
"homepage": "https://gitlab.com/application-kit/application-kit/-/tree/main/packages/observability",
|
|
17
19
|
"bugs": "https://gitlab.com/application-kit/application-kit/-/issues",
|
|
@@ -97,7 +99,13 @@
|
|
|
97
99
|
"default": "./dist/cjs/metrics/index.js"
|
|
98
100
|
}
|
|
99
101
|
},
|
|
100
|
-
"./package.json": "./package.json"
|
|
102
|
+
"./package.json": "./package.json",
|
|
103
|
+
"./tracing": {
|
|
104
|
+
"import": {
|
|
105
|
+
"types": "./dist/esm/tracing/index.d.ts",
|
|
106
|
+
"default": "./dist/esm/tracing/index.js"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
101
109
|
},
|
|
102
110
|
"main": "./dist/cjs/metrics/index.js",
|
|
103
111
|
"module": "./dist/esm/metrics/index.js",
|
|
@@ -106,7 +114,7 @@
|
|
|
106
114
|
"dist"
|
|
107
115
|
],
|
|
108
116
|
"scripts": {
|
|
109
|
-
"build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src/cqrs && node ../../scripts/barrels.mjs create ./src/health --entry-only=cli,graphql && node ../../scripts/barrels.mjs create ./src/http && node ../../scripts/barrels.mjs create ./src/logging && node ../../scripts/barrels.mjs create ./src/metrics && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
|
|
117
|
+
"build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src/cqrs && node ../../scripts/barrels.mjs create ./src/health --entry-only=cli,graphql && node ../../scripts/barrels.mjs create ./src/http && node ../../scripts/barrels.mjs create ./src/logging && node ../../scripts/barrels.mjs create ./src/metrics && node ../../scripts/barrels.mjs create ./src/tracing && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
|
|
110
118
|
"lint:tsc": "tsc --noEmit --pretty --project .",
|
|
111
119
|
"test:mutation": "stryker run",
|
|
112
120
|
"test:unit": "vitest run"
|
|
@@ -124,6 +132,11 @@
|
|
|
124
132
|
"@nestjs/testing": "^11.2.7",
|
|
125
133
|
"@nestjs/typeorm": "^11.0.3",
|
|
126
134
|
"@opentelemetry/api": "^1.9.1",
|
|
135
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "^0.221.0",
|
|
136
|
+
"@opentelemetry/instrumentation": "^0.221.0",
|
|
137
|
+
"@opentelemetry/instrumentation-http": "^0.221.0",
|
|
138
|
+
"@opentelemetry/sdk-node": "^0.221.0",
|
|
139
|
+
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
127
140
|
"@prometheus-io/client": "^0.16.0",
|
|
128
141
|
"@stryker-mutator/core": "^10.0.0",
|
|
129
142
|
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
@@ -149,6 +162,11 @@
|
|
|
149
162
|
"@nestjs/terminus": "^11.0.0",
|
|
150
163
|
"@nestjs/typeorm": "^11.0.0",
|
|
151
164
|
"@opentelemetry/api": "^1.9.0",
|
|
165
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "^0.221.0",
|
|
166
|
+
"@opentelemetry/instrumentation": "^0.221.0",
|
|
167
|
+
"@opentelemetry/instrumentation-http": "^0.221.0",
|
|
168
|
+
"@opentelemetry/sdk-node": "^0.221.0",
|
|
169
|
+
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
152
170
|
"@prometheus-io/client": "^0.16.0",
|
|
153
171
|
"class-transformer": "^0.5.1",
|
|
154
172
|
"class-validator": "^0.15.1",
|
|
@@ -178,6 +196,21 @@
|
|
|
178
196
|
"@opentelemetry/api": {
|
|
179
197
|
"optional": true
|
|
180
198
|
},
|
|
199
|
+
"@opentelemetry/exporter-trace-otlp-grpc": {
|
|
200
|
+
"optional": true
|
|
201
|
+
},
|
|
202
|
+
"@opentelemetry/instrumentation": {
|
|
203
|
+
"optional": true
|
|
204
|
+
},
|
|
205
|
+
"@opentelemetry/instrumentation-http": {
|
|
206
|
+
"optional": true
|
|
207
|
+
},
|
|
208
|
+
"@opentelemetry/sdk-node": {
|
|
209
|
+
"optional": true
|
|
210
|
+
},
|
|
211
|
+
"@opentelemetry/semantic-conventions": {
|
|
212
|
+
"optional": true
|
|
213
|
+
},
|
|
181
214
|
"class-transformer": {
|
|
182
215
|
"optional": true
|
|
183
216
|
},
|