Intro
Cette version française explique Node.js monitoring and alerts with practical examples avec le même objectif pratique que l article source : aider le lecteur à comprendre le contexte, les décisions à prendre et les points à vérifier avant de passer à l action.
Les applications Node.js se développent vite, mais elles ne "semblent" rapides que si la latence, les erreurs et les dépendances sont sous contrôle. Ce guide montre concrètement quoi surveiller, comment instrumenter des métriques et des logs utiles, comment écrire des règles d'alerte qui réduisent le bruit, comment construire un Node.js dashboard focalisé et comment dérouler un Node.js incident response simple. Vous pouvez tout piloter en local d'abord, puis monter en charge en sécurité.
Vue d'ensemble du workflow
Un flux pragmatique pour aller des signaux à l'action :
- Identifier des signaux qui reflètent l'expérience utilisateur et la santé du service.
- Instrumenter des Node.js metrics et des logs structurés dans votre service Node.js.
- Collecter et stocker les données dans votre stack de monitoring.
- Visualiser via un dashboard ciblé pour accélérer le tri.
- Définir des Node.js alerts actionnables avec des seuils et des durées claires.
- Répondre avec un workflow d'incident léger et vérifier le retour à la normale.
- Revoir et affiner seuils, panneaux et runbooks après chaque événement.
Que surveiller dans Node.js
Concentrez-vous d'abord sur un petit nombre de signaux à forte valeur.
Golden signals
- Latence : p50, p95, p99 pour les routes clés et le service global.
- Débit : requêtes par seconde (RPS).
- Erreurs : taux de 5xx et types d'erreurs.
- Saturation : CPU, mémoire (RSS et heap), lag de la boucle d'événements.
Internes Node.js
- Lag de la boucle d'événements : un lag soutenu indique saturation ou pression GC.
- Garbage collection : temps et fréquence des major GC.
- Mémoire : heap utilisée vs max, croissance RSS (suspicion de fuite si croissance monotone sous charge stable).
- Redémarrages de process : sorties inattendues ou crash loops.
Dépendances
- MongoDB : latence des opérations, nombre d'erreurs, pool de connexions utilisé vs max.
- Redis : latence des commandes, timeouts, erreurs de connexion, profondeur de file/backlog.
- APIs externes : latence, taux de non‑2xx, ouvertures de circuit breaker.
Santé et readiness
- Statut de healthcheck et temps de réponse.
- Profondeur de file et taux succès/échec des workers.
Exemples d'instrumentation
Ci‑dessous, une app Express minimale avec des métriques Prometheus via prom-client, la mesure du lag de la boucle d'événements et des logs JSON structurés avec un identifiant de requête.
// app.js
'use strict';
const express = require('express');
const { performance, monitorEventLoopDelay } = require('perf_hooks');
const client = require('prom-client');
const crypto = require('crypto');
const app = express();
const register = new client.Registry();
// Étiqueter toutes les métriques avec un nom de service pour filtrer
const serviceName = process.env.SERVICE_NAME || 'api';
register.setDefaultLabels({ service: serviceName });
// Métriques par défaut du process Node.js (CPU, mémoire, GC, etc.)
client.collectDefaultMetrics({ register });
// Jauge de lag de la boucle d'événements (secondes)
const ell = monitorEventLoopDelay({ resolution: 20 });
ell.enable();
const eventLoopLag = new client.Gauge({
name: 'event_loop_lag_seconds',
help: 'Lag de la boucle d'événements en secondes (moyenne sur l'intervalle de scrape)'
});
register.registerMetric(eventLoopLag);
setInterval(() => {
eventLoopLag.set(ell.mean / 1é); // mean en nanosecondes
ell.reset();
}, 1000);
// Histogramme de durée des requêtes HTTP
const httpDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'Durée des requêtes HTTP en secondes',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 5]
});
register.registerMetric(httpDuration);
// Compteur d'erreurs HTTP
const httpErrors = new client.Counter({
name: 'http_request_errors_total',
help: 'Nombre total de requêtes HTTP en erreur',
labelNames: ['route', 'status_code', 'cause']
});
register.registerMetric(httpErrors);
// Middleware d'ID de requête pour corrélation des logs
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || crypto.randomUUID();
res.setHeader('x-request-id', req.id);
next();
});
// Middleware de timing + logging
app.use(async (req, res, next) => {
const start = performance.now();
res.on('finish', () => {
const ms = performance.now() - start;
const route = req.route ? req.route.path : req.path;
httpDuration.labels(req.method, route, String(res.statusCode)).observe(ms / 1000);
const log = {
ts: new Date().toISOString(),
level: res.statusCode >= 500 ? 'error' : 'info',
msg: 'request',
req_id: req.id,
method: req.method,
route,
status: res.statusCode,
latency_ms: Math.round(ms),
service: serviceName
};
// Émettre sur stdout ; collecte par votre agent de logs
process.stdout.write(JSON.stringify(log) + '\n');
});
next();
});
// Routes d'exemple
app.get('/api/todos', (req, res) => {
// Travail variable simulé
setTimeout(() => res.json([{ id: 1, title: 'learn monitoring' }]), Math.random() * 80);
});
app.get('/api/error', (req, res) => {
const err = new Error('forced');
httpErrors.labels('/api/error', '500', 'forced').inc();
res.status(500).json({ error: err.message });
});
// Endpoint des métriques
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
// Log de démarrage
const log = { ts: new Date().toISOString(), level: 'info', msg: 'listening', port, service: serviceName };
process.stdout.write(JSON.stringify(log) + '\n');
});
Exemples de logs structurés (stdout) pour un contrôle visuel rapide :
{"ts":"2025-01-01T12:00:00.000Z","level":"info","msg":"request","req_id":"c1b2","method":"GET","route":"/api/todos","status":200,"latency_ms":42,"service":"api"}
{"ts":"2025-01-01T12:00:01.000Z","level":"error","msg":"request","req_id":"c1b3","method":"GET","route":"/api/error","status":500,"latency_ms":3,"service":"api"}
Règles d'alerte et signaux de logs
Commencez par des alertes qui reflètent la douleur utilisateur et la saturation soutenue. Ci‑dessous, des exemples Prometheus alignés sur les métriques précédentes.
SLO de latence en rupture (p95 > 300 ms pendant 10 minutes) :
groups:
- name: node-api-latency
rules:
- alert: NodeServiceHighLatencyP95
expr: |
histogram_quantile(
0.95,
sum(rate(http_request_duration_seconds_bucket{service="api"}[5m])) by (le)
) > 0.3
for: 10m
labels:
severity: page
service: api
annotations:
summary: "Latence p95 élevée sur api"
description: "p95 > 300ms pendant 10m"
Taux d'erreur > 2 % pendant 10 minutes (utilise l'histogramme _count comme total) :
groups:
- name: node-api-errors
rules:
- alert: NodeServiceHighErrorRate
expr: |
sum(rate(http_request_errors_total{service="api"}[5m]))
/
sum(rate(http_request_duration_seconds_count{service="api"}[5m]))
> 0.02
for: 10m
labels:
severity: page
service: api
annotations:
summary: "Taux d'erreurs élevé sur api"
description: ">2% d'erreurs pendant 10m"
Lag de boucle d'événements soutenu au‑dessus de 200 ms pendant 5 minutes :
groups:
- name: node-api-ell
rules:
- alert: NodeServiceEventLoopLagHigh
expr: event_loop_lag_seconds{service="api"} > 0.2
for: 5m
labels:
severity: ticket
service: api
annotations:
summary: "Lag de boucle d'événements élevé"
description: ">200ms pendant 5m suggère saturation ou pression GC"
Alertes optionnelles sur les dépendances (adaptez aux noms de vos jauges) :
- Saturation du pool MongoDB : db_pool_in_use / db_pool_max > 0.8 pendant 10m.
- Backlog Redis : worker_queue_backlog > 1000 pendant 10m.
Signaux de logs à suivre
- Pics de 5xx par route :
- Filtre : level=error ET status>=500, group by route.
- Erreurs de dépendances :
- Mongo network errors : message contient "MongoNetworkError" ou code "ETIMEDOUT".
- Redis timeouts : message contient "RedisTimeoutError" ou "ECONNRESET".
- Répondeurs lents :
- latency_ms>500 avec status<500 indique de la lenteur sans échec franc.
Gardez les alertes actionnables
- Liez d'abord les alertes à des symptômes visibles (latence, taux d'erreur).
- Utilisez une fenêtre temporelle (for:) pour éviter les transitoires.
- Orientez les métriques internes bruyantes (ex. pauses GC brèves) vers ticket/notification plutôt qu'une astreinte.
Tableaux de bord orientés action
Organisez pour décider, pas pour empiler des données.
Première rangée (golden signals)
- Latence p50/p95/p99 (globale et top 3 routes) :
- Query :
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{service="api"}[5m])) by (le, route)) - Débit (rps) :
- Query :
sum(rate(http_request_duration_seconds_count{service="api"}[1m])) - Taux d'erreur (%) :
- Query :
100 * sum(rate(http_request_errors_total{service="api"}[5m])) / sum(rate(http_request_duration_seconds_count{service="api"}[5m]))
Santé des ressources
- Lag de boucle d'événements (secondes) :
event_loop_lag_seconds{service="api"} - Mémoire RSS et heap utilisée :
process_resident_memory_bytes,nodejs_heap_size_used_bytes - CPU user/system :
process_cpu_user_seconds_total,process_cpu_system_seconds_total(en rates)
Drilldowns
- Routes les plus lentes : trier p95 par route.
- Latence et erreurs des dépendances : panneaux par métrique MongoDB/Redis.
- Marqueur de déploiement récent : ajoutez un label build/version aux métriques et annotez les changements.
Lien vers les logs
- Ajoutez des liens qui passent route et x-request-id à votre viewer pour sauter d'un panneau lent aux requêtes exactes.
Workflows de réponse aux incidents
Visez un triage rapide et une mitigation sûre.
- Accuser réception et cadrer l'impact
- Vérifier les golden signals : est‑ce visible côté utilisateur (latence ou erreurs) ?
- Identifier le périmètre : route unique, service entier ou dépendance.
- Formuler une hypothèse en 2-3 minutes
- Latence élevée avec CPU normal : probable lenteur d'une dépendance.
- Lag élevé + RSS en hausse : pression GC ou travail synchrone sur le thread principal.
- Pic d'erreurs après un changement : régression ou dérive de configuration.
- Mitiger
- Revenir en arrière ou désactiver le changement risqué.
- Réduire la charge (rate limits) sur des routes spécifiques si nécessaire.
- Redémarrer des réplicas récalcitrants seulement si l'impact est compris.
- Vérifier le rétablissement
- Latence et taux d'erreur reviennent dans les bandes normales.
- Lag de boucle d'événements se stabilise.
- Les logs montrent moins de 5xx et d'erreurs de dépendances.
- Capturer les notes et améliorer
- Documenter le déclencheur, le correctif et les suivis (panneaux à ajouter, seuils à ajuster, code à optimiser).
Runbooks de départ
- Mémoire/RSS en hausse continue : prendre un heap snapshot en environnement sûr, rechercher gros buffers ou caches sans TTL.
- Dépendance lente : ajouter timeouts et retries avec jitter ; envisager circuit breaker et isolation (bulkhead) sur les chemins chauds.
- Lag de boucle d'événements : déplacer le travail CPU‑intensif ou bloquant vers un worker thread ou un process séparé ; éviter les appels sync FS/crypto sur le thread principal.
Plan pilote local
Démarrez petit, mesurez et inspectez en local avant la généralisation.
Périmètre
- Un service, deux routes (/api/todos et /api/error).
- Métriques : histogramme de durée, compteur d'erreurs, jauge de lag, métriques process par défaut.
- Alertes : p95 de latence, taux d'erreur.
- Dashboard : golden signals et lag de boucle d'événements.
Étapes
- Ajouter le middleware et les métriques depuis l'exemple.
- Exposer /metrics et confirmer le texte de métriques.
- Générer charge et erreurs en local :
- Frapper /api/todos à répétition pour remplir les histogrammes.
- Frapper /api/error pour incrémenter les compteurs d'erreurs.
- Valider les alertes en abaissant temporairement les seuils pour déclencher en quelques minutes, puis restaurer des niveaux sains.
- Construire un dashboard minimal avec 4 panneaux : latence p95, rps, taux d'erreur, lag.
- Ajouter un request ID côté client (x-request-id) et vérifier sa présence dans logs et réponses pour la corrélation.
Critères de sortie
- Vous voyez p50/p95 bouger sous charge et se stabiliser ensuite.
- L'alerte de taux d'erreur ne se déclenche que lors de pannes forcées et s'éteint quand elles cessent.
- Le lag de boucle reste proche de zéro en charge normale et monte sous stress.
Montée en charge
- Ajouter des labels de route à vos 5 endpoints principaux.
- Ajouter ensuite les métriques de dépendances (usage de pool MongoDB, latence/backlog Redis).
- Déployer vers un service de production à la fois et vérifier panneaux et alertes.
Conclusion
Un Node.js monitoring efficace commence par quelques métriques à haut signal : latence, débit, taux d'erreur et saturation. Instrumentez‑les avec des labels clairs, exposez un endpoint /metrics et émettez des logs structurés avec des request IDs. Gardez les premières Node.js alerts simples et centrées sur l'utilisateur, et construisez un Node.js dashboard qui accélère le triage. Pilotez en local, vérifiez les comportements, puis étendez aux endpoints et dépendances critiques. Affinez les seuils après de vrais incidents et gardez vos runbooks courts et actionnables.