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

Aanbevolen

structlog 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.

Python: structlog
import structlog
# Configureer structlog voor JSONL-output
structlog.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-regel
log.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

Snelst

pino 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.

Node.js: pino
import pino from 'pino';
// pino geeft standaard JSONL uit
const log = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
});
// Elke aanroep produceert één JSONL-regel
log.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 context
const reqLog = log.child({ requestId: 'req-abc-123' });
reqLog.info({ path: '/api/users' }, 'request.start');

Go: zerolog

Zero Alloc

zerolog 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.

Go: zerolog
package main
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// Configureer JSONL-output naar stdout
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
// Elke aanroep produceert één JSONL-regel
log.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-configuratie
# filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/app/*.jsonl
json.keys_under_root: true
json.add_error_key: true
json.message_key: message
json.overwrite_keys: true
output.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-pipeline
# logstash.conf
input {
beats {
port => 5044
codec => json
}
}
filter {
# Parseer timestamp uit het JSON-veld
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
# Voeg omgevingstag toe
mutate {
add_field => { "environment" => "production" }
remove_field => ["agent", "ecs", "host"]
}
# Routeer op logniveau
if [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.

Fluent Bit-configuratie
[SERVICE]
Flush 5
Daemon Off
Log_Level info
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/app/*.jsonl
Parser json
Tag app.*
Refresh_Interval 5
[FILTER]
Name modify
Match app.*
Add cluster production-us-east-1
Add service my-api
[OUTPUT]
Name es
Match app.*
Host elasticsearch
Port 9200
Index app-logs
Type _doc
Logstash_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-configuratie
# fluentd.conf
<source>
@type tail
path /var/log/app/*.jsonl
pos_file /var/log/fluentd/app.pos
tag app.logs
<parse>
@type json
time_key timestamp
time_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 elasticsearch
host elasticsearch
port 9200
index_name app-logs
logstash_format true
<buffer>
@type memory
flush_interval 5s
chunk_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

AWS

CloudWatch 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.

AWS CloudWatch Logs
# CloudWatch Logs Insights queryvoorbeelden
# Zoek alle fouten in het afgelopen uur
fields @timestamp, level, event, user_id
| filter level = "error"
| sort @timestamp desc
| limit 100
# Aggregeer foutaantallen per eventtype
fields event
| filter level = "error"
| stats count(*) as error_count by event
| sort error_count desc
# P99 latentie per endpoint
fields path, duration_ms
| filter ispresent(duration_ms)
| stats pct(duration_ms, 99) as p99 by path
| sort p99 desc

GCP Cloud Logging

GCP

Google 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.

GCP Cloud Logging
# Python-voorbeeld voor Cloud Run / Cloud Functions
import json
import sys
def 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 logs
log("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

Azure

Azure 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.

Azure Monitor Logs
// KQL-queries voor JSONL-logs in Azure Monitor
// Zoek recente fouten
AppTraces
| 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 foutevents
AppTraces
| 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.

Gebruik consistente logniveaus
{"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.

Standaardiseer veldnamen
// 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.

Inspecteer JSONL-logs in je browser

Bekijk, doorzoek en valideer JSONL-logbestanden tot 1GB. Direct laden, verwerking aan clientzijde, 100% privé.

Veelgestelde vragen

JSONL Logging — Gestructureerde logs, ELK/Fluentd & cloud...