Gestructureerde logging met JSONL
Een uitgebreide handleiding voor gestructureerde logging met het JSONL (JSON Lines) formaat. Leer hoe je integreert met ELK Stack, Fluentd, CloudWatch, GCP Logging en Azure Monitor met productieklare codevoorbeelden.
Laatst bijgewerkt: februari 2026
Waarom gestructureerde logging met JSONL?
Traditionele platte-tekstlogs zijn leesbaar voor mensen maar bijna onmogelijk betrouwbaar door machines te parseren. Een regel als '2024-01-15 ERROR User login failed for admin' verbergt de ernst, het eventtype en de gebruikersnaam in een ongestructureerde string. Elk logaggregatiesysteem moet terugvallen op fragiele reguliere expressies om betekenis te extraheren, en elke wijziging in het logformaat breekt de pipeline.
Gestructureerde logging lost dit probleem op door elke logvermelding uit te zenden als een JSON-object met goed gedefinieerde velden. JSONL (JSON Lines) gaat nog een stap verder: één JSON-object per regel, geen omwikkelende array, geen afsluitende komma's. Dit maakt JSONL-logs append-vriendelijk, streambaar en triviaal parsebaar door elk groot logplatform. In deze handleiding leer je hoe je JSONL-logs produceert vanuit Python, Node.js en Go, en hoe je ze verstuurt naar ELK, Fluentd en de drie grote cloudproviders.
Vergeleken met andere gestructureerde formaten zoals CSV of XML biedt JSONL de beste balans van flexibiliteit, toolondersteuning en leesbaarheid voor mensen. Velden kunnen genest zijn, types worden behouden en je hoeft nooit vooraf een vast schema te definiëren. Dit is waarom JSONL de feitelijke standaard is geworden voor gestructureerde logging in moderne infrastructuur.
Logbibliotheken per taal
De meeste moderne logbibliotheken ondersteunen gestructureerde JSON-output standaard of via een eenvoudige configuratiewijziging. Hieronder staan de aanbevolen bibliotheken voor Python, Node.js en Go, die elk JSONL-compatibele output produceren.
Python: structlog
Aanbevolenstructlog is de toonaangevende gestructureerde logbibliotheek voor Python. Het wikkelt de standaard logging-module en produceert standaard JSON-output. De processorpipeline laat je logvermeldingen verrijken, filteren en transformeren voor serialisatie.
import structlog# Configureer structlog voor JSONL-outputstructlog.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()# Elke aanroep produceert één JSONL-regellog.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
Snelstpino is de snelste Node.js-logger die standaard JSONL-output produceert zonder configuratie. Het bereikt lage overhead door asynchroon schrijven en een minimaal serialisatiepad. Het bijbehorende pino-pretty pakket biedt leesbare output voor lokale ontwikkeling.
import pino from 'pino';// pino geeft standaard JSONL uitconst log = pino({level: 'info',timestamp: pino.stdTimeFunctions.isoTime,});// Elke aanroep produceert één JSONL-regellog.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"}// Child loggers erven contextconst reqLog = log.child({ requestId: 'req-abc-123' });reqLog.info({ path: '/api/users' }, 'request.start');
Go: zerolog
Zero Alloczerolog is een zero-allocation JSON-logger voor Go, ontworpen voor maximale prestaties in diensten met hoge doorvoer. Het schrijft direct naar een io.Writer zonder tussentijdse allocaties, waardoor het ideaal is voor latentiegevoelige applicaties. De geketende API is zowel ergonomisch als efficiënt.
package mainimport ("os""github.com/rs/zerolog""github.com/rs/zerolog/log")func main() {// Configureer JSONL-output naar stdoutzerolog.TimeFieldFormat = zerolog.TimeFormatUnixlog.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()// Elke aanroep produceert één JSONL-regellog.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")}
ELK Stack-integratie
De ELK Stack (Elasticsearch, Logstash, Kibana) is het meest gebruikte open-source logbeheerplatform. JSONL-logs integreren naadloos omdat Logstash en Filebeat JSON native kunnen parseren, waardoor complexe grok-patronen niet nodig zijn.
Filebeat is de lichtgewicht logverzender die je JSONL-logbestanden volgt en doorstuurt naar Logstash of Elasticsearch. Door json.keys_under_root op true te zetten worden de JSON-velden gepromoveerd tot top-level Elasticsearch-velden, waardoor ze direct doorzoekbaar zijn in 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}"# Optioneel: verstuur naar Logstash in plaats daarvan# output.logstash:# hosts: ["logstash:5044"]
Als je logs wilt transformeren of verrijken voor indexering, zit Logstash tussen Filebeat en Elasticsearch. De json-codec parst elke JSONL-regel automatisch. Gebruik het mutate-filter om velden te hernoemen, tags toe te voegen of gevoelige data te verwijderen voor opslag.
# logstash.confinput {beats {port => 5044codec => json}}filter {# Parseer timestamp uit het JSON-velddate {match => ["timestamp", "ISO8601"]target => "@timestamp"}# Voeg omgevingstag toemutate {add_field => { "environment" => "production" }remove_field => ["agent", "ecs", "host"]}# Routeer op logniveauif [level] == "error" or [level] == "fatal" {mutate { add_tag => ["alert"] }}}output {elasticsearch {hosts => ["http://elasticsearch:9200"]index => "app-logs-%{+YYYY.MM.dd}"}}
Fluentd & Fluent Bit
Fluentd en zijn lichtgewicht broer Fluent Bit zijn CNCF-afgestudeerde projecten die veel gebruikt worden in Kubernetes-omgevingen. Beide verwerken JSONL-logs native en kunnen ze doorsturen naar vrijwel elke bestemming, van Elasticsearch tot S3 tot cloud-native logdiensten.
Fluent Bit is de voorkeurlogcollector voor Kubernetes en resource-beperkte omgevingen. Het verbruikt ongeveer 450KB geheugen en kan honderdduizenden records per seconde verwerken. De json-parser verwerkt JSONL-invoer zonder aangepaste configuratie.
[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 biedt een rijker plugin-ecosysteem vergeleken met Fluent Bit, met meer dan 500 community-plugins. Gebruik het wanneer je geavanceerde routering, buffering of transformaties nodig hebt. De in_tail-plugin met format json leest JSONL-bestanden native.
# 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>
Cloud-loggingplatforms
Alle drie de grote cloudproviders accepteren JSONL-geformatteerde logs en parseren JSON-velden automatisch naar doorzoekbare, filterbare attributen. Dit elimineert de noodzaak om je eigen loginfrastructuur te draaien terwijl je dezelfde gestructureerde querymogelijkheden krijgt.
AWS CloudWatch Logs
AWSCloudWatch Logs parst automatisch JSON-logvermeldingen, waardoor elk veld doorzoekbaar is met CloudWatch Logs Insights. Wanneer je applicatie draait op ECS, Lambda of EC2 met de CloudWatch-agent, wordt JSONL-output geïndexeerd als gestructureerde data zonder extra configuratie. Gebruik Logs Insights SQL-achtige syntax om miljoenen logvermeldingen in seconden te doorzoeken.
# CloudWatch Logs Insights queryvoorbeelden# Zoek alle fouten in het afgelopen uurfields @timestamp, level, event, user_id| filter level = "error"| sort @timestamp desc| limit 100# Aggregeer foutaantallen per eventtypefields event| filter level = "error"| stats count(*) as error_count by event| sort error_count desc# P99 latentie per 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 (voorheen Stackdriver) behandelt JSON-payloads als gestructureerde logvermeldingen met eersteklas ondersteuning. Wanneer je JSONL naar stdout schrijft op Cloud Run, GKE of Cloud Functions, parst de logging-agent elke regel naar een gestructureerd LogEntry. Het severity-veld mapt direct naar Cloud Logging-ernstniveaus, en jsonPayload-velden worden geïndexeerd en doorzoekbaar.
# Python-voorbeeld voor Cloud Run / Cloud Functionsimport jsonimport sysdef log(severity: str, message: str, **fields):"""Schrijf een JSONL-logvermelding compatibel met GCP Cloud Logging."""entry = {"severity": severity.upper(), # Mapt naar Cloud Logging-ernst"message": message,**fields,}print(json.dumps(entry), file=sys.stdout, flush=True)# GCP parst deze automatisch als gestructureerde logslog("INFO", "user.login", user_id="u-42", ip="10.0.0.1")log("ERROR", "db.timeout", host="db-primary", latency_ms=5200)# Query in Cloud Logging:# jsonPayload.user_id = "u-42"# severity >= ERROR
Azure Monitor Logs
AzureAzure Monitor neemt JSONL-logs op via de Azure Monitor Agent of directe API-ingestie. Application Insights parst automatisch JSON-traces en aangepaste events. KQL (Kusto Query Language) biedt krachtige queries over gestructureerde logdata met ondersteuning voor aggregatie, tijdreeksanalyse en anomaliedetectie.
// KQL-queries voor JSONL-logs in Azure Monitor// Zoek recente foutenAppTraces| where SeverityLevel >= 3| extend parsed = parse_json(Message)| project TimeGenerated, parsed.event, parsed.user_id,parsed.error_message| order by TimeGenerated desc| take 100// Foutenpercentage over tijd (5-minuten buckets)AppTraces| where SeverityLevel >= 3| summarize ErrorCount = count() by bin(TimeGenerated, 5m)| render timechart// Top fouteventsAppTraces| where SeverityLevel >= 3| extend parsed = parse_json(Message)| summarize Count = count() by tostring(parsed.event)| top 10 by Count
Best practices voor JSONL-logging
Het volgen van consistente conventies in al je services maakt JSONL-logs gemakkelijker te queryen, correleren en waarschuwen. Deze best practices zijn afkomstig uit productiesystemen die miljarden logvermeldingen per dag verwerken.
Gebruik consistente logniveaus
Adopteer een standaard ernstschaal en gebruik deze consistent in alle services. De meest voorkomende conventie gebruikt vijf niveaus: DEBUG voor ontwikkelingsdiagnostiek, INFO voor normale operaties, WARN voor herstelbare problemen, ERROR voor fouten die aandacht vereisen, en FATAL voor onherstelbare crashes. Vertaal numerieke niveaus (zoals pino's 10-60 schaal) naar deze stringnamen bij ingestie voor uniform queryen.
{"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"}
Standaardiseer veldnamen
Definieer een gedeeld schema voor veelvoorkomende velden in alle services. Gebruik snake_case voor veldnamen, ISO 8601 voor timestamps en consistente naamgeving voor correlatie-identifiers. Minimaal moet elke logvermelding bevatten: timestamp, level, event (een machineleesbare eventnaam) en service. Request-gescipete vermeldingen moeten request_id en user_id bevatten voor tracing.
// Aanbevolen veldschema{"timestamp": "2026-02-15T08:30:00.000Z", // ISO 8601, altijd UTC"level": "info", // debug|info|warn|error|fatal"event": "order.created", // punt-notatie eventnaam"service": "order-api", // uitzendende service"version": "1.4.2", // serviceversie"request_id": "req-abc-123", // correlatie-ID"trace_id": "4bf92f3577b34da6", // gedistribueerde trace-ID"user_id": "u-42", // geauthenticeerde gebruiker"duration_ms": 120, // numeriek, geen string"order_id": "ord-789", // domein-specifieke context"items_count": 3 // domein-specifieke context}
Prestatieoverwegingen
Gestructureerde logging voegt serialisatie-overhead toe aan elke logaanroep. In diensten met hoge doorvoer kan dit significant worden. Gebruik asynchrone logging (pino, zerolog) om serialisatie van het kritieke pad te halen. Vermijd het loggen van grote objecten of request/response bodies op INFO-niveau. Gebruik sampling voor debug-logs met hoog volume. Schrijf naar lokale bestanden en laat een sidecar (Filebeat, Fluent Bit) het verzenden afhandelen, in plaats van logs synchroon over het netwerk te sturen.
Gebruik asynchrone loggers (pino, zerolog) om de hoofdthread niet te blokkeren tijdens JSON-serialisatie.
Log nooit volledige request- of response-bodies op INFO-niveau. Gebruik DEBUG-niveau en schakel dit alleen in bij het oplossen van problemen.
Sample events met hoog volume. Log 1 op de 100 health-check verzoeken in plaats van allemaal.
Schrijf logs naar lokale bestanden en verstuur ze asynchroon met Filebeat of Fluent Bit. Vermijd synchrone netwerkoproepen in het logpad.
Stel een maximale logregelgrootte in (bijv. 16KB) om te voorkomen dat te grote vermeldingen de pipeline verstoppen.
Roteer logbestanden met logrotate of ingebouwde bibliotheekrotatie om schijfuitputting te voorkomen.
Valideer je JSONL-logs online
Gebruik onze gratis browsergebaseerde tools om JSONL-logbestanden te inspecteren, valideren en converteren. Geen uploads nodig, alle verwerking gebeurt lokaal.