Observabilité et incidents
Comprendre ce qui se passe en production, être prévenu au bon moment, et apprendre de chaque panne.
Tout finit par tomber en panne : un partenaire ralentit, une requête SQL s'emballe, un déploiement casse un parcours. La question n'est pas de l'éviter à tout prix, mais de le voir vite, de comprendre où, de réparer calmement, puis de faire en sorte que ça ne recommence pas. La fiche 13 a posé les bases : des logs structurés, quatre indicateurs, une route de santé. Cette fiche va plus loin, et vaut pour n'importe quel langage.
Aucun exemple de cette fiche n'a été exécuté : ce sont des illustrations dans le format réel des outils (JSON, format Prometheus, PromQL, YAML, Markdown), relues d'après leur documentation.
1.Surveiller ou observer
La surveillance (monitoring) répond à des questions qu'on a prévues : le serveur répond-il ? le disque est-il plein ? Elle prévient quand quelque chose de connu se produit.
L'observabilité permet de répondre à des questions qu'on n'avait pas prévues, sans modifier le code ni redéployer : « pourquoi les commandes des clients belges sont-elles lentes depuis ce matin, et seulement avec ce transporteur ? ». Elle repose sur des données riches, reliées entre elles, et sur des outils pour les interroger.
Les deux se complètent : la surveillance te dit que quelque chose ne va pas, l'observabilité t'aide à comprendre pourquoi.
2.Les trois signaux
Une application observable émet trois sortes de données, que l'on appelle souvent les trois signaux, que montre le schéma 21.1 :
- Les logs : un événement précis, avec ses détails. Parfaits pour comprendre un cas, mais coûteux à stocker et à compter en masse.
- Les métriques : des nombres agrégés dans le temps. Peu coûteuses, idéales pour les tableaux de bord et les alertes, mais sans détail sur une requête particulière.
- Les traces : le chemin d'une requête à travers les composants, avec le temps passé dans chacun (section 5).
Leur force vient de leur corrélation. Une alerte sur une métrique mène à une trace lente, qui mène aux logs de cette requête précise. Pour ça, chaque ligne de log porte l'identifiant de la trace :
{
"timestamp": "2026-11-04T14:32:07.412Z",
"level": "error",
"message": "Carrier API call failed",
"service": "boutique",
"version": "3f9c2a1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"context": {
"order_id": 1042,
"carrier": "colis-express",
"http_status": 503,
"duration_ms": 2504,
"attempt": 3
}
}Le message est stable (fiche 13), les détails sont dans le contexte, et version donne le commit déployé : on voit immédiatement si une erreur est apparue avec la dernière version (fiche 18).
3.Les métriques
Une métrique a un nom, des étiquettes (labels) qui la découpent, et une valeur. Trois types couvrent presque tous les besoins :
- Le compteur (counter) ne fait que monter : le nombre de requêtes, d'erreurs, de commandes. On regarde son rythme d'augmentation.
- La jauge (gauge) monte et descend : la mémoire utilisée, les messages en attente dans une file.
- L'histogramme range les valeurs dans des tranches : combien de requêtes ont pris moins de 100 ms, moins de 250 ms, etc. C'est lui qui permet de calculer les percentiles (section 4).
Voici ces trois types au format de Prometheus, l'outil open source de référence. L'application expose ces lignes sur une route, et Prometheus vient les lire régulièrement :
# HELP http_requests_total Requêtes HTTP traitées depuis le démarrage.
# TYPE http_requests_total counter
http_requests_total{route="product_show",status="200"} 184210
http_requests_total{route="order_place",status="201"} 1724
http_requests_total{route="order_place",status="500"} 37
# HELP http_request_duration_seconds Durée des requêtes HTTP.
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{route="order_place",le="0.1"} 812
http_request_duration_seconds_bucket{route="order_place",le="0.25"} 1490
http_request_duration_seconds_bucket{route="order_place",le="0.5"} 1702
http_request_duration_seconds_bucket{route="order_place",le="1"} 1750
http_request_duration_seconds_bucket{route="order_place",le="+Inf"} 1761
http_request_duration_seconds_sum{route="order_place"} 402.7
http_request_duration_seconds_count{route="order_place"} 1761
# HELP messenger_queue_messages Messages en attente dans la file.
# TYPE messenger_queue_messages gauge
messenger_queue_messages{transport="async"} 12On les interroge avec le langage PromQL, puis on les affiche dans des tableaux de bord, avec Grafana par exemple :
# Requêtes par seconde sur le tunnel de commande, moyennées sur 5 minutes
sum(rate(http_requests_total{route="order_place"}[5m]))
# Part des requêtes en erreur (5xx) sur le tunnel de commande
sum(rate(http_requests_total{route="order_place", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{route="order_place"}[5m]))
# Le temps sous lequel répondent 95 % des requêtes (le p95), calculé depuis l'histogramme
histogram_quantile(0.95,
sum by (le) (rate(http_request_duration_seconds_bucket{route="order_place"}[5m])))Chaque combinaison de valeurs d'étiquettes crée une série à stocker. Une étiquette route a quelques dizaines de valeurs : parfait. Une étiquette user_id ou order_id en a des millions, et fait exploser la mémoire de l'outil et la facture. Ce qui est unique par requête va dans les logs et les traces, jamais dans les étiquettes d'une métrique.
4.La moyenne ment
« Le temps de réponse moyen est de 182 ms » : ça semble parfait. Mais une moyenne mélange les requêtes rapides et les lentes, et ce sont les lentes qui font partir les clients.
Un percentile répond à une question plus utile. Le p95 est le temps sous lequel répondent 95 % des requêtes : 5 % sont plus lentes. Le p50, ou médiane, coupe les requêtes en deux moitiés. Surveille le p95 ou le p99 de tes parcours importants : un client qui charge vingt pages en une visite a de grandes chances de tomber au moins une fois sur le p95.
5.Les traces et OpenTelemetry
Une trace suit une requête de bout en bout. Elle est faite de spans : une opération, avec son début, sa durée et son parent. Le contrôleur, chaque requête SQL, chaque appel HTTP est un span. Le résultat se lit comme un planning, comme sur le schéma 21.3.
- La trace traverse les services : quand la boutique appelle une autre API, elle transmet l'identifiant de la trace dans l'en-tête HTTP
traceparent, défini par une norme du W3C. L'autre service ajoute ses propres spans à la même trace. - On n'enregistre pas tout : garder la trace de chaque requête coûte cher. On en conserve un échantillon (sampling), et toutes celles qui ont échoué ou ont été lentes.
Pour produire logs, métriques et traces, la norme est aujourd'hui OpenTelemetry, un projet de la CNCF soutenu par tous les grands outils. Il fournit des bibliothèques pour chaque langage, dont un SDK PHP officiel avec une instrumentation automatique pour Symfony, et un collecteur qui reçoit les données et les transmet :
# Le collecteur OpenTelemetry : il reçoit logs, métriques et traces,
# et les envoie à l'outil choisi
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch: {} # regrouper les envois plutôt qu'un appel par événement
exporters:
otlphttp:
endpoint: https://observability.example.com # changer d'outil, c'est changer cette ligne
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]L'intérêt est de ne pas lier le code à un outil : l'application parle OpenTelemetry, et le collecteur envoie vers Grafana, Datadog, Elastic ou un autre. Changer d'outil, c'est changer une ligne de sa configuration.
- Les traces enregistrent parfois les requêtes SQL avec leurs paramètres, ou les en-têtes HTTP, dont
Authorizationet les cookies. Configure l'instrumentation pour les masquer, comme pour les logs (fiche 13). - La route des métriques ne doit pas être publique : elle révèle tes routes, ton trafic et tes erreurs.
- L'accès aux outils d'observabilité donne beaucoup d'informations : réserve-le, et garde une trace de qui consulte quoi.
- Les logs sont aussi la mémoire d'un incident de sécurité. Une fuite de données personnelles doit être notifiée à la CNIL dans les 72 heures, et ce sont eux qui permettront de dire ce qui a fuité.
6.SLI, SLO et budget d'erreurs
« Le site doit marcher » n'est pas un objectif mesurable. Trois sigles, popularisés par les équipes SRE (Site Reliability Engineering) de Google, le rendent précis :
| Sigle | Ce que c'est | Exemple pour la boutique |
|---|---|---|
| SLI (indicateur) | Une mesure de ce que vit l'utilisateur | La part des commandes qui réussissent en moins de 2 secondes |
| SLO (objectif) | La valeur visée pour ce SLI, sur une période | 99,9 % sur 30 jours glissants |
| SLA (engagement) | Une promesse contractuelle, avec des pénalités | 99,5 % promis aux clients professionnels |
Le SLA est plus bas que le SLO : on vise mieux que ce qu'on promet, pour garder une marge. Et un SLO n'est jamais de 100 % : c'est impossible, et chaque 9 coûte cher (fiche 19). Ce qui manque pour atteindre 100 % s'appelle le budget d'erreurs :
| SLO sur 30 jours | Budget d'erreurs, en temps d'arrêt | Pour un million de commandes |
|---|---|---|
| 99 % | 7 h 12 min | 10 000 échecs |
| 99,5 % | 3 h 36 min | 5 000 échecs |
| 99,9 % | 43 min | 1 000 échecs |
| 99,99 % | 4 min 19 s | 100 échecs |
Le budget transforme un débat d'opinions en règle partagée entre le produit et la technique. Tant qu'il en reste, l'équipe livre des fonctionnalités et prend des risques raisonnables. Quand il est épuisé, elle se consacre à la fiabilité jusqu'à la fin de la période. Cette règle doit être décidée à l'avance, avec le métier.
Pars de ce que vit l'utilisateur, pas de ce qui est facile à mesurer : « la commande a réussi » plutôt que « le CPU est sous 80 % ». Commence par deux ou trois parcours qui comptent (voir un produit, payer, se connecter). Un SLO sur chaque route, personne ne le regarde.
7.Des alertes utiles
La fiche 13 l'a dit : une alerte doit demander une action. Voici comment s'y tenir.
- Alerte sur les symptômes, pas sur les causes. « Les commandes échouent » réveille quelqu'un. « Le CPU est à 90 % » non : peut-être que tout va bien pour les clients. Les causes servent à l'enquête, sur les tableaux de bord.
- Alerte sur le budget d'erreurs. Plutôt qu'un seuil fixe, mesure à quelle vitesse le budget fond. Une consommation 14,4 fois trop rapide épuise 2 % du budget du mois en une heure : il faut agir maintenant. Une fuite lente peut attendre le lendemain.
- Deux niveaux :
page, qui appelle la personne d'astreinte, etticket, qui crée une tâche pour les heures ouvrées. - Un lien vers une procédure (runbook) dans chaque alerte : quoi regarder en premier, comment revenir en arrière. À 3 h du matin, personne ne réfléchit bien.
# Règles d'alerte Prometheus, pour un SLO de 99,9 % de commandes réussies sur 30 jours
groups:
- name: boutique-slo
rules:
# Le budget part 14,4 fois trop vite : 2 % du budget du mois en une heure.
# Les deux fenêtres évitent d'alerter pour un pic de quelques secondes.
- alert: CheckoutErrorBudgetFastBurn
expr: |
(
sum(rate(http_requests_total{route="order_place", status=~"5.."}[1h]))
/ sum(rate(http_requests_total{route="order_place"}[1h]))
) > (14.4 * 0.001)
and
(
sum(rate(http_requests_total{route="order_place", status=~"5.."}[5m]))
/ sum(rate(http_requests_total{route="order_place"}[5m]))
) > (14.4 * 0.001)
labels:
severity: page # réveille la personne d'astreinte
annotations:
summary: Les commandes échouent, le budget d'erreurs fond.
runbook_url: https://wiki.example.com/runbooks/checkout-errors
# Une fuite lente : 5 % du budget en six heures. Pas d'urgence, un ticket suffit.
- alert: CheckoutErrorBudgetSlowBurn
expr: |
(
sum(rate(http_requests_total{route="order_place", status=~"5.."}[6h]))
/ sum(rate(http_requests_total{route="order_place"}[6h]))
) > (6 * 0.001)
labels:
severity: ticket
annotations:
summary: Le taux d'erreur des commandes reste trop haut depuis des heures.Une alerte qui sonne pour rien apprend à l'équipe à ignorer les alertes. Chaque fois qu'une alerte se déclenche sans demander d'action, corrige-la ou supprime-la. Et si quelqu'un est d'astreinte, l'astreinte doit être organisée : un tour de rôle, une compensation, et jamais une seule personne toutes les nuits.
8.Gérer un incident
Un incident, c'est une dégradation que vivent les utilisateurs : une panne, une lenteur forte, des données fausses. Le pire moment pour inventer une méthode, c'est pendant. On la décide donc avant.
- Qualifier la sévérité, sur une échelle connue de tous : par exemple SEV1 pour tout le site arrêté, SEV2 pour un parcours important cassé, SEV3 pour une gêne limitée. La sévérité décide de qui est appelé, et à quelle heure.
- Un canal unique (#incident) où tout s'écrit, avec l'heure : c'est la chronologie du post-mortem.
- Atténuer avant de comprendre : revenir à la version précédente (fiche 13) ou couper un interrupteur (fiche 18), même sans connaître la cause. On enquête une fois les clients tirés d'affaire.
- Communiquer régulièrement, même pour dire « on cherche encore » : une page de statut pour les clients, un message au support toutes les 30 minutes.
- Clôturer explicitement, quand les indicateurs sont revenus à la normale, et fixer la date du post-mortem.
9.Le post-mortem sans coupable
Après un incident, l'équipe écrit un post-mortem : ce qui s'est passé, pourquoi, et ce qu'on change. Il est sans coupable (blameless) : on part du principe que chacun a agi au mieux avec ce qu'il savait à ce moment-là. Ce n'est pas de la gentillesse, c'est de l'efficacité : si une erreur vaut une sanction, les gens cachent leurs erreurs, et l'équipe n'apprend plus rien.
# Post-mortem : commandes en échec le 4 novembre 2026
**Statut** : actions en cours · **Sévérité** : SEV2 · **Rédaction** : l'équipe boutique
## Résumé
De 14 h 05 à 14 h 52, 31 % des commandes ont échoué avec une erreur 500.
Les clients voyaient « une erreur est survenue » au moment de payer.
## Impact
- 47 minutes de dégradation, 212 commandes refusées, dont 143 repassées ensuite.
- Budget d'erreurs du mois consommé à 68 % en une seule fois.
## Chronologie (heure de Paris)
- 14 h 02 : déploiement de la version 3f9c2a1.
- 14 h 05 : premières erreurs 500 sur la route de commande.
- 14 h 09 : l'alerte de consommation rapide du budget se déclenche.
- 14 h 24 : l'astreinte voit l'alerte, prend l'incident et ouvre le canal #incident.
- 14 h 41 : les traces montrent un délai de 2,5 s sur chaque appel au transporteur.
- 14 h 48 : retour à la version précédente.
- 14 h 52 : le taux d'erreur revient à la normale.
## Causes
1. La version 3f9c2a1 appelle le transporteur pendant la requête, et non dans un message.
2. Le transporteur répondait lentement ce jour-là ; le délai de 2,5 s, ajouté aux nouveaux
essais, dépassait la limite de 10 s de la requête.
3. Les tests ne simulaient pas un partenaire lent.
## Ce qui a bien marché
- L'alerte sur le budget d'erreurs a détecté l'incident en 4 minutes, sans attendre un client.
- Le retour à la version précédente a pris 4 minutes.
## Ce qui a ralenti
- 15 minutes entre l'alerte et sa prise en charge : elle n'arrivait que par e-mail.
- Les traces n'étaient pas reliées aux logs : 17 minutes pour trouver l'appel lent.
## Actions
| Action | Responsable | Échéance |
|---|---|---|
| Passer l'appel au transporteur dans un message Messenger | Équipe boutique | 11/11 |
| Ajouter `trace_id` dans chaque ligne de log | Équipe plateforme | 18/11 |
| Envoyer les alertes `page` par appel téléphonique | Équipe plateforme | 08/11 |
| Un test qui simule un transporteur lent | Équipe boutique | 11/11 |- Des faits, datés, tirés du canal d'incident et des outils, pas des souvenirs.
- Plusieurs causes, presque toujours : un changement, un contexte, une protection absente. « Julien n'a pas testé » n'est jamais une cause ; « rien ne testait un partenaire lent » en est une.
- Ce qui a bien marché, aussi : c'est ce qu'il faut garder.
- Des actions avec un responsable et une date. Un post-mortem sans action suivie n'a servi à rien.
- Partagé largement : les autres équipes ont souvent les mêmes failles.
Pièges fréquents
| Piège | Ce qui arrive | La parade |
|---|---|---|
| Apprendre la panne par un client | Des dizaines de minutes perdues, et la confiance avec | Une alerte sur ce que vivent les utilisateurs |
| Des logs sans identifiant de trace | Impossible de relier une erreur à sa requête | trace_id dans chaque ligne |
| Surveiller la moyenne | Les clients lents sont invisibles | Le p95 ou le p99 des parcours importants |
| Un identifiant unique en étiquette de métrique | Des millions de séries, un outil saturé | Ces valeurs dans les logs et les traces |
| Des alertes sur le CPU et la mémoire | Des réveils pour rien, puis plus personne n'écoute | Des alertes sur les symptômes et le budget |
| Un SLO de 100 % | Un objectif impossible, donc ignoré | Un SLO réaliste, et un budget d'erreurs |
| Chercher la cause avant d'atténuer | Les clients attendent pendant l'enquête | Revenir en arrière d'abord |
| Tout le monde répare, personne ne coordonne | Deux actions contradictoires en même temps | Un responsable de l'incident désigné |
| Chercher un coupable | Les erreurs se cachent, les vraies causes restent | Un post-mortem sans coupable |
| Des actions sans responsable ni date | Le même incident revient | Chaque action suivie jusqu'au bout |
Checklist de l'observabilité
- Les logs sont structurés, sans secret, et portent l'identifiant de trace et la version déployée.
- Les parcours importants ont des métriques de trafic, d'erreurs et de durée, en histogramme.
- Les traces montrent le temps passé dans la base et les appels externes.
- Deux ou trois SLO sont définis avec le métier, et leur budget est visible par toute l'équipe.
- Chaque alerte demande une action, et renvoie vers une procédure.
- On sait qui est d'astreinte, et comment l'appeler.
- L'échelle de sévérité, les rôles et le canal d'incident sont décidés à l'avance.
- Chaque incident important donne un post-mortem sans coupable, avec des actions suivies.
Exercice : le post-mortem d'un incident
Voici le canal d'incident de la boutique, un mardi matin, tel qu'il a été écrit sur le moment :
Canal #incident, mardi 12 novembre (heure de Paris)
09:58 [déploiement] version 7d21e04 en production (refonte de la recherche de produits)
10:03 [support] Un client dit que la page d'accueil met « une éternité » à s'afficher
10:11 [support] Trois autres clients, même problème. Personne n'a été alerté ?
10:12 Karim : je regarde. Le CPU de la base est à 98 % depuis 10:00.
10:15 Léa : c'est encore le déploiement de Julien, il aurait dû tester
10:16 Karim : je relance la base, ça devrait passer
10:19 Karim : relancée. Le CPU remonte déjà à 95 %.
10:24 Julien : la recherche fait une requête par catégorie, sans index sur category_id
10:26 Julien : je repasse sur la version précédente
10:31 [déploiement] version 41b8c9a (précédente) en production
10:34 Karim : CPU de la base à 23 %, temps de réponse normal
10:35 Léa : ok c'est réglé, on clôt
Extraits des métriques (p95 de la page d'accueil) :
09:55 180 ms 10:05 4,2 s 10:20 6,8 s 10:35 190 ms
Alertes configurées : espace disque, serveur injoignable.- Reconstitue la chronologie, et calcule deux durées : entre le début du problème et sa détection, puis entre le début et le retour à la normale. Qui a détecté l'incident ?
- Relance de la base à 10:16 : pourquoi n'a-t-elle rien changé ? Quelle action aurait dû venir en premier, et à quelle heure était-elle possible ?
- Repère les messages qui cherchent un coupable, et réécris-les en faits.
- Propose un SLI et un SLO pour la page d'accueil, puis une alerte en PromQL, sur le modèle des sections 3 et 7, qui aurait prévenu l'équipe avant le support.
- Rédige le post-mortem avec le modèle de la section 9 : au moins trois causes et trois actions, chacune avec un responsable et une date.
- Question de recul : « ok c'est réglé, on clôt ». Que manque-t-il avant de clore cet incident ?
Conseils
- Commence par une question réelle. Plutôt que d'instrumenter tout d'un coup, prends le dernier incident et demande-toi quelle donnée t'aurait fait gagner le plus de temps. Ajoute celle-là.
- Regarde tes tableaux de bord quand tout va bien. On ne reconnaît une courbe anormale que si l'on connaît la courbe normale.
- Entraîne-toi aux incidents : simule une panne en recette (un partenaire coupé, une base lente) et déroule la procédure. Le jour réel, les gestes seront connus.
- Lis des post-mortems publics. Beaucoup d'entreprises publient les leurs : c'est une mine de causes inattendues, et un modèle de ton factuel.
Documentation officielle
- OpenTelemetry, dont le SDK PHP et le collecteur (en anglais)
- Prometheus, dont les histogrammes et les règles d'alerte (en anglais)
- La norme W3C Trace Context, l'en-tête
traceparent(en anglais) - Les logs dans Symfony, avec Monolog
- Le livre Site Reliability Engineering de Google, gratuit en ligne, dont la culture du post-mortem et les alertes sur les SLO (en anglais)
- Notifier une violation de données personnelles, à la CNIL