E-NO
Surveillance REST API 11 min de lecture

Surveillance et alertes d'API REST avec exemples pratiques : guide d'implémentation

calendar_today Publié : 2026-07-21
update Dernière mise à jour : 2026-07-21
analytics Efficacité SEO : 100%
Illustration du guide technique pour « Surveillance et alertes d'API REST avec exemples pratiques : guide d'implémentation ».

Une API REST fiable fait trois choses bien : elle expose des signaux de santé clairs, déclenche des alertes opportunes et supporte une réponse aux incidents fluide. Ce guide montre quoi surveiller, quelles métriques comptent, comment écrire des règles d'alerte qui évitent le bruit, et comment concevoir logs et tableaux de bord pour un diagnostic rapide. Vous obtiendrez aussi des exemples copier-coller en Node.js Express avec MongoDB plus des appels externes vers Stripe et l'API OpenAI, et un Plan Pilote Local sûr pour tout valider sur votre portable.

Pour qui :

  • Développeurs qui construisent ou opèrent des API REST
  • Consultants DevOps déployant des standards de production
  • Équipes techniques de startups voulant du signal sans sur-ingénierie

Résultats attendus :

  • Une liste de contrôle concrète de métriques et alertes pour API REST
  • Une journalisation structurée qui accélère le triage
  • Des tableaux de bord qui montrent l'essentiel
  • Un flux de travail d'incident reproductible
  • Un pilote local exécutable aujourd'hui

Vue d'ensemble du flux de travail

Un flux de travail de surveillance d'API REST fiable comporte six étapes :

  1. Instrumenter
  • Émettre les métriques RED par route : Rate (débit), Errors (erreurs), Duration (durée)
  • Ajouter les métriques de ressources : CPU, mémoire, latence de la boucle d'événements
  • Envelopper les dépendances : MongoDB, Stripe, OpenAI
  • Utiliser request_id pour corréler logs et métriques
  1. Collecter
  • Exposer /metrics pour le scraping
  • Envoyer les logs JSON à votre collecteur (stdout dans les conteneurs suffit)
  1. Alerter
  • Écrire un petit ensemble d'alertes nettes et actionnables
  • Privilégier les conditions basées sur les SLO aux seuils bruyants
  1. Visualiser
  • Tableaux de bord qui répondent : est-ce up, est-ce rapide, qui est cassé
  1. Répondre
  • Procédures légères, propriétaires clairs, chemins de rollback rapides
  1. Apprendre
  • Après incidents, affiner métriques, alertes et procédures

Quoi surveiller et pourquoi

Signaux de base d'API REST

  • Disponibilité : taux 2xx vs 5xx, par route
  • Latence : p50, p95, p99, par route et méthode
  • Trafic : RPS (requêtes par seconde) par route et méthode
  • Erreurs : patterns 4xx (auth, validation) et 5xx (bugs, timeouts)
  • Saturation : CPU, mémoire, latence de la boucle d'événements, longueur de file
  • Timeouts et retries : compte et taux par dépendance
  • Auth et rate limiting : tendances 401/403/429

Dépendances

  • MongoDB : latence d'opération (find, insert, update), usage du pool de connexions, timeouts, codes d'erreur
  • Caches/files : taux de hit, profondeur de file, lag consommateur

Fournisseurs externes

  • Stripe : succès et latence par endpoint, 4xx vs 5xx, 429s, quota restant, conflits d'idempotence
  • API OpenAI : succès, latence, usage de tokens par requête, 429s, timeouts, erreurs de filtre de contenu le cas échéant

Métriques et règles d'alerte

Utiliser les principes RED et USE :

  • RED (Rate, Errors, Duration) par route : requêtes par seconde, taux d'erreur, histogrammes de durée
  • USE (Utilization, Saturation, Errors) par ressource : utilisation CPU, saturation mémoire, compteurs d'erreurs

Exemples de noms de métriques

  • http_server_requests_total{method, route, status_code}
  • http_server_request_duration_seconds_bucket
  • process_cpu_seconds_total, process_resident_memory_bytes
  • nodejs_eventloop_lag_seconds (si disponible)
  • mongo_query_duration_seconds_bucket{operation, collection, outcome}
  • third_party_request_total{provider, operation, outcome}
  • third_party_request_duration_seconds_bucket{provider, operation}
  • third_party_rate_limit_exhausted_total{provider}

Exemples d'alertes PromQL (adapter à vos labels)

Taux d'erreur élevé

sum(rate(http_server_requests_total{status_code=~"5.."}[5m]))
 /
sum(rate(http_server_requests_total[5m]))
 > 0.05
for: 10m

Régression de latence (exclure /metrics)

