Introduction
La surveillance transforme une Express API d'une boîte noire en un service de confiance. Dans ce guide, vous allez :
- Ajouter des métriques et des logs de niveau production à une Express API
- Définir des règles d'alerte qui détectent les vrais problèmes sans bruit
- Construire des tableaux de bord qui répondent vite aux questions d'astreinte
- Mettre en pratique un workflow d'incident qui réduit le MTTR
C'est un guide pratique, avec du code, des règles et un petit pilote local à exécuter avant de toucher à la production. Les mots-clés de référence (pour rester repérables dans l'écosystème) apparaissent naturellement : Express API monitoring, Express API alerts, Express API metrics, Express API dashboard, Express API incident response.
Vue d'ensemble du workflow
Une approche étagée limite le rework, clarifie les rôles et accélère le passage des idées aux implémentations fonctionnelles.
- Instrumenter
- Ajouter des métriques et des logs dans le processus API.
- Exposer un endpoint /metrics et des probes de santé.
- Collecter
- Scraper ou pousser les métriques vers votre base time-series.
- Expédier les logs structurés vers un store consultable.
- Visualiser
- Construire des dashboards pour les golden signals et les routes lentes.
- Alerter
- Définir des règles à fort signal alignées sur l'impact utilisateur et les SLOs.
- Répondre
- Trier, mitiger et communiquer via un runbook concis.
- Améliorer
- Ajuster les seuils, enrichir les labels, réduire le bruit.
Métriques et signaux clés
Express est à la périphérie HTTP. Concentrez-vous d'abord sur les golden signals : latence, trafic, erreurs et saturation.
Métriques à collecter
- Total des requêtes : compteur labellisé par méthode, route, status_code.
- Total des erreurs : compteur filtré status_code >= 500.
- Durée des requêtes : histogramme en millisecondes, labels méthode, route, status_code.
- Requêtes actives : gauge pour détecter la saturation lors des pics.
- Métriques de process : lag de boucle d'événements, heap utilisée, CPU, handles ouverts.
- Latence/erreurs de dépendances : HTTP sortant, base (ex. MongoDB), cache.
Exemple d'instrumentation Express
Ajoutez des métriques légères avec prom-client. Gardez des labels à faible cardinalité. Préférez les templates de route (par ex. /users/:id) aux chemins bruts.
// metrics.js
const client = require('prom-client');
// Métriques par défaut de Node.js
client.collectDefaultMetrics({ prefix: 'node_' });
const httpRequestDurationMs = new client.Histogram({
name: 'http_request_duration_ms',
help: 'Durée des requêtes HTTP en ms',
labelNames: ['method', 'route', 'status_code'],
buckets: [10, 50, 100, 300, 500, 1000, 2000, 5000]
});
const httpRequestsTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total des requêtes HTTP',
labelNames: ['method', 'route', 'status_code']
});
const httpRequestsErrorsTotal = new client.Counter({
name: 'http_requests_errors_total',
help: 'Total des réponses HTTP 5xx',
labelNames: ['method', 'route']
});
const activeRequests = new client.Gauge({
name: 'http_active_requests',
help: 'Requêtes HTTP en cours',
labelNames: ['route']
});
function metricsMiddleware(req, res, next) {
const route = req.route && req.route.path ? req.route.path : req.path || 'unknown';
activeRequests.labels(route).inc();
const end = httpRequestDurationMs.startTimer();
res.on('finish', () => {
const status = res.statusCode;
const labels = { method: req.method, route, status_code: String(status) };
httpRequestsTotal.labels(labels.method, labels.route, labels.status_code).inc();
if (status >= 500) {
httpRequestsErrorsTotal.labels(labels.method, labels.route).inc();
}
end(labels);
activeRequests.labels(route).dec();
});
next();
}
async function metricsEndpoint(req, res) {
res.set('Content-Type', client.register.contentType);
res.end(await client.register.metrics());
}
module.exports = {
metricsMiddleware,
metricsEndpoint,
};
Intégration avec Express :
// server.js
const express = require('express');
const { metricsMiddleware, metricsEndpoint } = require('./metrics');
const pino = require('pino');
const pinoHttp = require('pino-http');
const app = express();
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
app.use(pinoHttp({ logger, genReqId: () => crypto.randomUUID() }));
app.use(express.json());
app.use(metricsMiddleware);
app.get('/healthz', (req, res) => res.status(200).send('ok'));
app.get('/readyz', (req, res) => res.status(200).send('ready'));
// Exemples de routes
app.get('/users/:id', async (req, res) => {
// Simule du travail
await new Promise(r => setTimeout(r, Math.random() * 200));
res.json({ id: req.params.id });
});
app.get('/error', (req, res) => {
res.status(500).json({ error: 'boom' });
});
app.get('/metrics', metricsEndpoint);
const port = process.env.PORT || 3000;
app.listen(port, () => logger.info({ port }, 'api-listening'));
Astuce : assurez-vous que les labels de route utilisent des templates (par ex. /users/:id). Beaucoup de routeurs exposent req.route.path après le match. Les chemins bruts peuvent exploser la cardinalité.
Règles d'alerte qui comptent
Des règles d'alerte doivent refléter l'impact utilisateur et des conditions soutenues. Évitez les pics d'un seul échantillon. Utilisez une fenêtre courte de mise en page (5-15 min) et des clauses for.
Taux d'erreur
Pager quand le ratio 5xx dépasse 2 % pendant 5 minutes.
- alert: HighErrorRate
expr: (
sum(rate(http_requests_errors_total[5m]))
/
sum(rate(http_requests_total[5m]))
) > 0.02
for: 5m
labels:
severity: page
annotations:
summary: 'Elevated 5xx error rate (>2% for 5m)'
description: 'Investigate recent deploys, dependencies, and logs.'
Latence élevée (p95)
Pager quand la p95 dépasse 300 ms pendant 10 minutes.
- alert: HighLatencyP95
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_ms_bucket[5m])) by (le)
) > 0.3
for: 10m
labels:
severity: page
annotations:
summary: 'p95 latency > 300ms for 10m'
description: 'Check saturated workers, DB, and hot routes.'
Note : dans beaucoup de systèmes, la durée est en secondes. 0.3 s = 300 ms.
Saturation
Pager quand les requêtes en vol restent élevées.
- alert: SaturatedWorkers
expr: avg_over_time(http_active_requests[5m]) > 100
for: 10m
labels:
severity: page
annotations:
summary: 'High in-flight HTTP requests'
description: 'Scale out or shed load if needed.'
Ajustez le seuil selon votre capacité de concurrence.
Pas de trafic ou chute soudaine
Avertir quand le débit de requêtes tombe proche de zéro aux heures attendues.
- alert: NoTraffic
expr: sum(rate(http_requests_total[5m])) < 1
for: 10m
labels:
severity: warn
annotations:
summary: 'Request rate near zero'
description: 'Possible upstream issue, DNS, or LB route.'
Logs et traces
Quand une alerte se déclenche, il faut des réponses rapides. Structurez les logs pour la recherche et la corrélation.
Champs de log recommandés
- ts: horodatage ISO
- level: info, warn, error
- msg: message court
- request_id: UUID par requête
- method, route, path
- status, latency_ms
- user_id ou account_id (si pertinent et sûr)
- remote_ip, user_agent (attention aux politiques PII)
- service, version, commit_sha
Exemple Pino avec corrélation
const pino = require('pino');
const pinoHttp = require('pino-http');
const { randomUUID } = require('crypto');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
app.use(pinoHttp({
logger,
genReqId: (req) => req.headers['x-request-id'] || randomUUID(),
customLogLevel: function (res, err) {
if (res.statusCode >= 500 || err) return 'error';
if (res.statusCode >= 400) return 'warn';
return 'info';
},
customSuccessMessage: function () {
return 'request-complete';
},
customErrorMessage: function () {
return 'request-error';
},
serializers: {
req(req) {
return {
id: req.id,
method: req.method,
route: req.route && req.route.path ? req.route.path : req.url,
};
},
res(res) {
return { statusCode: res.statusCode };
},
},
}));
Signaux de log utiles
- Pics de 5xx et de fermetures client (type 499)
- Timeouts (ETIMEDOUT), resets (ECONNRESET), épuisement de pools
- Erreurs répétées liées à une route, un tenant ou une version
- Long tails : quelques requêtes très lentes avec médiane basse
Si vous adoptez plus tard le traçage distribué, commencez par propager request_id comme en-tête de trace dans votre stack.
Tableaux de bord efficaces
Un dashboard doit répondre aux 5 questions clés d'astreinte en une minute.
Panneaux recommandés :
- Trafic : requêtes par seconde, par route et méthode
- Latence : tendances p50, p95, p99 ; top N des routes lentes du moment
- Erreurs : taux d'erreurs par status_code et par route
- Saturation : requêtes en vol, lag de boucle, CPU, heap, pauses GC
- Dépendances : latence et erreurs DB, HTTP sortant
- Version : métriques par version/commit pour décider un rollback rapide
Conseils de mise en page :
- Une page unique de triage, avec liens vers des vues détaillées.
- Annotez avec des marqueurs de déploiement.
- Couleurs et unités cohérentes ; millisecondes lisibles pour la latence.
Workflow d'incident
Un flux léger et reproductible transforme les alertes en rétablissement rapide.
- Accuser réception et évaluer
- Confirmez l'alerte et ouvrez le tableau de bord de triage.
- Mesurez l'impact utilisateur (taux d'erreurs, latence, chute de trafic).
- Stabiliser
- Restaurez la version précédente si régression probable.
- Scale-out, ajout de circuit breaker, ou rate limit des tenants bruyants.
- Diagnostiquer
- Filtrez les logs par request_id, route et version.
- Comparez les routes chaudes et les panneaux de dépendances.
- Mitiger
- Publiez un correctif de config, feature flag, ou hot patch.
- Ajoutez des timeouts/retries temporaires pour les dépendances fragiles.
- Vérifier
- Assurez-vous que les alertes se résorbent et que les golden signals reviennent au niveau de base.
- Apprendre
- Capturez ce qui a détecté le problème, ce qui a aidé et ce qui manquait.
- Ajustez alertes et dashboards ; ajoutez labels ou nouvelles métriques.
Gardez un runbook court par alerte avec propriétaire, vérifications et remèdes.
Plan de pilote local
Commencez petit, mesurez et inspectez en local avant déploiement. Un pilote restreint et mesurable réduit le risque et clarifie les étapes suivantes.
1) Exécuter l'API en local
Installez les dépendances :
npm i express prom-client pino pino-http
node server.js
Frappez des endpoints et vérifiez les métriques :
curl -s localhost:3000/healthz
curl -s localhost:3000/users/123
curl -s localhost:3000/error
curl -s localhost:3000/metrics | head
2) Optionnel : scraper et alerter en local
Utilisez un docker-compose minimal pour une base de métriques, un dashboard et un composant d'alerte.
version: '3.9'
services:
api:
build: .
ports: ['3000:3000']
prometheus:
image: prom/prometheus: latest
ports: ['9090:9090']
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml: ro
alertmanager:
image: prom/alertmanager: latest
ports: ['9093:9093']
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml: ro
grafana:
image: grafana/grafana: latest
ports: ['3001:3000']
Exemple de prometheus.yml :
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'express-api'
metrics_path: /metrics
static_configs:
- targets: ['api:3000']
rule_files:
- rules.yml
Exemple de rules.yml avec deux alertes :
groups:
- name: express
rules:
- alert: HighErrorRate
expr: (sum(rate(http_requests_errors_total[5m])) / sum(rate(http_requests_total[5m]))) > 0.02
for: 5m
labels: { severity: page }
annotations:
summary: 'Elevated 5xx error rate'
- alert: HighLatencyP95
expr: histogram_quantile(0.95, sum(rate(http_request_duration_ms_bucket[5m])) by (le)) > 0.3
for: 10m
labels: { severity: page }
annotations:
summary: 'p95 latency > 300ms'
3) Générer du trafic de test
# trafic stable et sain
for i in $(seq 1 200); do curl -s localhost:3000/users/$i > /dev/null; done
# induire latence et erreurs
curl -s localhost:3000/error > /dev/null
Observez le dashboard et validez que les alertes ne se déclenchent que lorsque les conditions persistent.
4) Définir pass/fail et documenter
- Succès si les métriques apparaissent, les dashboards s'affichent, et les alertes se déclenchent puis s'effacent comme prévu.
- Échec si les labels explosent en cardinalité, si les alertes sont bruyantes ou si des métriques manquent.
- Notez les seuils, ensembles de labels et lacunes à corriger avant la mise en scène.
Conclusion
Vous avez une base pratique pour l'Express API monitoring :
- Golden signals et labels à faible cardinalité
- Règles d'alerte actionnables reflétant l'impact utilisateur
- Logs corrélés qui accélèrent le diagnostic
- Un Express API dashboard qui répond vite aux questions d'astreinte
- Un workflow Express API incident response simple et un pilote local sûr
Prochaines étapes :
- Ajustez les seuils selon votre trafic et votre capacité
- Ajoutez des métriques de dépendances (bases de données, appels sortants)
- Étendez les logs avec la propagation de request_id entre services
- Pilotez sur un service en staging, puis déployez progressivement
Un démarrage mesuré et un workflow clair vous aident à passer des idées à une Express API fiable et observable.