Retour au cours

infra / monitoring-observabilite

Écrire des métriques applicatives

Leçon 31 exercice

Explication

Ce que vous allez apprendre

  • Exposer un endpoint /metrics compatible Prometheus depuis une application Python ou Node.js
  • Choisir entre les trois types de métriques : Counter, Gauge et Histogram
  • Mesurer automatiquement la latence et le débit d'une API via un middleware
  • Créer des métriques métier en plus des métriques purement techniques
  • Éviter dès l'écriture le piège de la cardinalité (approfondi plus loin dans le cours)

Dans quel contexte ?

Une équipe backend vient de brancher Prometheus (leçon précédente) mais il ne collecte encore rien d'utile : Prometheus sait où chercher, mais l'application mon-api n'expose aucune métrique. Le développeur doit maintenant instrumenter le code lui-même pour que chaque requête HTTP alimente des compteurs et des histogrammes consultables ensuite dans Grafana.

D'abord, il faut choisir le bon type de donnée à exposer

Prometheus ne propose pas un seul type de métrique générique : il en distingue plusieurs, chacun adapté à une forme de donnée précise. Se tromper de type rend une métrique inutilisable ou trompeuse une fois agrégée.

Le plus simple à comprendre est le Counter : une valeur qui ne fait qu'augmenter, comme un compteur de requêtes totales. Il redémarre à zéro uniquement si l'application elle-même redémarre — jamais autrement.

TypeComportementExemple concret
CounterToujours croissanthttp_requests_total, orders_created_total
GaugeMonte et descend librementactive_user_sessions, queue_depth
HistogramDistribution en buckets, percentiles calculables côté serveurhttp_request_duration_seconds
SummaryComme Histogram, mais percentiles calculés côté clientrarement recommandé en multi-instances

Une fois le Counter compris, le Gauge devient évident par contraste

Un Gauge représente une valeur instantanée qui peut aussi bien monter que descendre : le nombre de connexions actives, la taille d'une file d'attente, ou la température d'un processeur. Contrairement au Counter, on peut l'incrémenter ET le décrémenter (.inc() puis .dec()).

Prérequis

Cette leçon utilise directement le fichier prometheus.yml configuré à la leçon précédente. Assure-toi que Prometheus scrape bien mon-api:8000/metrics avant de continuer.

Il reste un problème : comment mesurer une latence, une distribution de valeurs ?

Un simple nombre ne suffit pas pour représenter "la durée des requêtes HTTP", car cette durée varie énormément d'une requête à l'autre. C'est exactement le rôle de l'Histogram : il classe chaque valeur observée dans des tranches ("buckets") prédéfinies, ce qui permet de calculer ensuite des percentiles (P50, P95, P99) directement dans Prometheus.

Piège fréquent

Les buckets par défaut de prometheus_client sont pensés pour des latences web classiques (millisecondes à quelques secondes). Un job batch qui dure plusieurs minutes, mesuré avec ces buckets par défaut, produira un histogramme totalement illisible : il faut définir des buckets adaptés à l'ordre de grandeur réel de la donnée mesurée.

Voici comment on assemble tout ça dans un vrai middleware

Le pattern devient alors mécanique : à chaque requête entrante, on incrémente un Gauge "en cours", on chronomètre la durée, on enregistre cette durée dans l'Histogram, puis on incrémente le Counter avec le status code final. C'est exactement ce que montre le middleware FastAPI ou Express du code de cette leçon.

Bonne pratique

Ne te limite pas aux métriques techniques (latence, erreurs). Ajoute aussi des métriques métier comme orders_created_total ou cart_value_euros : ce sont souvent elles qui ont le plus de valeur pour l'équipe produit, bien au-delà du seul monitoring technique.

Le piège à anticiper avant même d'écrire ton premier label

Ne mets jamais un user_id, un request_id ou un timestamp comme valeur de label : chaque valeur distincte crée une nouvelle série temporelle stockée séparément, et un identifiant unique par utilisateur peut multiplier ce nombre par centaines de milliers. Ce sujet, la cardinalité, mérite une leçon entière plus loin dans ce cours — retiens pour l'instant la règle simple : un label doit avoir un nombre de valeurs possibles connu et limité à l'avance.

Maintenant que l'application expose de vraies métriques, la prochaine étape logique est d'apprendre à les interroger avec PromQL, le langage de requête de Prometheus.

Commandes & code

Écrire des métriques applicatives