histogram_quantile(
 0.95,
 sum by (le) (rate(http_server_request_duration_seconds_bucket{route!=\"\\/metrics\"}[5m]))
) > 0.300
for: 10m

Lag de la boucle d'événements (Node.js)

max_over_time(nodejs_eventloop_lag_seconds[5m]) > 0.050
for: 10m

Timeouts ou erreurs MongoDB

sum(rate(mongo_query_duration_seconds_count{outcome="timeout"}[5m])) > 1
or
sum(rate(mongo_query_errors_total[5m])) > 1

Échecs fournisseurs et 429s

sum(rate(third_party_request_total{provider=~"stripe|openai",outcome="error"}[5m])) > 2
or
increase(third_party_rate_limit_exhausted_total{provider=~"stripe|openai"}[15m]) > 0

Pensée basée sur les SLO (Service Level Objectives - objectifs de niveau de service)

  • Définir les SLI (Service Level Indicators - indicateurs de niveau de service) : success_rate, request_latency
  • Fixer les SLO : ex. 99,5 % de succès, p95 < 300 ms, 24x7
  • Alerter sur la consommation du budget d'erreur : alertes multi-fenêtres fast burn et slow burn

Logs et événements structurés

Concevoir des logs JSON pour répondre à qui, quoi, où, quand et pourquoi :

Inclure dans chaque log de requête

  • ts : timestamp ISO
  • level : info|warn|error
  • request_id : UUID défini à l'entrée
  • method, route, path, status
  • duration_ms
  • remote_ip (sanitized), user_agent (optionnel)
  • user_id ou tenant_id si disponible et autorisé

Champs de log pour appels dépendances

  • dependency : mongo|stripe|openai
  • operation : findOne|charges.create|chat.completions
  • outcome : ok|error|timeout|rate_limited
  • status ou code
  • duration_ms
  • retry_after_ms ou rate_limit_remaining si disponible

Vie privée et sécurité

  • Ne jamais logger secrets, tokens, données de carte, ou prompts avec PII (Personally Identifiable Information - informations personnellement identifiables)
  • Hasher ou omettre identifiants utilisateur si incertain
  • Échantillonner les logs de payload verbeux à faible taux en non-production

Tableaux de bord qui comptent

Tableau de bord d'accueil

  • Première ligne : taux de succès, latence p95, RPS, 5xx par route
  • Ligne de tendances : percentiles de latence p50-p99, consommation budget d'erreur, marqueurs de déploiement

Drill-down par route

  • Percentiles de latence, débit, distribution 4xx vs 5xx par route
  • Top messages d'erreur et signatures de stack

Dépendances

  • MongoDB : latence p50-p99 par opération, pool connexions en cours, timeouts
  • Fournisseurs : taux de succès, latence, 429s, quota restant

Saturation

  • CPU, mémoire, lag boucle d'événements, pauses GC, redémarrages conteneurs

Vue de garde

  • Alertes ouvertes, top contrevenants, liens vers procédures

Exemples pratiques par stack

Les extraits ci-dessous montrent une configuration minimale mais prête pour la production pour une API Express avec métriques style Prometheus et logs structurés. Ils incluent l'instrumentation MongoDB et des wrappers pour les appels Stripe et OpenAI.

Configuration Express avec métriques et logs

Installation

npm i express prom-client pino pino-http uuid

server.js

const express = require('express');
const client = require('prom-client');
const pino = require('pino');
const pinoHttp = require('pino-http');
const { v4: uuidv4 } = require('uuid');

const app = express();
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });

// Registre Prometheus et métriques de processus par défaut
const register = new client.Registry();
client.collectDefaultMetrics({ register });

// Métriques RED
const httpDuration = new client.Histogram({
 name: 'http_server_request_duration_seconds',
 help: 'Durée de requête HTTP en secondes',
 labelNames: ['method', 'route', 'status_code']
});
const httpRequests = new client.Counter({
 name: 'http_server_requests_total',
 help: 'Total requêtes HTTP',
 labelNames: ['method', 'route', 'status_code']
});
register.registerMetric(httpDuration);
register.registerMetric(httpRequests);

// Request ID et logging
app.use((req, res, next) => {
 req.id = req.headers['x-request-id'] || uuidv4();
 res.setHeader('x-request-id', req.id);
 next();
});
app.use(pinoHttp({ logger, genReqId: req => req.id }));

// Middleware de timing
app.use((req, res, next) => {
 const end = httpDuration.startTimer({ method: req.method });
 res.on('finish', () => {
 const route = req.route ? req.route.path : req.path;
 const labels = { method: req.method, route, status_code: String(res.statusCode) };
 httpRequests.inc(labels, 1);
 end({ route, status_code: String(res.statusCode) });
 req.log.info({
 msg: 'request_complete', request_id: req.id, method: req.method,
 route, status: res.statusCode, duration_ms: res.responseTime
 });
 });
 next();
});

// Routes d'exemple
app.get('/healthz', (req, res) => res.status(200).json({ ok: true }));

app.get('/api/v1/items/:id', async (req, res) => {
 // Imaginez un appel DB ici
 res.json({ id: req.params.id, name: 'example' });
});

// Endpoint métriques
app.get('/metrics', async (req, res) => {
 res.set('Content-Type', register.contentType);
 res.end(await register.metrics());
});

// Gestionnaire d'erreurs
app.use((err, req, res, next) => {
 req.log.error({ msg: 'unhandled_error', request_id: req.id, err });
 res.status(500).json({ error: 'internal_error', request_id: req.id });
});

const port = process.env.PORT || 3000;
app.listen(port, () => logger.info({ msg: 'listening', port }));

Notes

  • Corrélation : x-request-id est répercuté pour aligner clients et logs
  • Métriques : le label route utilise req.route.path quand disponible pour éviter l'explosion de cardinalité

Instrumentation MongoDB

Installation

npm i mongodb

mongo.js

const { MongoClient } = require('mongodb');
const client = require('prom-client');

const mongoDuration = new client.Histogram({
 name: 'mongo_query_duration_seconds',
 help: 'Durée requête MongoDB',
 labelNames: ['operation', 'collection', 'outcome']
});

async function withTiming(op, collection, fn) {
 const end = mongoDuration.startTimer({ operation: op, collection });
 try {
 const result = await fn();
 end({ outcome: 'ok' });
 return result;
 } catch (e) {
 const outcome = /timeout/i.test(e.message) ? 'timeout' : 'error';
 end({ outcome });
 throw e;
 }
}

async function createClient(uri) {
 const cli = new MongoClient(uri, { maxPoolSize: 20 });
 await cli.connect();
 return cli;
}

module.exports = { withTiming, createClient, mongoDuration };

Utilisation dans une route

// dans server.js
const { createClient, withTiming } = require('./mongo');

let db;
(async () => {
 const cli = await createClient(process.env.MONGO_URI || 'mongodb://localhost:27017');
 db = cli.db('app');
})();

app.get('/api/v1/items/:id', async (req, res, next) => {
 try {
 const id = req.params.id;
 const doc = await withTiming('findOne', 'items', () => db.collection('items').findOne({ _id: id }));
 if (!doc) return res.status(404).json({ error: 'not_found' });
 res.json(doc);
 } catch (e) { next(e); }
});

Wrapper Stripe

Installation

npm i stripe

stripe.js

const Stripe = require('stripe');
const client = require('prom-client');

const stripe = new Stripe(process.env.STRIPE_API_KEY || 'sk_test_xxx');

const tpReqs = new client.Counter({
 name: 'third_party_request_total',
 help: 'Requêtes tierces',
 labelNames: ['provider', 'operation', 'outcome']
});
const tpDur = new client.Histogram({
 name: 'third_party_request_duration_seconds',
 help: 'Durée requête tierce',
 labelNames: ['provider', 'operation']
});
const tpRateExhaust = new client.Counter({
 name: 'third_party_rate_limit_exhausted_total',
 help: 'Quota rate limit épuisé tierce partie',
 labelNames: ['provider']
});

async function createCharge(params) {
 const op = 'charges.create';
 const end = tpDur.startTimer({ provider: 'stripe', operation: op });
 try {
 const res = await stripe.charges.create(params);
 tpReqs.inc({ provider: 'stripe', operation: op, outcome: 'ok' });
 if (res.headers && Number(res.headers['stripe-rate-limit-remaining']) === 0) {
 tpRateExhaust.inc({ provider: 'stripe' });
 }
 end();
 return res;
 } catch (e) {
 const outcome = e.statusCode === 429 ? 'rate_limited' : 'error';
 tpReqs.inc({ provider: 'stripe', operation: op, outcome });
 if (e.statusCode === 429) tpRateExhaust.inc({ provider: 'stripe' });
 end();
 throw e;
 }
}

module.exports = { createCharge };

Wrapper API OpenAI

Installation

npm i openai

openai.js

const { OpenAI } = require('openai');
const client = require('prom-client');

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY || 'sk-xxx' });

