Logging Strutturato con JSONL

Una guida completa al logging strutturato usando il formato JSONL (JSON Lines). Impara a integrare con ELK Stack, Fluentd, CloudWatch, GCP Logging e Azure Monitor con esempi di codice pronti per la produzione.

Ultimo aggiornamento: Febbraio 2026

Perché il Logging Strutturato con JSONL?

I log tradizionali in testo puro sono leggibili dall'uomo ma quasi impossibili da analizzare in modo affidabile per le macchine. Una riga come '2024-01-15 ERROR User login failed for admin' nasconde la severità, il tipo di evento e il nome utente all'interno di una stringa non strutturata. Ogni strumento di aggregazione dei log deve ricorrere a espressioni regolari fragili per estrarre significato, e qualsiasi modifica al formato del log rompe la pipeline.

Il logging strutturato risolve questo problema emettendo ogni voce di log come un oggetto JSON con campi ben definiti. JSONL (JSON Lines) fa un passo ulteriore: un oggetto JSON per riga, nessun array avvolgente, nessuna virgola finale. Questo rende i log JSONL adatti all'append, allo streaming e banalmente analizzabili da ogni piattaforma di log principale. In questa guida imparerai come produrre log JSONL da Python, Node.js e Go, e come inviarli a ELK, Fluentd e ai tre principali provider cloud.

Rispetto ad altri formati strutturati come CSV o XML, JSONL offre il miglior equilibrio di flessibilità, supporto degli strumenti e leggibilità umana. I campi possono essere nidificati, i tipi sono preservati e non è mai necessario definire uno schema fisso in anticipo. Ecco perché JSONL è diventato lo standard de facto per il logging strutturato nelle infrastrutture moderne.

Librerie di Logging per Linguaggio

La maggior parte delle librerie di logging moderne supporta l'output JSON strutturato nativamente o tramite una semplice modifica della configurazione. Di seguito le librerie consigliate per Python, Node.js e Go, ciascuna produce output compatibile con JSONL.

Python: structlog

Consigliato

structlog è la libreria di logging strutturato leader per Python. Avvolge il modulo logging standard e produce output JSON per default. La sua pipeline di processori ti permette di arricchire, filtrare e trasformare le voci di log prima della serializzazione.

Python: structlog
import structlog
# Configure structlog for 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()
# Each call produces one JSONL line
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

Più Veloce

pino è il logger Node.js più veloce, produce output JSONL per default senza alcuna configurazione. Raggiunge un basso overhead attraverso la scrittura asincrona e un percorso di serializzazione minimo. Il pacchetto companion pino-pretty fornisce output leggibile per lo sviluppo locale.

Node.js: pino
import pino from 'pino';
// pino outputs JSONL by default
const log = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
});
// Each call produces one JSONL line
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 inherit context
const reqLog = log.child({ requestId: 'req-abc-123' });
reqLog.info({ path: '/api/users' }, 'request.start');

Go: zerolog

Zero Allocazioni

zerolog è un logger JSON a zero allocazioni per Go, progettato per le massime prestazioni in servizi ad alto throughput. Scrive direttamente su un io.Writer senza allocazioni intermedie, rendendolo ideale per applicazioni sensibili alla latenza. La sua API a catena è sia ergonomica che efficiente.

Go: zerolog
package main
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// Configure JSONL output to stdout
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
// Each call produces one JSONL line
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")
}

Integrazione ELK Stack

Lo ELK Stack (Elasticsearch, Logstash, Kibana) è la piattaforma open-source di gestione dei log più diffusa. I log JSONL si integrano perfettamente perché Logstash e Filebeat possono analizzare JSON nativamente, eliminando la necessità di pattern grok complessi.

Filebeat è il log shipper leggero che segue i tuoi file di log JSONL e li inoltra a Logstash o Elasticsearch. Impostare json.keys_under_root a true promuove i campi JSON a campi Elasticsearch di primo livello, rendendoli direttamente ricercabili in Kibana.

Configurazione Filebeat
# 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}"
# Optional: send to Logstash instead
# output.logstash:
# hosts: ["logstash:5044"]

