Kurs NestJS · Moduł 12: Konteneryzacja i CI/CD
Observability - metryki z Prometheus
W tej lekcji5
Prometheusa poznałeś już w module o monitoringu: cztery typy metryk, scraping i endpoint /metrics napisany ręcznie kontrolerem. Działało - ale przy każdej nowej metryce trzeba było pamiętać o rejestrze, a przy każdym module o wstrzyknięciu serwisu, który je trzyma.
NestJS ma na to gotowe rozwiązanie. @willsoto/nestjs-prometheus zbiera i eksponuje metryki aplikacji w formacie Prometheus - a przy okazji sprawia, że metryki stają się zwykłymi providerami, wstrzykiwanymi jak każdy inny.
Rejestracja metryk
Metryki deklarujesz w module, przez funkcje pomocnicze pakietu:
1@Module({
2 imports: [PrometheusModule.register()],
3 providers: [
4 makeCounterProvider({
5 name: 'http_requests_total',
6 help: 'Calkowita liczba zadan HTTP',
7 labelNames: ['method', 'route', 'status'],
8 }),
9 makeGaugeProvider({
10 name: 'active_connections',
11 help: 'Liczba aktywnych polaczen',
12 }),
13 makeHistogramProvider({
14 name: 'http_request_duration_seconds',
15 help: 'Czas obslugi zadania HTTP',
16 labelNames: ['method', 'route'],
17 }),
18 ],
19})
20export class MetricsModule {}Kolejność wewnątrz makeCounterProvider jest stała: makeCounterProvider({, potem name: 'http_requests_total',, dalej help: '...',, na końcu }).
PrometheusModule.register() wystawia endpoint /metrics - tego już nie piszesz sam. Trzy pomocnicze funkcje odpowiadają trzem typom metryk, które znasz: Counter tylko rośnie (liczba żądań), Gauge rośnie i maleje (aktywne połączenia), Histogram mierzy rozkład w kubełkach (czasy odpowiedzi). Czwarty typ, Summary, oblicza kwantyle po stronie aplikacji.
Użycie w serwisie
Zarejestrowaną metrykę wstrzykujesz jak każdy provider - tyle że wskazując ją po nazwie:
1@Injectable()
2export class MetricsService {
3 constructor(
4 @InjectMetric('http_requests_total')
5 private readonly requestsCounter: Counter<string>,
6
7 @InjectMetric('http_request_duration_seconds')
8 private readonly requestDuration: Histogram<string>,
9 ) {}
10
11 recordRequest(method: string, route: string, status: number) {
12 this.requestsCounter.inc({ method, route, status: String(status) });
13 }
14
15 startTimer(method: string, route: string) {
16 return this.requestDuration.startTimer({ method, route });
17 }
18}@InjectMetric('nazwa') działa jak @Inject z tokenem, który poznałeś przy providerach z useValue - metryka nie jest klasą, więc NestJS nie dobierze jej po typie.
Dwie metody warto rozróżnić. inc() podnosi licznik o jeden; to wszystko, co Counter potrafi. startTimer() zwraca funkcję, którą wywołujesz po zakończeniu pracy - dopiero wtedy histogram zapisuje zmierzony czas:
1const timer = this.metricsService.startTimer('GET', '/legions');
2
3await this.legionService.findAll();
4
5timer();Ten wzorzec - weź funkcję, wykonaj pracę, wywołaj funkcję - jest wygodniejszy niż ręczne odejmowanie znaczników czasu i sam dba o jednostkę: histogramy Prometheusa liczą w sekundach, nie milisekundach.
Metoda RED - co mierzyć najpierw
Metryk można dodać setki. RED to skrót wskazujący trzy, od których zawsze zaczynasz, uporządkowane od najważniejszej:
- Rate - liczba żądań na sekundę. Mówi, czy w ogóle jest ruch.
- Errors - procent żądań zakończonych błędem. Mówi, czy ruch jest obsługiwany.
- Duration - czas odpowiedzi, zwykle jako p95 i p99. Mówi, czy jest obsługiwany dobrze.
Kolejność nie jest przypadkowa i warto ją zapamiętać: spadek Rate do zera znaczy, że nikt nie może się połączyć - to alarm natychmiastowy. Rosnące Errors to awaria widoczna dla użytkowników. Rosnące Duration to problem, który dopiero się rozwija.
Zwróć uwagę, dlaczego czas podajemy jako percentyle, a nie średnią. Średnia 200 ms brzmi dobrze, nawet gdy co dwudziesty użytkownik czeka pięć sekund - p95 mówi wprost: „95% żądań zmieściło się poniżej tej wartości". To jest liczba, która opisuje doświadczenie, a nie je zaciera.
Co zwraca /metrics
Endpoint wystawiony przez pakiet oddaje dane w formacie tekstowym Prometheusa - nie JSON, nie XML, nie CSV:
1# HELP http_requests_total Calkowita liczba zadan HTTP
2# TYPE http_requests_total counter
3http_requests_total{method="GET",route="/legions",status="200"} 1027
4http_requests_total{method="POST",route="/legions",status="201"} 43Kształt jest prosty i warto go umieć przeczytać. Linie zaczynające się od # HELP niosą opis metryki, a od # TYPE - jej typ. Dalej idą same pomiary: nazwa, etykiety w nawiasach klamrowych, wartość.
Ten format wygląda ubogo obok JSON-a, ale właśnie o to chodzi: Prometheus odpytuje tysiące aplikacji co kilkanaście sekund, więc parsowanie musi być tanie. Gdy zajrzysz pod /metrics w przeglądarce i zobaczysz ścianę tekstu - to znaczy, że działa poprawnie.
Podsumowanie
Oczy Imperium patrzą, a metryki są zwykłymi providerami:
@willsoto/nestjs-prometheuszbiera i eksponuje metryki w formacie Prometheus - nie obsługuje WebSocketów, nie testuje endpointów, nie generuje dokumentacji,PrometheusModule.register()wystawia endpoint/metricsza Ciebie,- metryki rejestrujesz jako providery:
makeCounterProvider,makeGaugeProvider,makeHistogramProvider, - kolejność w rejestracji:
makeCounterProvider({,name:,help:,}), - Counter może tylko rosnąć (liczba żądań); Gauge rośnie i maleje; Histogram mierzy rozkład w kubełkach; Summary oblicza kwantyle,
@InjectMetric('nazwa')wstrzykuje metrykę po nazwie, bo nie jest klasą i typ nie wystarcza,inc()podnosi licznik;startTimer()zwraca funkcję, którą wywołujesz po pracy - histogram liczy w sekundach,- metoda RED od najważniejszego: Rate (żądania na sekundę) → Errors (procent błędów) → Duration (p95, p99),
- czas podawaj jako percentyle, nie średnią - średnia ukrywa co dwudziestego użytkownika czekającego pięć sekund,
/metricszwraca format tekstowy Prometheusa:# HELP,# TYPE, potem nazwa z etykietami i wartość - nie JSON, XML ani CSV.
W następnej lekcji pójdziemy o krok dalej niż liczby - poznasz distributed tracing, który pokazuje drogę pojedynczego żądania przez wiele usług. A na razie zapamiętaj: metryki mówią, ile i jak szybko; RED wskazuje, które trzy liczby sprawdzić najpierw.
Kod do tej lekcji: src/prometheus-metrics.ts
1// Observability - Metryki z Prometheus
2console.log("=== PROMETHEUS METRICS ===\n");
3
4// Typy metryk Prometheus
5interface MetricType {
6 name: string;
7 type: 'counter' | 'gauge' | 'histogram' | 'summary';
8 description: string;
9 example: string;
10}
11
12const metricTypes: MetricType[] = [
13 {
14 name: 'Counter',
15 type: 'counter',
16 description: 'Tylko rosnie (np. liczba zadan)',
17 example: 'http_requests_total{method="GET",status="200"} 1547',
18 },
19 {
20 name: 'Gauge',
21 type: 'gauge',
22 description: 'Rosnie i maleje (np. aktywne polaczenia)',
23 example: 'active_connections 42',
24 },
25 {
26 name: 'Histogram',
27 type: 'histogram',
28 description: 'Rozklad wartosci w bucketach (np. czasy odpowiedzi)',
29 example: 'http_request_duration_seconds_bucket{le="0.1"} 1400',
30 },
31 {
32 name: 'Summary',
33 type: 'summary',
34 description: 'Kwantyle (p50, p95, p99)',
35 example: 'request_duration_summary{quantile="0.95"} 0.25',
36 },
37];
38
39console.log("Typy metryk Prometheus:\n");
40metricTypes.forEach((m, i) => {
41 console.log(`${i + 1}. ${m.name} (${m.type})`);
42 console.log(` Opis: ${m.description}`);
43 console.log(` Przyklad: ${m.example}\n`);
44});
45
46// Metoda RED
47console.log("=== METODA RED ===\n");
48const red = {
49 Rate: 'Liczba zadan na sekunde',
50 Errors: 'Procent zadan z bledem',
51 Duration: 'Czas odpowiedzi (p95, p99)',
52};
53
54Object.entries(red).forEach(([k, v]) => {
55 console.log(` ${k}: ${v}`);
56});
57
58console.log("\n=== ENDPOINT /metrics ===\n");
59console.log("# HELP http_requests_total Calkowita liczba zadan");
60console.log("# TYPE http_requests_total counter");
61console.log('http_requests_total{method="GET",status="200"} 1547');
62Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Do czego służy pakiet @willsoto/nestjs-prometheus?
2. Czym charakteryzuje się metryka typu Counter w Prometheus?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Zdefiniuj trzy metryki: http_requests_total (Counter z labelNames: method, route, status), active_connections (Gauge), http_request_duration_seconds (Histogram z bucketami)
- Układanie w pionie
Uporządkuj elementy metody RED od najważniejszego do najrzadziej sprawdzanego:
- Klikanie w kolejności
Ułóż elementy rejestracji metryki Counter w PrometheusModule:
- Edytor kodu
Wstrzyknij metryki @InjectMetric, użyj requestsCounter.inc() i requestDuration.startTimer() / timer()