const tpReqs = new client.Counter({
 name: 'third_party_request_total',
 help: 'Requêtes tierces',
 labelNames: ['provider', 'operation', 'outcome']
});
const tpDur = new client.Histogram({
 name: 'third_party_request_duration_seconds',
 help: 'Durée requête tierce',
 labelNames: ['provider', 'operation']
});
const tpRateExhaust = new client.Counter({
 name: 'third_party_rate_limit_exhausted_total',
 help: 'Quota rate limit épuisé tierce partie',
 labelNames: ['provider']
});

async function createChatCompletion(msgs) {
 const op = 'chat.completions';
 const end = tpDur.startTimer({ provider: 'openai', operation: op });
 try {
 const res = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: msgs });
 tpReqs.inc({ provider: 'openai', operation: op, outcome: 'ok' });
 end();
 return res;
 } catch (e) {
 const code = e.status || e.code || 0;
 const outcome = code === 429 ? 'rate_limited' : 'error';
 tpReqs.inc({ provider: 'openai', operation: op, outcome });
 if (code === 429) tpRateExhaust.inc({ provider: 'openai' });
 end();
 throw e;
 }
}

module.exports = { createChatCompletion };

Ajouter une route qui utilise les wrappers

// dans server.js
const { createCharge } = require('./stripe');
const { createChatCompletion } = require('./openai');

