Сервис, который нельзя наблюдать, нельзя и эксплуатировать: при сбое останется гадать. Наблюдаемость — три опоры (логи, трейсы, метрики) плюс health-checks для оркестратора. NestJS даёт встроенный логгер и официальный модуль здоровья, остальное стыкуется со стандартными инструментами Node.
Структурные логи
У NestJS есть встроенный Logger, но для прода логи должны быть машиночитаемыми — строками JSON с полями. На практике стандартный логгер заменяют на структурный (например, через nestjs-pino), сохраняя тот же интерфейс. Ключевое поле — идентификатор запроса, проставленный в interceptor: по нему строки одного запроса сшиваются.
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class CreateProductHandler {
private readonly logger = new Logger(CreateProductHandler.name);
async handle(command: CreateProductCommand) {
const product = await this.repo.save(/* ... */);
this.logger.log({ message: 'product_created', productId: product.id });
return product;
}
}
Важно не средство, а правило: лог — это событие с полями, а не свободный текст в консоль.
Health-checks
Для Kubernetes нужны два разных сигнала, и путать их опасно. liveness — жив ли процесс (провал → перезапусти); должен быть простым. readiness — готов ли принимать трафик (провал → не шли запросы); тут уместно проверить базу. Официальный модуль — @nestjs/terminus.
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly db: TypeOrmHealthIndicator,
) {}
@Get('ready')
@HealthCheck()
ready() {
return this.health.check([() => this.db.pingCheck('database')]);
}
@Get('live')
@HealthCheck()
live() {
return this.health.check([]);
}
}
Ошибка — делать liveness зависимым от базы: упала база, Kubernetes решит, что процесс мёртв, и начнёт перезапускать, хотя перезапуск чужую базу не лечит. Liveness — про себя, readiness — про готовность.
Трейсинг: OpenTelemetry
Трейс показывает путь запроса через сервис и дальше — в базу, в соседний сервис — и где сколько времени ушло. Стандарт — OpenTelemetry; его Node SDK авто-инструментирует HTTP-сервер, TypeORM и клиентов, добавляя span на каждый запрос. Трейсы уходят в коллектор (Jaeger, Tempo) настройкой экспортёра, не кодом приложения. Корреляция запроса из логов сшивается с трейсом — вместе они отвечают на «что случилось с этим конкретным запросом».
Метрики: Prometheus
Метрики — числа во времени: число запросов, задержки, ошибки. Стандарт — Prometheus, который периодически забирает их с эндпоинта /metrics. В Node это prom-client; для NestJS есть готовые интеграции, поднимающие базовые метрики (число и длительность запросов, коды) и отдающие их на /metrics. Свои бизнес-счётчики (например, созданных заказов) добавляют поверх. Метрики отвечают на «что происходит в целом», трейсы — на «что с этим запросом».
Где это в UCP
Наблюдаемость — не украшение перед продом, а часть определения «готово»: сервис, который нельзя продиагностировать, не доведён до конца. Три опоры отвечают на разные вопросы, health-checks дают оркестратору правду о состоянии. Это тот же набор, что Actuator, Micrometer и трейсинг в Spring-биндинге, только инструментами Node. Для продукт-инженера, который владеет сервисом до пользователя, наблюдаемость закрывает последний участок пути: увидеть, что выкаченное работает, и быстро понять, если нет.