Logging estructurado con JSONL

Una guia completa sobre logging estructurado usando formato JSONL (JSON Lines). Aprende a integrar con ELK Stack, Fluentd, CloudWatch, GCP Logging y Azure Monitor con ejemplos de codigo listos para produccion.

Ultima actualizacion: febrero 2026

¿Por que logging estructurado con JSONL?

Los logs tradicionales en texto plano son legibles por humanos pero casi imposibles de analizar de forma fiable por maquinas. Una linea como '2024-01-15 ERROR User login failed for admin' oculta la severidad, el tipo de evento y el nombre de usuario dentro de una cadena no estructurada. Cada herramienta de agregacion de logs debe recurrir a expresiones regulares fragiles para extraer significado, y cualquier cambio en el formato del log rompe el pipeline.

El logging estructurado resuelve este problema emitiendo cada entrada de log como un objeto JSON con campos bien definidos. JSONL (JSON Lines) lleva esto un paso mas alla: un objeto JSON por linea, sin arreglo envolvente, sin comas finales. Esto hace que los logs JSONL sean faciles de agregar, transmitir y analizar de forma trivial por todas las principales plataformas de logs. En esta guia aprenderas como producir logs JSONL desde Python, Node.js y Go, y como enviarlos a ELK, Fluentd y los tres principales proveedores de nube.

Comparado con otros formatos estructurados como CSV o XML, JSONL ofrece el mejor equilibrio entre flexibilidad, soporte de herramientas y legibilidad humana. Los campos pueden ser anidados, los tipos se preservan, y nunca necesitas definir un esquema fijo de antemano. Esta es la razon por la que JSONL se ha convertido en el estandar de facto para logging estructurado en infraestructura moderna.

Bibliotecas de logging por lenguaje

La mayoria de las bibliotecas de logging modernas soportan salida JSON estructurada de forma nativa o mediante un simple cambio de configuracion. A continuacion se presentan las bibliotecas recomendadas para Python, Node.js y Go, cada una produciendo salida compatible con JSONL.

Python: structlog

Recomendado

structlog es la biblioteca lider de logging estructurado para Python. Envuelve el modulo logging estandar y produce salida JSON por defecto. Su pipeline de procesadores te permite enriquecer, filtrar y transformar entradas de log antes de la serializacion.

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

Mas rapido

pino es el logger mas rapido de Node.js, produciendo salida JSONL por defecto sin configuracion. Logra baja sobrecarga a traves de escritura asincrona y un camino de serializacion minimo. El paquete companero pino-pretty proporciona salida legible por humanos para desarrollo local.

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 Alloc

zerolog es un logger JSON de cero asignaciones para Go, disenado para maximo rendimiento en servicios de alto rendimiento. Escribe directamente a un io.Writer sin asignaciones intermedias, lo que lo hace ideal para aplicaciones sensibles a la latencia. Su API encadenada es tanto ergonomica como eficiente.

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")
}

Integracion con ELK Stack

El ELK Stack (Elasticsearch, Logstash, Kibana) es la plataforma de gestion de logs de codigo abierto mas ampliamente desplegada. Los logs JSONL se integran perfectamente porque Logstash y Filebeat pueden analizar JSON de forma nativa, eliminando la necesidad de patrones grok complejos.

Filebeat es el recolector ligero de logs que hace seguimiento de tus archivos de log JSONL y los reenvia a Logstash o Elasticsearch. Establecer json.keys_under_root en true promueve los campos JSON a campos de nivel superior de Elasticsearch, haciendolos directamente buscables en Kibana.

Configuracion de 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"]

Si necesitas transformar o enriquecer logs antes de indexarlos, Logstash se situa entre Filebeat y Elasticsearch. El codec json analiza cada linea JSONL automaticamente. Usa el filtro mutate para renombrar campos, agregar etiquetas o eliminar datos sensibles antes del almacenamiento.

Pipeline de 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 y Fluent Bit

Fluentd y su hermano ligero Fluent Bit son proyectos graduados de CNCF ampliamente utilizados en entornos Kubernetes. Ambos manejan logs JSONL de forma nativa y pueden reenviarlos a practicamente cualquier destino, desde Elasticsearch hasta S3 o servicios de log nativos de la nube.

Fluent Bit es el recolector de logs preferido para Kubernetes y entornos con recursos limitados. Consume aproximadamente 450KB de memoria y puede procesar cientos de miles de registros por segundo. El analizador json maneja la entrada JSONL sin ninguna configuracion personalizada.

Configuracion de 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 ofrece un ecosistema de plugins mas rico comparado con Fluent Bit, con mas de 500 plugins de la comunidad. Usalo cuando necesites enrutamiento avanzado, buffering o transformaciones. El plugin in_tail con formato json lee archivos JSONL de forma nativa.