app.post('/api/v1/payments', express.json(), async (req, res, next) => {
 try {
 const charge = await createCharge({ amount: 100, currency: 'usd', source: 'tok_visa' });
 res.json({ id: charge.id, status: charge.status });
 } catch (e) { next(e); }
});

app.post('/api/v1/assist', express.json(), async (req, res, next) => {
 try {
 const { prompt } = req.body;
 const chat = await createChatCompletion([{ role: 'user', content: prompt }]);
 res.json({ reply: chat.choices?.[0]?.message?.content || '' });
 } catch (e) { next(e); }
});

Flux de travail de réponse aux incidents

Rendre les alertes actionnables

  • Titre : quoi a cassé et où (route, fournisseur)
  • Symptômes : détails taux d'erreur ou latence
  • Liens : tableau de bord route, déploiements récents, procédure

Checkliste de garde

  • Acquitter l'alerte, assigner un unique responsable
  • Vérifier le tableau de bord d'accueil et la route affectée
  • Confirmer l'impact utilisateur. Si sévère, rollback du changement récent
  • Si fournisseur externe en échec, dégrader gracieusement ou mettre en file
  • Ajouter request_id d'une ligne de log en échec pour tracer les avals
  • Après correction, vérifier métriques, puis écrire résumé court et prochaines étapes

Plan Pilote Local

Objectif

  • Valider métriques, logs, et quelques formules d'alerte localement avec périmètre minimal

Périmètre

  • Deux routes : un GET rapide et un POST qui touche MongoDB
  • Un appel wrapper Stripe et un wrapper OpenAI (peuvent être simulés)
  • Collecter métriques par défaut et RED, exposer /metrics

Étapes

  1. Instrumenter
  • Ajouter le middleware Express, timing MongoDB, et wrappers tiers
  1. Simuler du trafic
# Route rapide
hey -z 30s -c 10 http://localhost:3000/healthz
# Route DB
hey -z 30s -c 5 http://localhost:3000/api/v1/items/123
  1. Inspecter les métriques
curl -s localhost:3000/metrics | grep http_server_request_duration_seconds
  1. Provoquer erreurs et 429s
  • Pointer la clé Stripe vers une clé test et induire erreurs de validation
  • Pour OpenAI, simuler 429 en faisant échouer temporairement les appels en code quand un flag header est présent
  1. Valider champs de log
  • S'assurer que request_id, route, status, duration_ms, champs dépendance apparaissent
  1. Rédiger expressions d'alerte
  • Coller exemples PromQL dans votre outil d'alerte et vérifier que les séries existent

Critères de succès

  • Vous voyez latence p95 et taux 5xx par route
  • Vous tracez une requête en échec à travers app, MongoDB, et logs fournisseur via request_id
  • Une alerte exemple aurait tiré sous une défaillance induite

Prochaines étapes

  • Déployer le même pattern sur une route à plus fort trafic, puis sur d'autres services

Conclusion

Surveillez ce que les utilisateurs ressentent et ce dont les systèmes ont besoin. Commencez petit avec un pilote local, branchez les IDs de corrélation, émettez les métriques RED, ajoutez une poignée d'alertes solides, et construisez des tableaux de bord qui répondent à de vraies questions. Pratiquez la réponse aux incidents avec procédures légères et propriété claire. Une fois le pilote prouvé, appliquez les mêmes patterns à l'ensemble de vos services d'API REST.

Recherches connexes

Score de qualité de l’article

Utilité pour le lecteur 100%
  • check_circle Guide prêt à lire
  • check_circle Exemples pratiques inclus
  • check_circle URL d’article optimisée pour le SEO