infra / nginx
Logs et formats personnalisés
Explication
Ce que vous allez apprendre
- Définir un format de log personnalisé, notamment en JSON
- Comprendre pourquoi un format JSON facilite énormément l'analyse en aval
- Séparer les logs par site plutôt que de tout mélanger dans un seul fichier global
- Désactiver le logging là où il n'apporte rien (health checks, assets statiques)
- Utiliser
$request_timeet$upstream_response_timepour localiser une latence
Dans quel contexte ?
Une équipe SRE doit diagnostiquer pourquoi certaines requêtes d'une API mettent parfois plus de deux secondes à répondre, alors que la majorité répondent en moins de 100 millisecondes. Sans logs exploitables, cette investigation reste une devinette ; avec un format de log riche et structuré, la réponse peut apparaître en quelques minutes d'analyse.
D'abord, le format par défaut a ses limites
Le format combined, standard historique hérité d'Apache, reste un format texte avec un ordre de champs fixe. Il fonctionne, mais devient pénible à parser fiablement dès qu'un champ contient un espace ou un caractère spécial, et il ne s'intègre pas naturellement aux outils de log modernes (ELK, Loki, Grafana).
Une fois cette limite identifiée, le format JSON s'impose naturellement
Un log_format json_combined escape=json '{...}' structure chaque ligne de log comme un objet JSON valide, avec des clés nommées explicitement (status, request_time, upstream_response_time...). N'importe quel outil de log centralisé peut alors parser ces lignes sans configuration fragile basée sur des positions de colonnes.
| Format | Facilité de parsing | Intégration outils modernes |
|---|---|---|
combined (texte) | Fragile si un champ contient un espace | Nécessite souvent une regex de parsing |
JSON (escape=json) | Direct, chaque champ est nommé | Native pour ELK, Loki, la plupart des SIEM |
Ensuite, une bonne pratique d'organisation évite de mélanger l'ingérable
Séparer les logs par site (access_log /var/log/nginx/example.com.access.log) plutôt que d'utiliser un seul fichier global rend chaque investigation plus rapide : pas besoin de filtrer des milliers de lignes d'autres sites pour retrouver l'information pertinente.
Il reste un réflexe simple mais souvent oublié : ne pas tout logger
Un health check appelé toutes les cinq secondes par un load balancer externe, ou le chargement de centaines d'assets statiques par page, gonflent inutilement les fichiers de log sans apporter d'information utile en cas d'incident. access_log off; sur ces location précises garde les logs concentrés sur ce qui compte réellement.
Prérequis
Cette leçon suppose que tu es à l'aise avec les location (leçon 3) : access_log off s'applique typiquement à des location ciblées, pas au serveur entier.
Enfin, deux champs particulièrement précieux pour le diagnostic de performance
$request_time mesure le temps total pris par Nginx pour traiter la requête, $upstream_response_time isole la part de ce temps passée à attendre la réponse du backend. Comparer les deux permet de savoir immédiatement si une lenteur vient de Nginx lui-même (rare) ou de l'application derrière (le cas le plus fréquent).
Bonne pratique
Combine un format JSON avec $request_time et $upstream_response_time dès la mise en production d'un nouveau service : ces deux champs, absents du format par défaut, sont systématiquement ceux qu'on regrette de ne pas avoir le jour d'un incident de performance.
Piège fréquent
Oublier de configurer la rotation des logs (logrotate) sur un serveur à fort trafic peut remplir le disque en quelques semaines, provoquant une panne totale du serveur bien après la mise en production initiale, une fois que personne ne surveille plus activement cette machine.
Maintenant que le diagnostic par les logs est en place, la prochaine leçon aborde un sujet technique différent : les redirections et réécritures d'URL, avec leurs subtilités souvent mal comprises (rewrite vs return, last vs break).
Commandes & code
Logs et formats personnalisés
http {
# Format par défaut "combined" — déjà défini nativement, rappelé ici pour référence
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
# Format JSON : bien plus exploitable par un système de logs centralisé (leçon dédiée plus loin)
log_format json_combined escape=json '{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/access.log json_combined;
error_log /var/log/nginx/error.log warn; # niveaux : debug, info, notice, warn, error, crit
}server {
server_name example.com;
# Logs par site (plus simple à analyser qu'un log global mélangé)
access_log /var/log/nginx/example.com.access.log json_combined;
error_log /var/log/nginx/example.com.error.log warn;
location /health {
access_log off; # évite de polluer les logs avec des checks de santé toutes les 5 secondes
}
location ~* \.(js|css|png|jpg|svg)$ {
access_log off; # idem pour les assets statiques à fort volume
}
}# Rotation des logs (logrotate, déjà configuré par défaut sur Debian/Ubuntu)
cat /etc/logrotate.d/nginx
# daily, rotate 14 (garde 14 jours), compress, delaycompress, missingok
# postrotate : envoie SIGUSR1 à Nginx pour rouvrir les fichiers de log sans coupure
sudo nginx -s reopen # force la réouverture manuelle des logs après rotation# Analyse rapide en ligne de commande (avant de brancher un vrai outil de log centralisé)
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10 # top 10 IP
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn # répartition des status codes
grep ' 5[0-9][0-9] ' /var/log/nginx/access.log | tail -20 # dernières erreurs 5xx
# Avec jq si log_format json_combined est utilisé
tail -f /var/log/nginx/access.log | jq 'select(.status >= 500)'Résumé
- Un format JSON (
escape=json) est bien plus exploitable en aval (ELK, Loki) qu'un format texte classique. access_log offsur/healthet les assets statiques évite de saturer les logs pour rien.logrotate+nginx -s reopengèrent la rotation sans jamais perdre de lignes ni couper le service.$request_timeet$upstream_response_timedans les logs permettent de diagnostiquer où passe la latence (Nginx vs backend).
Exercices pratiques
Mission : localiser l'origine d'une latence intermittente sur une API
Objectif : Configurer un format de log JSON avec les champs de timing nécessaires pour distinguer une lenteur Nginx d'une lenteur backend, et réduire le bruit des logs.
Contexte
Certaines requêtes de l'API mettent plus de 2 secondes à répondre, la majorité répondant en moins de 100 ms. Le format de log actuel est le combined par défaut, qui ne contient aucune information de timing. Le endpoint /health, appelé toutes les 5 secondes par le load balancer, pollue par ailleurs les logs.