Dans une API Node.js, les logs de requêtes HTTP sont souvent le premier outil qui permet de comprendre un incident, de mesurer une lenteur ou de vérifier qu’un reverse proxy transmet bien les bonnes informations. Le package morgan npm sert précisément à ça: produire des access logs lisibles, simples à brancher dans Express et assez souples pour s’adapter à un backend local, une API de production ou un service derrière un load balancer. Dans cet article, je vais montrer ce qu’il fait vraiment, comment je l’intègre proprement, quels formats choisir et où sont ses limites.
L’essentiel à garder avant de brancher Morgan sur une API
- Morgan journalise les requêtes HTTP, pas les événements métier de l’application.
- Le bon emplacement du middleware change directement la qualité des logs que vous récupérez.
- `combined`, `common`, `dev` et `tiny` n’ont pas le même niveau de détail ni le même usage.
- `skip` et `immediate` servent à réduire le bruit ou à capturer une requête au plus tôt.
- En production, je combine souvent Morgan avec une sortie fichier, une rotation de logs et une politique de rétention claire.

À quoi sert Morgan dans une API Node.js
Je l’utilise quand je veux voir, en une ligne, qui appelle quoi, avec quel statut, et en combien de millisecondes. Morgan est un middleware de journalisation d’accès HTTP: il observe le trafic qui traverse Express et écrit des lignes de log avec des informations comme la méthode, l’URL, le code de réponse, la taille de la réponse, le referer, l’user-agent ou le temps de réponse.
Ce point est important: Morgan n’est pas un logger applicatif complet. Il ne remplace ni les logs métier, ni la télémétrie, ni les traces distribuées. En revanche, pour le diagnostic quotidien d’une API, il donne immédiatement de la visibilité sur les routes lentes, les erreurs 4xx/5xx et les appels inattendus. Autrement dit, il couvre très bien la couche HTTP, et c’est déjà énorme.
- Je m’en sers pour vérifier qu’un endpoint reçoit bien du trafic.
- Je l’active pour repérer rapidement les réponses en erreur.
- Je l’exploite pour relier une plainte utilisateur à une requête concrète.
- Je l’emploie aussi pour suivre les temps de réponse sans ouvrir un outil plus lourd.
Une fois ce rôle clair, la vraie question devient simple: comment le brancher sans rater des requêtes et sans noyer l’équipe sous le bruit inutile ?
Le brancher correctement dans Express
L’intégration de base est volontairement simple. On installe le package, on charge le middleware et on le place avant les routes que l’on veut observer. C’est ce placement qui évite une partie des erreurs classiques: si Morgan arrive trop bas dans la pile, certaines requêtes auront déjà été consommées par d’autres middlewares ou routes, et elles ne remonteront pas comme prévu.
const express = require('express');
const morgan = require('morgan');
const app = express();
app.use(morgan('combined'));
app.get('/health', (req, res) => {
res.json({ ok: true });
});
app.get('/users', (req, res) => {
res.json([]);
});
app.listen(3000, () => {
console.log('API en écoute sur le port 3000');
});Dans une API structurée, je préfère souvent le placer très tôt dans la chaîne, avant les routes, et parfois avant certains middlewares de transformation qui peuvent terminer la requête ou court-circuiter le flux. Si je veux limiter la journalisation à une partie de l’application, je peux aussi le monter sur un router précis plutôt que sur toute l’app. C’est utile quand on veut un comportement différent pour `/api`, `/admin` ou un sous-domaine interne.
Le point à retenir est pragmatique: Morgan doit voir passer la requête au bon endroit. Sinon, il logue moins que prévu, ou pas avec le contexte que vous attendiez. Une fois l’intégration en place, le choix du format devient le levier principal pour rendre ces logs vraiment utiles.
Choisir le bon format de journalisation
Le format n’est pas un détail esthétique. Il détermine la densité des logs, leur lisibilité et leur valeur en production. Pour moi, le bon format dépend surtout de deux choses: le volume de trafic et le niveau d’enquête dont l’équipe a besoin.
| Format | Ce qu’il apporte | Quand je l’utilise | Limite principale |
|---|---|---|---|
combined |
Contexte riche avec IP, date, méthode, URL, statut, taille, referer et user-agent | Production, staging, API qu’on veut diagnostiquer vite | Plus verbeux que les autres |
common |
Version plus légère du log Apache classique | Quand je veux garder l’essentiel en réduisant le bruit | Moins de contexte pour analyser un incident |
dev |
Sortie lisible, colorée, très pratique en local | Développement et débogage manuel | Trop bavard pour une prod sérieuse |
tiny |
Format minimal avec le strict nécessaire | Services à fort trafic ou besoins de logs compacts | Peu d’indices quand il faut enquêter |
| Format personnalisé | Je choisis exactement les champs utiles | Quand j’ai un besoin métier ou une contrainte d’observabilité | Demande un peu plus de maintenance |
Pour les mesures de performance, je fais bien la différence entre :response-time et :total-time. Le premier mesure le temps jusqu’à l’écriture des en-têtes de réponse; le second va jusqu’à la fin réelle de l’écriture sur la connexion. Sur une API qui renvoie des payloads un peu lourds, cet écart peut être intéressant à suivre.
Quand j’ai besoin d’un format plus riche, j’ajoute souvent un jeton personnalisé, par exemple un identifiant de requête ou un PID quand plusieurs processus Node tournent en parallèle. C’est ce genre de détail qui fait gagner du temps au moment d’aligner plusieurs logs sur un même incident.
const express = require('express');
const morgan = require('morgan');
morgan.token('request-id', (req) => req.headers['x-request-id'] || '-');
const app = express();
app.use(morgan(':request-id :method :url :status :response-time ms'));Le bon format n’est donc pas le plus long, mais le plus exploitable. Une fois ce réglage posé, il reste à éviter le bruit superflu sans perdre les signaux utiles.
Réduire le bruit sans perdre l’information utile
Deux options changent vraiment la façon dont je travaille avec Morgan: `skip` et `immediate`. La première sert à exclure des requêtes qui n’apportent rien à l’analyse, comme `/health`, `/metrics` ou certains assets techniques. La seconde enregistre la requête dès son arrivée, avant la réponse, ce qui peut sauver une ligne si le serveur plante en cours de traitement.
const express = require('express');
const fs = require('fs');
const path = require('path');
const morgan = require('morgan');
const app = express();
const accessLogStream = fs.createWriteStream(
path.join(process.cwd(), 'access.log'),
{ flags: 'a' }
);
app.use(morgan('combined', { stream: accessLogStream }));
app.use(morgan('dev', {
skip: (req, res) => req.originalUrl === '/health' || req.originalUrl === '/metrics'
}));Ce type de montage est utile parce que Morgan peut être utilisé plusieurs fois. Je peux envoyer tous les accès dans un fichier, tout en gardant les erreurs visibles en console. En pratique, c’est un compromis très sain: le flux principal reste exploitable par les outils d’observabilité, et les anomalies ressortent localement pendant le développement ou les tests.
Il y a toutefois un vrai arbitrage avec immediate. Oui, j’obtiens une trace plus tôt, mais je perds les données de réponse, donc le statut, la longueur et les temps de fin ne sont plus disponibles. Je réserve donc ce mode aux cas où le risque de crash ou d’interruption vaut davantage que la finesse du log. Pour tout le reste, le mode standard est plus utile.
Le bruit n’est pas seulement une question de volume. C’est aussi une question de conservation, de rotation et de qualité du signal. Une API bien instrumentée logue peu, mais logue juste.
Les pièges que je surveille avant la mise en production
Le premier piège, c’est l’emplacement du middleware. Morgan ne logue que ce qui passe réellement à travers lui. Si une route ou un autre middleware termine la réponse avant lui, la ligne de log ne sortira pas comme prévu. Sur un projet Express un peu ancien, je vérifie toujours l’ordre des app.use() avant de chercher un problème plus complexe.
Le deuxième piège, c’est le contexte réseau. Quand l’application est derrière un reverse proxy ou un load balancer, l’adresse client affichée par :remote-addr peut être celle du proxy au lieu du navigateur ou du client réel. Dans ce cas, je configure Express avec trust proxy avec précaution, parce que cette option ne doit jamais être activée à l’aveugle. Il faut qu’elle corresponde exactement à l’architecture réseau, sinon on ouvre la porte à des valeurs de confiance mal interprétées.
Le troisième piège, c’est la fuite d’information. Une URL journalisée peut contenir un token dans une chaîne de requête, et un token personnalisé peut exposer un identifiant sensible si je ne le filtre pas. Dans les APIs exposées au public, je fais donc attention aux paramètres, aux en-têtes récupérés dans des jetons custom et aux données personnelles qui peuvent se retrouver dans les logs sans que l’équipe s’en rende compte.
- Je limite ce que j’ajoute dans les formats personnalisés.
- Je contrôle les endpoints de santé pour éviter les milliers de lignes inutiles.
- Je m’assure que la rétention des logs est cohérente avec l’usage réel.
- Je vérifie que les accès aux fichiers ou au collecteur central sont bien restreints.
Enfin, je garde un œil sur la version installée. La branche récente du package a ajouté le jeton :pid et a corrigé un point de sécurité autour de :remote-user, ce qui rappelle une règle simple: un logger d’accès n’est pas un composant neutre, il mérite les mêmes réflexes de mise à jour que le reste de la pile backend.
Le réglage minimal que je recommande pour une API sérieuse
Si je devais résumer ma configuration de base pour une API Node.js, je partirais sur une approche très sobre: un format lisible en production, un format plus bavard en local, une sortie adaptée au flux réel et quelques exclusions ciblées. Rien de spectaculaire, mais c’est ce qui tient dans la durée.
-
En local, je garde
devpour lire vite ce qui se passe. -
En production, je pars souvent sur
combinedou sur un format personnalisé plus strict. -
Pour les endpoints de santé, je coupe le bruit avec
skip. -
Si l’app est derrière un proxy, je valide la stratégie
trust proxyavant de me fier à l’IP loguée. - Si le trafic augmente, je privilégie une sortie fichier ou un collecteur central avec rotation.
- Si l’équipe a besoin de corrélation, j’ajoute un identifiant de requête dans un jeton dédié.
Ce que j’aime avec Morgan, c’est sa franchise technique: il fait peu de choses, mais il les fait proprement. Pour une API backend, c’est souvent exactement ce qu’il faut pour diagnostiquer vite sans transformer la journalisation en projet annexe.