Aplikacja działa. Żaden wyjątek nie poleciał, logi milczą, health check świeci na zielono. A jednak użytkownicy piszą, że „strona zamula". Sprawdzasz - odpowiedzi przychodzą po ośmiuset milisekundach zamiast po stu. Od kiedy? Nie wiadomo. Logi zapisują zdarzenia, a to jest trend: coś, co narastało tygodniami i czego nie widać w żadnym pojedynczym wpisie.
Rzym stawiał na granicach wieże sygnałowe. Nie meldowały pojedynczych zdarzeń - mierzyły ruch: ilu jeźdźców przejechało, jak długo trwa przeprawa, ilu wartowników nie wróciło. Dopiero z tych liczb widać było, że coś się psuje, zanim padła brama. Tym są metryki.
Standardem zbierania metryk jest Prometheus - system, który zbiera i przechowuje liczby opisujące aplikację. Oferuje cztery typy pomiaru, od najprostszego do najbardziej złożonego:
Do czasu trwania żądań HTTP właściwy jest Histogram. Counter powiedziałby tylko, ile żądań było, a Gauge - ile trwało ostatnie. Histogram pokazuje kształt: że dziewięć na dziesięć żądań mieści się poniżej 200 ms, a co dziesiąte przekracza sekundę. To ten kształt zdradza problem, którego średnia by nie pokazała.
Metrykę tworzysz raz, opisując ją trzema polami:
1import { Counter } from 'prom-client';
2
3@Injectable()
4export class PrometheusService {
5 private readonly httpRequestCounter = new Counter({
6 name: 'http_requests_total',
7 help: 'Total HTTP requests',
8 labelNames: ['method', 'route', 'status'],
9 });
10
11 recordRequest(method: string, route: string, status: number) {
12 this.httpRequestCounter.inc({ method, route, status: String(status) });
13 }
14}Kolejność pól jest umowna, ale zawsze ta sama:
to identyfikator metryki, name
- opis czytany przez człowieka, help
- lista wymiarów, po których będzie można ciąć dane.labelNames
Etykiety są tu najciekawsze. Dzięki nim jeden licznik odpowiada na wiele pytań: ile było żądań POST, ile trafiło na
/legions, ile zakończyło się kodem 500. Bez etykiet potrzebowałbyś osobnego licznika na każdą kombinację.Uwaga na pułapkę: etykieta o wielu możliwych wartościach mnoży liczbę serii danych. Wstawienie identyfikatora użytkownika jako etykiety utworzy tyle serii, ilu masz użytkowników - i położy Prometheusa. Etykiety mają mieć skończony, mały zbiór wartości, @name.
Zanim napiszesz własne metryki, warto włączyć te wbudowane:
1import { collectDefaultMetrics, register } from 'prom-client';
2
3collectDefaultMetrics();collectDefaultMetrics() zbiera domyślne metryki systemowe - zużycie procesora, pamięci i opóźnienie pętli zdarzeń Node.js. Ta ostatnia jest szczególnie cenna: rosnące opóźnienie event loopu znaczy, że coś blokuje wątek i aplikacja przestaje nadążać, choć żaden endpoint jeszcze nie pada.Zwróć uwagę, czego ta funkcja nie robi: niczego nie wysyła, nie tworzy wykresów ani nie zeruje liczników. Jedynie zaczyna zbierać.
Prometheus nie przyjmuje danych przysyłanych przez aplikację - sam po nie przychodzi. Twoim zadaniem jest wystawić je pod ustalonym adresem:
1@Controller()
2export class MetricsController {
3 @Get('/metrics')
4 async getMetrics(@Res() res: Response) {
5 res.set('Content-Type', register.contentType);
6 res.send(await register.metrics());
7 }
8}register to rejestr wszystkich zdefiniowanych metryk, a register.metrics() zwraca je w formacie tekstowym, który Prometheus rozumie. Nagłówek Content-Type musi wskazywać tekst zwykły, nie JSON - stąd register.contentType, które ustawia właściwą wartość za Ciebie.Ten model nazywa się scrapingiem: Prometheus co kilkanaście sekund odpytuje ten adres i zapisuje to, co zastał. Aplikacja nie musi wiedzieć, kto ją obserwuje ani czy ktokolwiek w ogóle.
Cała droga od zera do wykresu wygląda tak:
prom-client./metrics.Podział ról między dwoma ostatnimi bywa myląco zacierany. Prometheus zbiera i przechowuje, Grafana rysuje i alarmuje. To rozdzielenie sprawia, że możesz wymienić jedno bez drugiego - i że aplikacja nie zna żadnego z nich.
Wieże sygnałowe stoją, ruch jest mierzony:
name, help, labelNames,collectDefaultMetrics() zbiera metryki systemowe - CPU, pamięć, opóźnienie event loopu; niczego nie wysyła,/metrics z Content-Type ustawionym na tekst zwykły przez register.contentType,prom-client, zdefiniowanie metryk, endpoint /metrics, konfiguracja scrapera, wizualizacja w Grafanie,W następnej lekcji zejdziemy z poziomu trendów do pojedynczego błędu - poznasz techniki debugowania, gdy wiadomo już, że coś jest nie tak, ale nie wiadomo gdzie. A na razie zapamiętaj: log mówi, co się stało raz; metryka mówi, co dzieje się stale - i to ona ostrzega, zanim padnie brama.