Configuracion de 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>

Plataformas de logging en la nube

Los tres principales proveedores de nube aceptan logs en formato JSONL y analizan automaticamente los campos JSON en atributos buscables y filtrables. Esto elimina la necesidad de ejecutar tu propia infraestructura de logs mientras te da las mismas capacidades de consulta estructurada.

AWS CloudWatch Logs

AWS

CloudWatch Logs analiza automaticamente las entradas de log JSON, haciendo cada campo consultable con CloudWatch Logs Insights. Cuando tu aplicacion se ejecuta en ECS, Lambda o EC2 con el agente de CloudWatch, la salida JSONL se indexa como datos estructurados sin configuracion adicional. Usa la sintaxis tipo SQL de Logs Insights para consultar millones de entradas de log en segundos.

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 (anteriormente Stackdriver) trata las cargas JSON como entradas de log estructuradas con soporte de primera clase. Cuando escribes JSONL a stdout en Cloud Run, GKE o Cloud Functions, el agente de logging analiza cada linea en un LogEntry estructurado. El campo severity se mapea directamente a los niveles de severidad de Cloud Logging, y los campos de jsonPayload se indexan y se vuelven buscables.

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 ingesta logs JSONL a traves del Azure Monitor Agent o ingestion directa por API. Application Insights analiza automaticamente trazas JSON y eventos personalizados. KQL (Kusto Query Language) proporciona consultas potentes sobre datos de log estructurados con soporte para agregacion, analisis de series temporales y deteccion de anomalias.

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

Mejores practicas para logging JSONL

Seguir convenciones consistentes en todos tus servicios hace que los logs JSONL sean mas faciles de consultar, correlacionar y configurar alertas. Estas mejores practicas provienen de sistemas en produccion que procesan miles de millones de entradas de log por dia.

Usa niveles de log consistentes

Adopta una escala de severidad estandar y usala de forma consistente en todos los servicios. La convencion mas comun usa cinco niveles: DEBUG para diagnosticos de desarrollo, INFO para operaciones normales, WARN para problemas recuperables, ERROR para fallos que requieren atencion, y FATAL para caidas irrecuperables. Mapea niveles numericos (como la escala 10-60 de pino) a estos nombres de cadena en el momento de la ingestion para consultas uniformes.

Usa niveles de log consistentes
{"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"}

Estandariza los nombres de campos

Define un esquema compartido para campos comunes en todos los servicios. Usa snake_case para nombres de campos, ISO 8601 para marcas de tiempo, y nombres consistentes para identificadores de correlacion. Como minimo, cada entrada de log debe contener: timestamp, level, event (un nombre de evento legible por maquina) y service. Las entradas con alcance de solicitud deben incluir request_id y user_id para rastreo.

Estandariza los nombres de campos
// 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
}

Consideraciones de rendimiento

El logging estructurado agrega sobrecarga de serializacion a cada llamada de log. En servicios de alto rendimiento, esto puede ser significativo. Usa logging asincrono (pino, zerolog) para mover la serializacion fuera del camino critico. Evita registrar objetos grandes o cuerpos de solicitud/respuesta a nivel INFO. Usa muestreo para logs de depuracion de alto volumen. Escribe en archivos locales y deja que un sidecar (Filebeat, Fluent Bit) se encargue del envio, en lugar de enviar logs por la red de forma sincrona.

Usa loggers asincronos (pino, zerolog) para evitar bloquear el hilo principal durante la serializacion JSON.

Nunca registres cuerpos completos de solicitud o respuesta a nivel INFO. Usa el nivel DEBUG y activalo solo cuando estes solucionando problemas.

Muestrea eventos de alto volumen. Registra 1 de cada 100 solicitudes de health-check en lugar de todas.

Escribe logs en archivos locales y envialos asincronamente con Filebeat o Fluent Bit. Evita llamadas de red sincronas en la ruta de logging.

Establece un tamano maximo de linea de log (por ejemplo, 16KB) para evitar que entradas sobredimensionadas obstruyan el pipeline.

Rota los archivos de log con logrotate o rotacion integrada de la biblioteca para prevenir el agotamiento del disco.

Valida tus logs JSONL online

Usa nuestras herramientas gratuitas basadas en navegador para inspeccionar, validar y convertir archivos de log JSONL. Sin subidas necesarias, todo el procesamiento ocurre localmente.

Inspecciona logs JSONL en tu navegador

Visualiza, busca y valida archivos de log JSONL de hasta 1GB. Carga instantanea, procesamiento del lado del cliente, 100% privado.

Preguntas frecuentes

Logging JSONL — Logs estructurados, ELK/Fluentd e ingesta...