Retour au cours

infra / nginx

Logs et formats personnalisés

Leçon 111 exercice

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_time et $upstream_response_time pour 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.

FormatFacilité de parsingIntégration outils modernes
combined (texte)Fragile si un champ contient un espaceNé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

nginx
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
}
nginx
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
    }
}
bash
# 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
bash
# 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 off sur /health et les assets statiques évite de saturer les logs pour rien.
  • logrotate + nginx -s reopen gèrent la rotation sans jamais perdre de lignes ni couper le service.
  • $request_time et $upstream_response_time dans les logs permettent de diagnostiquer où passe la latence (Nginx vs backend).

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →