Logging structure avec JSONL
Un guide complet sur le logging structure au format JSONL (JSON Lines). Apprenez a integrer avec ELK Stack, Fluentd, CloudWatch, GCP Logging et Azure Monitor avec des exemples de code prets pour la production.
Derniere mise a jour : fevrier 2026
Pourquoi le logging structure avec JSONL ?
Les journaux en texte brut traditionnels sont lisibles par les humains mais pratiquement impossibles a analyser de maniere fiable par les machines. Une ligne comme '2024-01-15 ERROR User login failed for admin' enfouit la severite, le type d'evenement et le nom d'utilisateur dans une chaine non structuree. Chaque outil d'agregation de journaux doit recourir a des expressions regulieres fragiles pour en extraire le sens, et tout changement dans le format du journal casse le pipeline.
Le logging structure resout ce probleme en emettant chaque entree de journal sous forme d'objet JSON avec des champs bien definis. JSONL (JSON Lines) va encore plus loin : un objet JSON par ligne, pas de tableau englobant, pas de virgules finales. Cela rend les journaux JSONL compatibles avec l'ajout, le streaming et trivialement analysables par toutes les principales plateformes de journaux. Dans ce guide, vous apprendrez a produire des journaux JSONL depuis Python, Node.js et Go, et a les envoyer vers ELK, Fluentd et les trois principaux fournisseurs cloud.
Par rapport aux autres formats structures comme CSV ou XML, JSONL offre le meilleur equilibre entre flexibilite, support outillage et lisibilite humaine. Les champs peuvent etre imbriques, les types sont preserves, et vous n'avez jamais besoin de definir un schema fixe a l'avance. C'est pourquoi JSONL est devenu le standard de facto pour le logging structure dans l'infrastructure moderne.
Bibliotheques de logging par langage
La plupart des bibliotheques de logging modernes supportent la sortie JSON structuree nativement ou via un simple changement de configuration. Voici les bibliotheques recommandees pour Python, Node.js et Go, chacune produisant une sortie compatible JSONL.
Python : structlog
Recommandestructlog est la bibliotheque de logging structure de reference pour Python. Elle encapsule le module logging standard et produit une sortie JSON par defaut. Son pipeline de processeurs vous permet d'enrichir, filtrer et transformer les entrees de journal avant la serialisation.
import structlog# Configurer structlog pour une sortie JSONLstructlog.configure(processors=[structlog.processors.TimeStamper(fmt="iso"),structlog.processors.add_log_level,structlog.processors.StackInfoRenderer(),structlog.processors.JSONRenderer(),],wrapper_class=structlog.BoundLogger,context_class=dict,logger_factory=structlog.PrintLoggerFactory(),)log = structlog.get_logger()# Chaque appel produit une ligne JSONLlog.info("user.login", user_id="u-42", ip="10.0.0.1")# {"event":"user.login","user_id":"u-42","ip":"10.0.0.1","level":"info","timestamp":"2026-02-15T08:30:00Z"}log.error("db.connection_failed", host="db-primary", retries=3)# {"event":"db.connection_failed","host":"db-primary","retries":3,"level":"error","timestamp":"2026-02-15T08:30:01Z"}
Node.js : pino
Le plus rapidepino est le logger Node.js le plus rapide, produisant une sortie JSONL par defaut sans aucune configuration. Il atteint une faible surcharge grace a l'ecriture asynchrone et un chemin de serialisation minimal. Le package companion pino-pretty fournit une sortie lisible par l'homme pour le developpement local.
import pino from 'pino';// pino produit du JSONL par defautconst log = pino({level: 'info',timestamp: pino.stdTimeFunctions.isoTime,});// Chaque appel produit une ligne JSONLlog.info({ userId: 'u-42', ip: '10.0.0.1' }, 'user.login');// {"level":30,"time":"2026-02-15T08:30:00.000Z","userId":"u-42","ip":"10.0.0.1","msg":"user.login"}log.error({ host: 'db-primary', retries: 3 }, 'db.connection_failed');// {"level":50,"time":"2026-02-15T08:30:01.000Z","host":"db-primary","retries":3,"msg":"db.connection_failed"}// Les loggers enfants heritent du contexteconst reqLog = log.child({ requestId: 'req-abc-123' });reqLog.info({ path: '/api/users' }, 'request.start');
Go : zerolog
Zero allocationzerolog est un logger JSON a zero allocation pour Go, concu pour une performance maximale dans les services a haut debit. Il ecrit directement dans un io.Writer sans allocations intermediaires, ce qui le rend ideal pour les applications sensibles a la latence. Son API chainee est a la fois ergonomique et efficace.
package mainimport ("os""github.com/rs/zerolog""github.com/rs/zerolog/log")func main() {// Configurer la sortie JSONL vers stdoutzerolog.TimeFieldFormat = zerolog.TimeFormatUnixlog.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()// Chaque appel produit une ligne JSONLlog.Info().Str("user_id", "u-42").Str("ip", "10.0.0.1").Msg("user.login")// {"level":"info","user_id":"u-42","ip":"10.0.0.1","time":1739608200,"message":"user.login"}log.Error().Str("host", "db-primary").Int("retries", 3).Msg("db.connection_failed")}
Integration ELK Stack
L'ELK Stack (Elasticsearch, Logstash, Kibana) est la plateforme de gestion de journaux open source la plus largement deployee. Les journaux JSONL s'integrent parfaitement car Logstash et Filebeat peuvent analyser le JSON nativement, eliminant le besoin de modeles grok complexes.
Filebeat est le collecteur de journaux leger qui surveille vos fichiers de journaux JSONL et les transmet a Logstash ou Elasticsearch. Definir json.keys_under_root a true promeut les champs JSON en champs Elasticsearch de niveau superieur, les rendant directement recherchables dans Kibana.
# filebeat.ymlfilebeat.inputs:- type: logenabled: truepaths:- /var/log/app/*.jsonljson.keys_under_root: truejson.add_error_key: truejson.message_key: messagejson.overwrite_keys: trueoutput.elasticsearch:hosts: ["http://elasticsearch:9200"]index: "app-logs-%{+yyyy.MM.dd}"# Optionnel : envoyer vers Logstash a la place# output.logstash:# hosts: ["logstash:5044"]
Si vous devez transformer ou enrichir les journaux avant l'indexation, Logstash se place entre Filebeat et Elasticsearch. Le codec json analyse automatiquement chaque ligne JSONL. Utilisez le filtre mutate pour renommer les champs, ajouter des tags ou supprimer les donnees sensibles avant le stockage.
# logstash.confinput {beats {port => 5044codec => json}}filter {# Analyser l'horodatage depuis le champ JSONdate {match => ["timestamp", "ISO8601"]target => "@timestamp"}# Ajouter un tag d'environnementmutate {add_field => { "environment" => "production" }remove_field => ["agent", "ecs", "host"]}# Router par niveau de logif [level] == "error" or [level] == "fatal" {mutate { add_tag => ["alert"] }}}output {elasticsearch {hosts => ["http://elasticsearch:9200"]index => "app-logs-%{+YYYY.MM.dd}"}}
Fluentd et Fluent Bit
Fluentd et son petit frere leger Fluent Bit sont des projets diplomes du CNCF largement utilises dans les environnements Kubernetes. Tous deux gerent les journaux JSONL nativement et peuvent les transmettre a pratiquement n'importe quelle destination, d'Elasticsearch a S3 en passant par les services de journaux cloud natifs.
Fluent Bit est le collecteur de journaux prefere pour Kubernetes et les environnements a ressources limitees. Il consomme environ 450 Ko de memoire et peut traiter des centaines de milliers d'enregistrements par seconde. L'analyseur json gere l'entree JSONL sans aucune configuration personnalisee.
[SERVICE]Flush 5Daemon OffLog_Level infoParsers_File parsers.conf[INPUT]Name tailPath /var/log/app/*.jsonlParser jsonTag app.*Refresh_Interval 5[FILTER]Name modifyMatch app.*Add cluster production-us-east-1Add service my-api[OUTPUT]Name esMatch app.*Host elasticsearchPort 9200Index app-logsType _docLogstash_Format On
Fluentd offre un ecosysteme de plugins plus riche compare a Fluent Bit, avec plus de 500 plugins communautaires. Utilisez-le quand vous avez besoin de routage, buffering ou transformations avances. Le plugin in_tail avec format json lit les fichiers JSONL nativement.
# fluentd.conf<source>@type tailpath /var/log/app/*.jsonlpos_file /var/log/fluentd/app.postag app.logs<parse>@type jsontime_key timestamptime_format %Y-%m-%dT%H:%M:%S%z</parse></source><filter app.logs>@type record_transformer<record>hostname "#{Socket.gethostname}"environment production</record></filter><match app.logs>@type elasticsearchhost elasticsearchport 9200index_name app-logslogstash_format true<buffer>@type memoryflush_interval 5schunk_limit_size 5m</buffer></match>
Plateformes de logging cloud
Les trois principaux fournisseurs cloud acceptent les journaux au format JSONL et analysent automatiquement les champs JSON en attributs recherchables et filtrables. Cela elimine le besoin de gerer votre propre infrastructure de journaux tout en vous donnant les memes capacites d'interrogation structuree.
AWS CloudWatch Logs
AWSCloudWatch Logs analyse automatiquement les entrees de journal JSON, rendant chaque champ interrogeable avec CloudWatch Logs Insights. Quand votre application s'execute sur ECS, Lambda ou EC2 avec l'agent CloudWatch, la sortie JSONL est indexee comme donnees structurees sans configuration supplementaire. Utilisez la syntaxe de type SQL de Logs Insights pour interroger des millions d'entrees de journal en secondes.
# Exemples de requetes CloudWatch Logs Insights# Trouver toutes les erreurs de la derniere heurefields @timestamp, level, event, user_id| filter level = "error"| sort @timestamp desc| limit 100# Agreger les comptes d'erreurs par type d'evenementfields event| filter level = "error"| stats count(*) as error_count by event| sort error_count desc# Latence P99 par endpointfields path, duration_ms| filter ispresent(duration_ms)| stats pct(duration_ms, 99) as p99 by path| sort p99 desc
GCP Cloud Logging
GCPGoogle Cloud Logging (anciennement Stackdriver) traite les charges JSON comme des entrees de journal structurees avec un support natif. Quand vous ecrivez du JSONL vers stdout sur Cloud Run, GKE ou Cloud Functions, l'agent de logging analyse chaque ligne en un LogEntry structure. Le champ severity correspond directement aux niveaux de severite de Cloud Logging, et les champs jsonPayload deviennent indexes et recherchables.
# Exemple Python pour Cloud Run / Cloud Functionsimport jsonimport sysdef log(severity: str, message: str, **fields):"""Ecrire une entree de journal JSONL compatible avec GCP Cloud Logging."""entry = {"severity": severity.upper(), # Correspond a la severite Cloud Logging"message": message,**fields,}print(json.dumps(entry), file=sys.stdout, flush=True)# GCP analyse automatiquement ces journaux comme structureslog("INFO", "user.login", user_id="u-42", ip="10.0.0.1")log("ERROR", "db.timeout", host="db-primary", latency_ms=5200)# Requete dans Cloud Logging :# jsonPayload.user_id = "u-42"# severity >= ERROR
Azure Monitor Logs
AzureAzure Monitor ingere les journaux JSONL via l'Azure Monitor Agent ou l'ingestion directe par API. Application Insights analyse automatiquement les traces JSON et les evenements personnalises. KQL (Kusto Query Language) fournit des requetes puissantes sur les donnees de journaux structures avec support pour l'agregation, l'analyse de series temporelles et la detection d'anomalies.
// Requetes KQL pour les journaux JSONL dans Azure Monitor// Trouver les erreurs recentesAppTraces| where SeverityLevel >= 3| extend parsed = parse_json(Message)| project TimeGenerated, parsed.event, parsed.user_id,parsed.error_message| order by TimeGenerated desc| take 100// Taux d'erreur dans le temps (intervalles de 5 minutes)AppTraces| where SeverityLevel >= 3| summarize ErrorCount = count() by bin(TimeGenerated, 5m)| render timechart// Top des evenements d'erreurAppTraces| where SeverityLevel >= 3| extend parsed = parse_json(Message)| summarize Count = count() by tostring(parsed.event)| top 10 by Count
Bonnes pratiques pour le logging JSONL
Suivre des conventions coherentes dans tous vos services rend les journaux JSONL plus faciles a interroger, correler et alerter. Ces bonnes pratiques sont tirees de systemes de production traitant des milliards d'entrees de journal par jour.
Utiliser des niveaux de log coherents
Adoptez une echelle de severite standard et utilisez-la de maniere coherente dans tous les services. La convention la plus courante utilise cinq niveaux : DEBUG pour les diagnostics de developpement, INFO pour les operations normales, WARN pour les problemes recuperables, ERROR pour les defaillances necessitant une attention, et FATAL pour les crashs irrecuperables. Mappez les niveaux numeriques (comme l'echelle 10-60 de pino) a ces noms de chaines au moment de l'ingestion pour une interrogation uniforme.
{"level":"debug","event":"cache.miss","key":"user:42","timestamp":"2026-02-15T08:30:00Z"}{"level":"info","event":"request.completed","method":"GET","path":"/api/users","status":200,"duration_ms":45,"timestamp":"2026-02-15T08:30:01Z"}{"level":"warn","event":"rate_limit.approaching","client_id":"c-99","current":950,"limit":1000,"timestamp":"2026-02-15T08:30:02Z"}{"level":"error","event":"payment.failed","order_id":"ord-123","error":"card_declined","timestamp":"2026-02-15T08:30:03Z"}{"level":"fatal","event":"startup.failed","reason":"missing DATABASE_URL","timestamp":"2026-02-15T08:30:04Z"}
Standardiser les noms de champs
Definissez un schema partage pour les champs communs a travers tous les services. Utilisez le snake_case pour les noms de champs, ISO 8601 pour les horodatages et une denomination coherente pour les identifiants de correlation. Au minimum, chaque entree de journal doit contenir : timestamp, level, event (un nom d'evenement lisible par la machine) et service. Les entrees liees aux requetes doivent inclure request_id et user_id pour le tracage.
// Schema de champs recommande{"timestamp": "2026-02-15T08:30:00.000Z", // ISO 8601, toujours UTC"level": "info", // debug|info|warn|error|fatal"event": "order.created", // nom d'evenement en notation pointee"service": "order-api", // service emetteur"version": "1.4.2", // version du service"request_id": "req-abc-123", // ID de correlation"trace_id": "4bf92f3577b34da6", // ID de trace distribuee"user_id": "u-42", // utilisateur authentifie"duration_ms": 120, // numerique, pas chaine"order_id": "ord-789", // contexte specifique au domaine"items_count": 3 // contexte specifique au domaine}
Considerations de performance
Le logging structure ajoute une surcharge de serialisation a chaque appel de log. Dans les services a haut debit, cela peut devenir significatif. Utilisez le logging asynchrone (pino, zerolog) pour deplacer la serialisation hors du chemin critique. Evitez de loguer de gros objets ou des corps de requete/reponse au niveau INFO. Utilisez l'echantillonnage pour les journaux de debug a haut volume. Ecrivez dans des fichiers locaux et laissez un sidecar (Filebeat, Fluent Bit) gerer l'envoi, plutot que d'envoyer les journaux via le reseau de maniere synchrone.
Utilisez des loggers asynchrones (pino, zerolog) pour eviter de bloquer le thread principal pendant la serialisation JSON.
Ne loguez jamais les corps complets de requete ou reponse au niveau INFO. Utilisez le niveau DEBUG et activez-le uniquement lors du depannage.
Echantillonnez les evenements a haut volume. Loguez 1 requete de health-check sur 100 au lieu de toutes.
Ecrivez les journaux dans des fichiers locaux et envoyez-les de maniere asynchrone avec Filebeat ou Fluent Bit. Evitez les appels reseau synchrones dans le chemin de logging.
Definissez une taille maximale de ligne de journal (par ex. 16 Ko) pour empecher les entrees surdimensionnees d'encombrer le pipeline.
Effectuez la rotation des fichiers de journaux avec logrotate ou la rotation integree de la bibliotheque pour eviter l'epuisement du disque.
Validez vos journaux JSONL en ligne
Utilisez nos outils gratuits dans le navigateur pour inspecter, valider et convertir des fichiers de journaux JSONL. Sans telechargement, tout le traitement se fait localement.