Leçon 3 / 20

Structurer les journaux de l’API

25 min Node.js — production, files d’attente et sécurité Gratuit

Objectif : Émettre des journaux JSON exploitables pour suivre une requête et diagnostiquer une erreur.

Une erreur de commande est difficile à diagnostiquer si le journal contient seulement « une erreur est survenue ». En production, vous devez pouvoir retrouver les événements liés à une même requête et distinguer une information normale d’un incident à examiner.

Vous allez ajouter un module de journalisation au service de commandes. Il écrira des événements JSON sur la sortie standard, associera un identifiant aux requêtes et enregistrera leur durée ainsi que leur statut HTTP. La cartographie du chemin critique et l’injection de dépendances ont été abordées dans les deux leçons précédentes.

Choisir des événements exploitables

Un journal structuré est un événement constitué de champs nommés, sérialisé ici en JSON. Plutôt qu’une phrase libre, chaque ligne expose par exemple un niveau, un nom d’événement, une date et un identifiant de requête. Un outil de collecte peut ainsi filtrer les erreurs ou regrouper les lignes par identifiant sans devoir interpréter du texte rédigé à la main.

Les niveaux servent à exprimer la gravité : info décrit un événement attendu, warn signale une situation anormale qui ne bloque pas nécessairement le service, et error indique un échec à examiner. Le module ci-dessous produit des lignes JSON, ajoute l’horodatage et accepte un puits d’écriture injecté ; par défaut, il écrit sur la sortie standard.

src/observabilite/journalisation.jsJavaScript
const { randomUUID } = require('node:crypto');

const NIVEAUX = new Set(['info', 'warn', 'error']);
const IDENTIFIANT_VALIDE = /^[A-Za-z0-9._-]{1,100}$/;

function creerJournalisation({ sortie = process.stdout } = {}) {
  function ecrire(niveau, evenement, champs = {}) {
    if (!NIVEAUX.has(niveau)) {
      throw new TypeError(`Niveau de journalisation invalide : ${niveau}`);
    }

    const entree = {
      horodatage: new Date().toISOString(),
      niveau,
      evenement,
      ...champs
    };
    sortie.write(`${JSON.stringify(entree)}\n`);
  }

  function middlewareRequete(req, res, next) {
    const candidat = req.get('x-request-id');
    const identifiantRequete = candidat && IDENTIFIANT_VALIDE.test(candidat)
      ? candidat
      : randomUUID();
    const debut = process.hrtime.bigint();

    req.identifiantRequete = identifiantRequete;
    res.set('x-request-id', identifiantRequete);

    res.on('finish', () => {
      const dureeMs = Number(process.hrtime.bigint() - debut) / 1e6;
      ecrire(res.statusCode >= 500 ? 'error' : 'info', 'requete_terminee', {
        identifiantRequete,
        methode: req.method,
        chemin: req.path,
        statutHttp: res.statusCode,
        dureeMs: Math.round(dureeMs * 100) / 100
      });
    });

    next();
  }

  function journaliserErreur(err, req, res, next) {
    ecrire('error', 'requete_en_erreur', {
      identifiantRequete: req.identifiantRequete,
      methode: req.method,
      chemin: req.path,
      erreur: {
        nom: err.name,
        message: err.message
      }
    });
    next(err);
  }

  return {
    ecrire,
    middlewareRequete,
    journaliserErreur
  };
}

module.exports = { creerJournalisation };
Aperçu du rendu
Chargement de l'aperçu…

Le middleware valide l’en-tête x-request-id reçu, puis le remplace par un UUID s’il est absent ou non conforme. La réponse renvoie l’identifiant retenu, ce qui permet de le communiquer lors d’un diagnostic. La durée est mesurée avec une horloge monotone ; elle reste donc adaptée au calcul d’un temps écoulé, contrairement à une soustraction d’horodatages système.

Brancher le module dans Express

Dans la création de votre application Express, instanciez le module une seule fois et placez middlewareRequete avant les routes. Enregistrez ensuite journaliserErreur comme middleware d’erreur, après les routes et leurs gestionnaires. Cette fonction journalise l’erreur puis la transmet avec next(err) au gestionnaire chargé de construire la réponse HTTP ; elle ne remplace pas ce gestionnaire.

Comme la configuration chargée au démarrage est déjà transmise à la création de l’application, fournissez la journalisation de la même manière : comme une dépendance, plutôt que de faire lire des variables d’environnement à ce module. Vous pourrez injecter une autre sortie, par exemple un tampon dans un test, sans modifier le comportement des routes. Évitez d’écrire deux fois le même événement d’erreur : choisissez un point de journalisation clair pour chaque échec.

Interpréter les lignes produites

Après une réponse terminée, une ligne peut ressembler à l’exemple ci-dessous. Un statut serveur déclenche le niveau error pour l’événement de fin de requête ; les réponses non serveur restent au niveau info. L’identifiant permet de relier cette ligne à l’événement d’erreur si une exception a également été transmise au middleware de journalisation.

Dans les étapes suivantes, les journaux fourniront une base pour examiner les événements du service. Les contrôles de santé et les métriques seront abordés dans la prochaine leçon : un journal décrit un événement individuel, tandis qu’une métrique permet de suivre une évolution agrégée.

À retenir

  • Écrivez des événements JSON avec des champs stables plutôt que des messages libres.
  • Utilisez les niveaux info, warn et error pour rendre la gravité filtrable.
  • Associez un identifiant de corrélation aux requêtes et renvoyez-le dans la réponse.
  • Journalisez le statut et la durée, sans consigner de secrets ni de données inutiles.
  • Injectez le module de journalisation dans l’application pour pouvoir remplacer sa sortie en test.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.