Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Observability - metryki z Prometheus

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:

  1. Rate - liczba żądań na sekundę. Mówi, czy w ogóle jest ruch.
  2. Errors - procent żądań zakończonych błędem. Mówi, czy ruch jest obsługiwany.
  3. 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, @name.

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"} 43

Kształ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-prometheus
    zbiera i eksponuje metryki w formacie Prometheus
    - nie obsługuje WebSocketów, nie testuje endpointów, nie generuje dokumentacji,
  • PrometheusModule.register()
    wystawia endpoint
    /metrics
    za 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,
  • /metrics
    zwraca 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.

Przejdź do CodeWorlds