python
# Python — client officiel prometheus_client, avec FastAPI
from fastapi import FastAPI, Request
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST
from starlette.responses import Response
import time

app = FastAPI()

# Counter : ne fait qu'AUGMENTER (nombre de requêtes, erreurs, tâches traitées...)
REQUEST_COUNT = Counter(
    "http_requests_total", "Nombre total de requêtes HTTP",
    ["method", "path", "status"]
)

# Histogram : distribution de valeurs (latence, taille de payload) avec des "buckets"
REQUEST_LATENCY = Histogram(
    "http_request_duration_seconds", "Durée des requêtes HTTP",
    ["method", "path"],
    buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
)

# Gauge : valeur qui peut monter OU descendre (connexions actives, taille de file d'attente)
IN_PROGRESS = Gauge("http_requests_in_progress", "Requêtes en cours de traitement")


@app.middleware("http")
async def prometheus_middleware(request: Request, call_next):
    IN_PROGRESS.inc()
    start = time.perf_counter()
    response = await call_next(request)
    duration = time.perf_counter() - start

    REQUEST_LATENCY.labels(request.method, request.url.path).observe(duration)
    REQUEST_COUNT.labels(request.method, request.url.path, response.status_code).inc()
    IN_PROGRESS.dec()
    return response


@app.get("/metrics")
def metrics():
    return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)
javascript
// Node.js — client officiel prom-client, avec Express
const client = require("prom-client");
const express = require("express");
const app = express();

const register = new client.Registry();
client.collectDefaultMetrics({ register });   // CPU, mémoire, event loop lag... automatiques

const httpRequestDuration = new client.Histogram({
  name: "http_request_duration_seconds",
  help: "Durée des requêtes HTTP",
  labelNames: ["method", "route", "status_code"],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
});
register.registerMetric(httpRequestDuration);

app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer();
  res.on("finish", () => {
    end({ method: req.method, route: req.path, status_code: res.statusCode });
  });
  next();
});

app.get("/metrics", async (req, res) => {
  res.set("Content-Type", register.contentType);
  res.end(await register.metrics());
});
text
Choisir le bon type de métrique :

Counter   -> requêtes totales, erreurs totales, octets envoyés   (uniquement croissant, reset au redémarrage)
Gauge     -> connexions actives, taille de file, température CPU (monte ET descend)
Histogram -> latence, taille de payload  (distribution en buckets, permet de calculer des percentiles côté serveur)
Summary   -> comme Histogram mais calcule les percentiles côté CLIENT (moins flexible en agrégation multi-instances)
python
# Métriques métier, pas seulement techniques — souvent le plus précieux en production
ORDERS_CREATED = Counter("orders_created_total", "Commandes créées", ["payment_method"])
CART_VALUE = Histogram("cart_value_euros", "Valeur des paniers", buckets=[10, 25, 50, 100, 250, 500])
ACTIVE_SESSIONS = Gauge("active_user_sessions", "Sessions utilisateur actives")

# Exemple d'utilisation dans une route métier
@app.post("/orders")
def create_order(payment_method: str, total: float):
    ORDERS_CREATED.labels(payment_method=payment_method).inc()
    CART_VALUE.observe(total)
    return {"status": "created"}

Résumé

  • Counter (toujours croissant), Gauge (monte/descend), Histogram (distribution en buckets, percentiles calculables côté Prometheus) : 3 types à connaître par coeur.
  • Toujours exposer un endpoint /metrics que Prometheus viendra scraper (pattern "pull", cohérent avec la leçon précédente).
  • Les métriques MÉTIER (commandes créées, valeur panier) ont souvent plus de valeur business que les métriques purement techniques.
  • Limiter le nombre de labels et leurs valeurs possibles : un label avec un user_id en valeur crée une explosion de cardinalité (leçon dédiée plus loin).

Exercices pratiques

1 disponible
1

Mission : instrumenter la route commandes sans piéger Prometheus

Objectif : Choisir les bons types de métriques (Counter, Gauge, Histogram) pour instrumenter une route métier, sans introduire de cardinalité dangereuse.

Contexte

Le endpoint POST /orders de mon-api n'expose encore aucune métrique métier. Un collègue propose d'ajouter un label order_id sur le Counter de commandes créées "pour pouvoir chercher facilement une commande précise dans Grafana". Tu dois évaluer cette proposition et instrumenter la route correctement.

Résoudre l’exercice →