Imperium Rzymskie utrzymywało rozbudowaną sieć speculatores — zwiadowców i szpiegów, którzy zbierali informacje o stanie prowincji, ruchach wrogich armii i nastrojach ludności. W nowoczesnych aplikacjach, rolę speculatores pełni Prometheus — system zbierania i analizy metryk, który pozwala nam widzieć dokładnie, co dzieje się w naszej aplikacji w czasie rzeczywistym.
Metryki to liczbowe pomiary stanu aplikacji w czasie. W odróżnieniu od logów (tekstowe zdarzenia), metryki to serie liczbowe łatwe do agregacji i wizualizacji:
1// Logi vs Metryki
2const comparison = {
3 logi: {
4 typ: 'Tekstowe zdarzenia',
5 przyklad: '[INFO] Legionista zrekrutowany: leg_42',
6 uzycie: 'Debugowanie, audyt',
7 koszt: 'Wysoki storage przy dużym ruchu',
8 },
9 metryki: {
10 typ: 'Liczbowe serie czasowe',
11 przyklad: 'http_requests_total{method="GET", status="200"} 1547',
12 uzycie: 'Monitoring, alerting, dashboardy',
13 koszt: 'Niski — tylko liczby',
14 },
15};Prometheus definiuje cztery podstawowe typy metryk:
1// Cztery typy metryk Prometheus
2interface PrometheusMetrics {
3 counter: {
4 opis: 'Tylko rośnie (np. liczba żądań)';
5 przyklad: 'http_requests_total';
6 analogia: 'Licznik poległych w bitwach — tylko rośnie';
7 };
8 gauge: {
9 opis: 'Rośnie i maleje (np. aktywne połączenia)';
10 przyklad: 'active_connections';
11 analogia: 'Liczba legionistów w obozie — zmienia się';
12 };
13 histogram: {
14 opis: 'Rozkład wartości w bucketach (np. czasy odpowiedzi)';
15 przyklad: 'http_request_duration_seconds';
16 analogia: 'Czas przejścia depeszy — ile trwało 0-1s, 1-5s, 5-10s';
17 };
18 summary: {
19 opis: 'Podobny do histogram, ale z kwantylami (p50, p95, p99)';
20 przyklad: 'request_duration_summary';
21 analogia: 'Medianowy czas dostawy prowiantu';
22 };
23}Używamy pakietu
@willsoto/nestjs-prometheus:1yarn add @willsoto/nestjs-prometheus prom-client1// metrics/metrics.module.ts
2import { Module } from '@nestjs/common';
3import {
4 PrometheusModule,
5 makeCounterProvider,
6 makeGaugeProvider,
7 makeHistogramProvider,
8} from '@willsoto/nestjs-prometheus';
9
10@Module({
11 imports: [
12 PrometheusModule.register({
13 path: '/metrics', // Endpoint dla Prometheus
14 defaultMetrics: {
15 enabled: true, // Metryki Node.js (CPU, memory, GC)
16 },
17 }),
18 ],
19 providers: [
20 // Counter — liczba żądań HTTP
21 makeCounterProvider({
22 name: 'http_requests_total',
23 help: 'Calkowita liczba zadan HTTP',
24 labelNames: ['method', 'route', 'status'],
25 }),
26 // Gauge — aktywne połączenia
27 makeGaugeProvider({
28 name: 'active_connections',
29 help: 'Liczba aktywnych polaczen',
30 }),
31 // Histogram — czas odpowiedzi
32 makeHistogramProvider({
33 name: 'http_request_duration_seconds',
34 help: 'Czas odpowiedzi HTTP w sekundach',
35 labelNames: ['method', 'route', 'status'],
36 buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
37 }),
38 ],
39 exports: [PrometheusModule],
40})
41export class MetricsModule {}1// legion/legion.service.ts
2import { Injectable } from '@nestjs/common';
3import { InjectMetric } from '@willsoto/nestjs-prometheus';
4import { Counter, Histogram } from 'prom-client';
5
6@Injectable()
7export class LegionService {
8 constructor(
9 @InjectMetric('http_requests_total')
10 private requestsCounter: Counter<string>,
11
12 @InjectMetric('http_request_duration_seconds')
13 private requestDuration: Histogram<string>,
14 ) {}
15
16 async recruitLegionary(dto: CreateLegionaryDto) {
17 const timer = this.requestDuration.startTimer({
18 method: 'POST',
19 route: '/legiones',
20 });
21
22 try {
23 const result = await this.legionRepository.create(dto);
24 this.requestsCounter.inc({
25 method: 'POST',
26 route: '/legiones',
27 status: '201',
28 });
29 return result;
30 } finally {
31 timer({ status: '201' }); // Zapisz czas trwania
32 }
33 }
34}Po konfiguracji, endpoint
/metrics zwraca dane w formacie Prometheus:1# HELP http_requests_total Calkowita liczba zadan HTTP
2# TYPE http_requests_total counter
3http_requests_total{method="GET",route="/legiones",status="200"} 1547
4http_requests_total{method="POST",route="/legiones",status="201"} 342
5
6# HELP http_request_duration_seconds Czas odpowiedzi HTTP
7# TYPE http_request_duration_seconds histogram
8http_request_duration_seconds_bucket{le="0.01"} 890
9http_request_duration_seconds_bucket{le="0.05"} 1200
10http_request_duration_seconds_bucket{le="0.1"} 1400RED to sprawdzona metoda monitorowania serwisów:
1const redMethod = {
2 rate: {
3 opis: 'Liczba żądań na sekundę',
4 metryka: 'rate(http_requests_total[5m])',
5 alert: 'Gdy spadnie poniżej normy — możliwy problem',
6 },
7 errors: {
8 opis: 'Procent żądań z błędem',
9 metryka: 'rate(http_requests_total{status=~"5.."}[5m])',
10 alert: 'Gdy > 1% — coś jest nie tak',
11 },
12 duration: {
13 opis: 'Czas odpowiedzi (p95, p99)',
14 metryka: 'histogram_quantile(0.95, http_request_duration_seconds)',
15 alert: 'Gdy p95 > 500ms — degradacja wydajności',
16 },
17};Prometheus zbiera metryki, ale Grafana je wizualizuje jako dashboardy:
1# docker-compose.yml — Prometheus + Grafana
2services:
3 prometheus:
4 image: prom/prometheus:latest
5 volumes:
6 - ./prometheus.yml:/etc/prometheus/prometheus.yml
7 ports:
8 - "9090:9090"
9
10 grafana:
11 image: grafana/grafana:latest
12 ports:
13 - "3001:3000"
14 environment:
15 - GF_SECURITY_ADMIN_PASSWORD=imperium1# prometheus.yml
2scrape_configs:
3 - job_name: 'roman-api'
4 scrape_interval: 15s
5 static_configs:
6 - targets: ['api:4000']
7 metrics_path: '/metrics'1# alert.rules.yml
2groups:
3 - name: roman-api-alerts
4 rules:
5 - alert: HighErrorRate
6 expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.01
7 for: 5m
8 labels:
9 severity: critical
10 annotations:
11 summary: "Wysoki wskaznik bledow w API"Prometheus i Grafana to oczy naszego Imperium — widzą każdy ruch, mierzą każdą operację i alarmują, gdy coś jest nie tak. Bez metryk jesteśmy ślepi na problemy wydajnościowe.