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
Recomendadostructlog 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.
import structlog# Configure structlog for 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()# Each call produces one JSONL linelog.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 rapidopino 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.
import pino from 'pino';// pino outputs JSONL by defaultconst log = pino({level: 'info',timestamp: pino.stdTimeFunctions.isoTime,});// Each call produces one JSONL linelog.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 contextconst reqLog = log.child({ requestId: 'req-abc-123' });reqLog.info({ path: '/api/users' }, 'request.start');
Go: zerolog
Zero Alloczerolog 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.
package mainimport ("os""github.com/rs/zerolog""github.com/rs/zerolog/log")func main() {// Configure JSONL output to stdoutzerolog.TimeFieldFormat = zerolog.TimeFormatUnixlog.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()// Each call produces one JSONL linelog.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.
# 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}"# 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.
# logstash.confinput {beats {port => 5044codec => json}}filter {# Parse timestamp from the JSON fielddate {match => ["timestamp", "ISO8601"]target => "@timestamp"}# Add environment tagmutate {add_field => { "environment" => "production" }remove_field => ["agent", "ecs", "host"]}# Route by log levelif [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.
[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 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.
# 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>
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
AWSCloudWatch 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.
# CloudWatch Logs Insights query examples# Find all errors in the last hourfields @timestamp, level, event, user_id| filter level = "error"| sort @timestamp desc| limit 100# Aggregate error counts by event typefields event| filter level = "error"| stats count(*) as error_count by event| sort error_count desc# P99 latency by 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 (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.
# Python example for Cloud Run / Cloud Functionsimport jsonimport sysdef 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 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 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.
// KQL queries for JSONL logs in Azure Monitor// Find recent errorsAppTraces| 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 eventsAppTraces| 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.
{"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.
// 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.