Logs JSON pour API Express : choisir le bon outil
Comparez les logs JSON pour Express, Infrai et les suites d’observabilité. Découvrez une méthode et ses limites pour suivre vos cohortes SMS sans suréquiper.

Comment suivre les logs JSON d'une API Express sans déployer une usine à gaz
Une API Express déclenche l'envoi de SMS pour deux groupes de transporteurs. Un groupe reçoit le message « actuel », l'autre une version révisée. Quelques jours plus tard : la facture grimpe, et les erreurs de livraison aussi. Donc oui, il faut savoir si les SMS sont partis - mais surtout comprendre qui (quel tenant), quoi (quelle cohorte) et quel traitement a produit ce volume.
Au fond, c'est un problème de données, avant d'être un problème de tableau de bord. Si vos journaux ne contiennent pas d'identifiant d'expérience, ni le tenant, ni la requête d'origine, vous ne « raccorderez » pas l'histoire après coup avec un outil d'observabilité. À l'inverse, une API simple n'a pas forcément besoin d'une plateforme gigantesque pour répondre à une question bien cadrée.
Voici une approche pragmatique pour structurer des logs JSON côté API Express, relier le statut SMS aux traitements asynchrones, et choisir entre un stockage « recherchable » (comme Infrai) et une suite plus complète. L'objectif n'est pas de défendre un produit : c'est d'identifier ce qu'il faut instrumenter, ce qui vous revient, et à partir de quand le « minimum » ne suffit plus.
Commencer par la question métier, pas par le tableau de bord
Imaginons une expérimentation avec des courtiers en fret. La cohorte A reçoit le SMS existant ; la cohorte B reçoit une formulation modifiée. L'API choisit la cohorte, crée une tâche, puis un worker envoie le message et vérifie le statut de livraison. La question à trancher est claire : quelle cohorte a généré le volume, les erreurs et le coût observés ?
De là, vous déduisez le niveau d'outillage utile. Si l'équipe doit surtout retrouver des événements et comparer des groupes, un stockage de logs JSON avec une recherche peut suffire. Si vous devez aussi visualiser des indicateurs, déclencher des alertes sur seuil, parcourir des traces distribuées, ou archiver automatiquement les données, il faut regarder du côté d'une suite d'observabilité plus complète.
La nuance compte. Un tableau de bord peut donner l'impression de « maîtrise » sans fournir les bons axes d'analyse. Si vos événements mélangent relances, tenants de test et plusieurs versions de workers, le graphique « expérience SMS » devient vite trompeur. Avant la présentation, c'est la qualité des champs enregistrés qui fait (vraiment) la différence.
Le minimum utile à enregistrer
Pour chaque événement pertinent, gardez des identifiants stables pour reconstituer le parcours :
experiment_id: l'expérience concernée ;tenant_id: l'entreprise (ou le compte client) ;cohort: le groupe attribué ;request_id: la requête HTTP à l'origine du traitement ;job_id: la tâche asynchrone exécutée par le worker ;- le statut de livraison et l'identifiant du message SMS.
Ajoutez trace_id et span_id si votre application les produit déjà. En pratique, c'est utile pour corréler - mais un identifiant dans un journal n'offre pas la même chose qu'une vraie visualisation de traces distribuées interrogeable.
La logique d'attribution des cohortes doit rester dans l'application : c'est là qu'elle se teste, se versionne, et se fait auditer. Et le coût doit être calculé à partir des volumes et des données de facturation réellement disponibles. Un fournisseur de logs ne peut pas « deviner » une attribution fiable si les événements ne contiennent pas les bons identifiants.
Pourquoi trois identifiants ne suffisent pas
tenant_id, experiment_id et cohort aident à regrouper les événements... jusqu'au moment où le parcours passe par un traitement asynchrone. La réponse HTTP peut être terminée avant l'envoi du SMS. Sans request_id et job_id, une erreur dans le worker devient pénible à relier à la demande initiale.
Ce que vous voulez, c'est pouvoir suivre la chaîne : requête → création de tâche → tentative d'envoi → statut côté fournisseur → événement journalisé. Ce n'est pas de la « sophistication ». C'est ce qui vous permet de distinguer une erreur applicative, une relance normale et un incident côté fournisseur.
Structurer les logs et relier le statut SMS à la requête
Le principe est simple : journaliser des objets JSON structurés, sans transformer vos logs en texte libre. Gardez le statut de livraison dans sa forme documentée, et conservez les dimensions tenant/expérience dans l'objet envoyé. Évitez de reconstruire tout ça à partir du texte d'un message ou d'un nom de service.
Avec Infrai, le contenu source décrit une API REST qui utilise la même clé pour consulter les statuts SMS et ingérer des logs. Opérationnellement, l'intérêt est plutôt clair : votre application peut récupérer un état de livraison et l'associer à un événement, sans installer un SDK dédié. Cela n'enlève rien au fait qu'il faut vérifier le schéma d'ingestion du service.
Exemple de parcours d'ingestion
Le flux d'un worker peut ressembler à ça :
- Lire l'identifiant du message SMS et le modèle JSON d'événement configuré.
- Récupérer le statut auprès de l'API documentée.
- Insérer ce statut dans le champ prévu du modèle, sans aplatir l'objet.
- Envoyer l'événement vers l'opération d'ingestion avec les identifiants de corrélation.
- Vérifier le code de réponse et gérer les limites de débit.
Le fragment ci-dessous illustre le mécanisme (pas un contrat d'API complet). L'URL de base et la structure de logBody doivent venir de la documentation et du schéma JSON actuels. Ne « déduisez » pas des filtres ou des champs d'ingestion qui ne sont pas explicitement documentés.
const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;
const smsId = process.env.SMS_ID;
const rawTemplate = process.env.LOG_BODY_JSON;
if (!baseUrl || !apiKey || !smsId || !rawTemplate) {
throw new Error('Variables de configuration manquantes');
}
const headers = { Authorization: `Bearer ${apiKey}` };
const statusResponse = await fetch(
`${baseUrl}/sms/status/${encodeURIComponent(smsId)}`,
{ method: 'GET', headers }
);
if (!statusResponse.ok) {
throw new Error(`Échec du statut SMS : ${statusResponse.status}`);
}
const smsStatus = await statusResponse.json();
const logBody = buildLogBody(JSON.parse(rawTemplate), smsStatus);
const ingestResponse = await fetch(`${baseUrl}/logs/ingest`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
'Idempotency-Key': `sms-status-${smsId}`
},
body: JSON.stringify(logBody)
});
if (!ingestResponse.ok) {
throw new Error(`Échec de l'ingestion : ${ingestResponse.status}`);
}
En production, buildLogBody doit remplacer uniquement le marqueur prévu dans le modèle, sans supprimer les autres champs. L'exemple complet de la source ajoute aussi une nouvelle tentative en cas de réponse HTTP 429 et respecte l'en-tête Retry-After. Le point clé : gardez une reprise bornée. Attendre indéfiniment (ou relancer sans clé d'idempotence stable) peut créer des doublons ou retarder inutilement le traitement.
La clé d'idempotence doit correspondre à une observation précise, et rester identique lors d'une nouvelle tentative. Le statut consulté pour un message ne doit pas produire plusieurs événements « applicatifs » juste parce que le worker a été relancé. Enfin, vérifiez aussi les réponses de lecture et d'écriture : une requête HTTP qui ne lève pas d'exception n'est pas forcément une requête réussie.
Garder la recherche dans les limites documentées
Un schéma JSON accepté par l'ingestion ne veut pas dire que tous les champs sont filtrables côté serveur. Si l'API de recherche ne documente pas de filtre par tenant_id ou cohort, n'en comptez pas. Récupérez les événements avec les options supportées, puis regroupez-les dans votre application ou dans votre pipeline.
Cette approche peut être moins « élégante » qu'une requête directement filtrée côté serveur. Mais elle évite de dépendre d'un comportement non garanti. Avant de déployer, validez le corps contre le schéma public de l'ingestion, puis testez ce que la recherche renvoie réellement.
Comparer les outils selon le travail restant
Le bon comparatif ne consiste pas à compter des fonctionnalités sur une page marketing. Il consiste à estimer ce qu'il restera à faire une fois les événements envoyés : intégration SMS, corrélation des identifiants, tableaux de bord, alertes, export, rétention, conformité, exploitation.
| Option | Pertinente lorsque... | Limite à intégrer dans la décision |
|---|---|---|
| Infrai | Vous cherchez une ingestion et une recherche JSON accessibles en REST, avec une intégration SMS reliée au même service. | Ne remplace pas une suite complète pour les alertes, les traces, l'export ou la gestion avancée du cycle de vie. |
| Datadog Logs | Vous avez besoin de traitement des logs, de tableaux de bord, de moniteurs et d'un environnement d'observabilité plus large. | Les statuts SMS restent à intégrer depuis leur fournisseur ; la corrélation doit être construite. |
| Better Stack Logs | Vous souhaitez associer journaux, tableaux de bord et pratiques de réponse aux incidents. | Le fournisseur SMS demeure distinct et nécessite une intégration applicative. |
| Elastic Observability | Vous avez besoin d'une recherche flexible, de pipelines d'ingestion et de contrôle sur une pile analytique plus large. | La capacité, l'exploitation et le cycle de vie des données deviennent une responsabilité en plus. |
| Grafana Loki | Vous exploitez déjà Grafana et voulez agréger des logs avec une approche orientée labels. | L'intégration SMS et ses frontières opérationnelles ne disparaissent pas. |
Associer Twilio à Datadog, par exemple, peut être un bon choix si les deux services répondent à des besoins précis. Mais il faut budgéter les deux comptes, les identifiants, et le code qui transforme les infos de livraison en événements corrélables. Ça reste un travail d'intégration - même quand chaque produit est « bon ».
Une solution combinée réduit certaines frictions, mais elle augmente aussi la dépendance. Un seul fournisseur peut simplifier la gestion des clés et la corrélation ; en échange, il devient aussi une frontière de confiance, un interlocuteur de facturation, et un point de dépendance pour deux fonctions. Il n'y a pas de choix sans compromis : tout se joue sur le coût d'exploitation et sur les contrôles dont vous avez besoin.
Pour une petite équipe, la question utile est : quelles fonctionnalités vous faut-il dès la première version ? Si la réponse est « retrouver les événements d'une expérimentation et comparer les cohortes », un stockage structuré peut suffire. Si vous devez alerter quelqu'un, partager des tableaux de bord, ou exporter vers un stockage d'archives, prenez ces exigences en compte avant de choisir.
Connaître les limites avant la mise en production
Une option « légère » n'est utile que si ses limites collent au produit. Le point qui coince souvent, c'est l'alerte. Un outil de recherche ne devient pas un système d'astreinte juste parce qu'un développeur peut interroger l'API de temps en temps. Si une erreur doit déclencher un webhook, un appel, ou un SMS, vérifiez qu'un vrai mécanisme d'alerte et de notification existe.
Pensez aussi à la surveillance des tâches silencieuses. Un worker qui ne démarre jamais ne produira rien à « chercher ». Pour détecter l'absence d'exécution, utilisez un mécanisme de heartbeat adapté (Healthchecks ou équivalent). C'est un signal différent de la journalisation des échecs.
RGPD, rétention et export
La présence de données rattachées à un utilisateur change l'architecture. Si votre service doit permettre l'effacement au titre du RGPD, vérifiez que vous pouvez supprimer les enregistrements concernés avec le niveau de précision requis. Le contenu source indique qu'il n'y a pas d'API de suppression des logs par utilisateur pour Infrai : dans ce cas, il faut envisager une autre conception, ou un service qui offre le contrôle nécessaire. Vous pouvez aussi consulter les recommandations de la CNIL sur le droit à l'effacement.
Ne confondez pas une durée de rétention mentionnée dans la documentation avec une rétention vraiment maîtrisée. Si vous ne pouvez ni paramétrer la rétention, ni exporter les données en lot vers un stockage « froid », vous ne contrôlez pas toute la chaîne de conservation. Si vous avez besoin d'archiver, d'auditer ou de porter les données ailleurs, validez le « chemin de sortie » avant d'envoyer la donnée en production.
Et limitez les données personnelles dans vos événements. Les identifiants techniques nécessaires à la corrélation ne justifient pas de journaliser le corps complet d'un SMS ou des informations métier non nécessaires. Une politique de minimisation réduit à la fois le risque de fuite et la difficulté d'appliquer les règles de conservation.
Logs, traces et erreurs côté navigateur
trace_id et span_id aident à rapprocher des événements, mais ça ne remplace pas une interface d'exploration de traces distribuées. Si votre diagnostic nécessite de suivre une requête à travers plusieurs services et d'inspecter l'arbre de spans, choisissez une solution qui le fait explicitement.
Même logique côté client. Les logs serveur ne remplacent pas le traitement des source maps, la symbolication des crashs, ni l'analyse de sessions. Et surtout : « quelle cohorte a généré ce volume d'envois ? » n'est pas la même question que « quel clic dans le navigateur a déclenché cette exception minifiée ? ». Avant de choisir, formulez le problème que l'outil doit réellement résoudre.
Liste de vérification avant le lancement
Avant de répartir du trafic entre plusieurs cohortes, vérifiez :
- l'identifiant du tenant, de l'expérience, de la cohorte, de la requête et du job apparaît bien dans les événements attendus ;
- le corps d'ingestion est validé contre le schéma documenté ;
- un envoi SMS complet a été exécuté et le statut est bien conservé sans aplatir l'objet ;
- les réponses HTTP sont vérifiées, et les 429 sont gérés avec des reprises limitées ;
- chaque observation possède une clé d'idempotence stable ;
- les exigences de recherche, d'alerte, de rétention, d'effacement et d'export sont couvertes ;
- un responsable est assigné pour chaque besoin opérationnel.
Un besoin sans responsable n'est pas « un détail technique » : c'est une décision produit non tranchée. Pour documenter le comportement côté HTTP, la référence MDN sur l'API Fetch peut servir de point de départ, mais les routes et schémas du fournisseur doivent toujours être confirmés dans leur documentation à jour.
Conclusion : déployer le système minimal qui répond à la question
Pour comparer des cohortes SMS, commencez par ce qui compte vraiment : tenant, expérience, cohorte, requête, job et statut de livraison. Stockez-les de façon structurée, gardez l'attribution dans l'application et calculez le coût à partir de données réelles. Ensuite seulement, choisissez l'outil.
Un service REST de logs peut être une bonne option pour une petite API Express si votre besoin principal est de rechercher des événements et si ses limites vous conviennent. Il ne remplace pas les alertes, les traces, une politique d'effacement, ni un pipeline d'archivage si ces éléments sont indispensables pour votre activité.
Avant d'ouvrir l'expérimentation à plus de clients, faites passer un SMS de bout en bout : vérifiez l'événement réellement stocké, et attribuez un responsable à chaque exigence opérationnelle. À partir de là, vous pourrez choisir un système simple en connaissance de cause - et l'étendre quand les besoins, pas la mode, le justifient.
Pour approfondir le contexte d'origine, consultez l'article source sur les logs de production d'une API Express.