Se hai bisogno di trasformare o arricchire i log prima dell'indicizzazione, Logstash si posiziona tra Filebeat ed Elasticsearch. Il codec json analizza automaticamente ogni riga JSONL. Usa il filtro mutate per rinominare campi, aggiungere tag o rimuovere dati sensibili prima dell'archiviazione.

Pipeline Logstash
# logstash.conf
input {
beats {
port => 5044
codec => json
}
}
filter {
# Parse timestamp from the JSON field
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
# Add environment tag
mutate {
add_field => { "environment" => "production" }
remove_field => ["agent", "ecs", "host"]
}
# Route by log level
if [level] == "error" or [level] == "fatal" {
mutate { add_tag => ["alert"] }
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}

Fluentd e Fluent Bit

Fluentd e il suo fratello leggero Fluent Bit sono progetti CNCF graduated ampiamente usati negli ambienti Kubernetes. Entrambi gestiscono log JSONL nativamente e possono inoltrarli a virtualmente qualsiasi destinazione, da Elasticsearch a S3 ai servizi di log cloud-nativi.

Fluent Bit è il raccoglitore di log preferito per Kubernetes e ambienti con risorse limitate. Consuma circa 450KB di memoria e può elaborare centinaia di migliaia di record al secondo. Il parser json gestisce l'input JSONL senza alcuna configurazione personalizzata.

Configurazione Fluent Bit
[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 offre un ecosistema di plugin più ricco rispetto a Fluent Bit, con oltre 500 plugin della comunità. Usalo quando hai bisogno di routing avanzato, buffering o trasformazioni. Il plugin in_tail con format json legge nativamente i file JSONL.

Configurazione Fluentd
# 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>

Piattaforme di Logging Cloud

Tutti e tre i principali provider cloud accettano log in formato JSONL e analizzano automaticamente i campi JSON in attributi ricercabili e filtrabili. Questo elimina la necessità di gestire la propria infrastruttura di log pur offrendo le stesse capacità di query strutturate.

AWS CloudWatch Logs

AWS

CloudWatch Logs analizza automaticamente le voci di log JSON, rendendo ogni campo interrogabile con CloudWatch Logs Insights. Quando la tua applicazione gira su ECS, Lambda o EC2 con l'agente CloudWatch, l'output JSONL viene indicizzato come dati strutturati senza configurazione aggiuntiva. Usa la sintassi simile a SQL di Logs Insights per interrogare milioni di voci di log in pochi secondi.

AWS CloudWatch Logs
# CloudWatch Logs Insights query examples
# Find all errors in the last hour
fields @timestamp, level, event, user_id
| filter level = "error"
| sort @timestamp desc
| limit 100
# Aggregate error counts by event type
fields event
| filter level = "error"
| stats count(*) as error_count by event
| sort error_count desc
# P99 latency by 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 (precedentemente Stackdriver) tratta i payload JSON come voci di log strutturate con supporto di prima classe. Quando scrivi JSONL su stdout su Cloud Run, GKE o Cloud Functions, l'agente di logging analizza ogni riga in un LogEntry strutturato. Il campo severity si mappa direttamente ai livelli di severità di Cloud Logging, e i campi jsonPayload diventano indicizzati e ricercabili.

GCP Cloud Logging
# Python example for Cloud Run / Cloud Functions
import json
import sys
def log(severity: str, message: str, **fields):
"""Write a JSONL log entry compatible with GCP Cloud Logging."""
entry = {
"severity": severity.upper(), # Maps to Cloud Logging severity
"message": message,
**fields,
}
print(json.dumps(entry), file=sys.stdout, flush=True)
# GCP automatically parses these as structured 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 ingerisce log JSONL tramite l'Azure Monitor Agent o l'ingestione diretta via API. Application Insights analizza automaticamente le tracce JSON e gli eventi personalizzati. KQL (Kusto Query Language) fornisce interrogazioni potenti su dati di log strutturati con supporto per aggregazione, analisi delle serie temporali e rilevamento delle anomalie.

Azure Monitor Logs
// KQL queries for JSONL logs in Azure Monitor
// Find recent errors
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
// Error rate over time (5-minute buckets)
AppTraces
| where SeverityLevel >= 3
| summarize ErrorCount = count() by bin(TimeGenerated, 5m)
| render timechart
// Top error events
AppTraces
| where SeverityLevel >= 3
| extend parsed = parse_json(Message)
| summarize Count = count() by tostring(parsed.event)
| top 10 by Count

Best Practice per il Logging JSONL

Seguire convenzioni coerenti tra i tuoi servizi rende i log JSONL più facili da interrogare, correlare e utilizzare per gli alert. Queste best practice derivano da sistemi di produzione che elaborano miliardi di voci di log al giorno.

Usa Livelli di Log Coerenti

Adotta una scala di severità standard e usala in modo coerente su tutti i servizi. La convenzione più comune utilizza cinque livelli: DEBUG per la diagnostica di sviluppo, INFO per le operazioni normali, WARN per problemi recuperabili, ERROR per guasti che richiedono attenzione e FATAL per crash irrecuperabili. Mappa i livelli numerici (come la scala 10-60 di pino) a questi nomi stringa al momento dell'ingestione per query uniformi.

Usa Livelli di Log Coerenti
{"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"}

Standardizza i Nomi dei Campi

Definisci uno schema condiviso per i campi comuni tra tutti i servizi. Usa snake_case per i nomi dei campi, ISO 8601 per i timestamp e nomi coerenti per gli identificatori di correlazione. Come minimo, ogni voce di log dovrebbe contenere: timestamp, level, event (un nome evento leggibile dalla macchina) e service. Le voci con scope di richiesta dovrebbero includere request_id e user_id per il tracing.

Standardizza i Nomi dei Campi
// Recommended field schema
{
"timestamp": "2026-02-15T08:30:00.000Z", // ISO 8601, always UTC
"level": "info", // debug|info|warn|error|fatal
"event": "order.created", // dot-notation event name
"service": "order-api", // emitting service
"version": "1.4.2", // service version
"request_id": "req-abc-123", // correlation ID
"trace_id": "4bf92f3577b34da6", // distributed trace ID
"user_id": "u-42", // authenticated user
"duration_ms": 120, // numeric, not string
"order_id": "ord-789", // domain-specific context
"items_count": 3 // domain-specific context
}

Considerazioni sulle Prestazioni

Il logging strutturato aggiunge overhead di serializzazione a ogni chiamata di log. Nei servizi ad alto throughput, questo può diventare significativo. Usa il logging asincrono (pino, zerolog) per spostare la serializzazione fuori dal percorso critico. Evita di loggare oggetti grandi o body di richiesta/risposta a livello INFO. Usa il campionamento per i log di debug ad alto volume. Scrivi su file locali e lascia che un sidecar (Filebeat, Fluent Bit) gestisca l'invio, piuttosto che inviare log sulla rete in modo sincrono.

Usa logger asincroni (pino, zerolog) per evitare di bloccare il thread principale durante la serializzazione JSON.

Non loggare mai body completi di richiesta o risposta a livello INFO. Usa il livello DEBUG e attivalo solo durante il troubleshooting.

Campiona gli eventi ad alto volume. Logga 1 ogni 100 richieste di health-check invece di tutte.

Scrivi i log su file locali e inviali in modo asincrono con Filebeat o Fluent Bit. Evita chiamate di rete sincrone nel percorso di log.

Imposta una dimensione massima della riga di log (es. 16KB) per impedire che voci sovradimensionate intasino la pipeline.

Ruota i file di log con logrotate o la rotazione integrata nella libreria per prevenire l'esaurimento del disco.

Valida i Tuoi Log JSONL Online

Usa i nostri strumenti gratuiti basati su browser per ispezionare, validare e convertire file di log JSONL. Nessun upload necessario, tutta l'elaborazione avviene localmente.

Ispeziona i Log JSONL nel Tuo Browser

Visualizza, cerca e valida file di log JSONL fino a 1GB. Caricamento istantaneo, elaborazione lato client, 100% privato.

Domande Frequenti

Logging JSONL — Log Strutturati, ELK/Fluentd e Ingestione...