Strukturalne logowanie z JSONL

Kompleksowy przewodnik po strukturalnym logowaniu w formacie JSONL (JSON Lines). Dowiedz sie, jak integrowac z ELK Stack, Fluentd, CloudWatch, GCP Logging i Azure Monitor z przykladami kodu gotowymi do produkcji.

Ostatnia aktualizacja: luty 2026

Dlaczego strukturalne logowanie z JSONL?

Tradycyjne logi tekstowe sa czytelne dla czlowieka, ale prawie niemozliwe do niezawodnego parsowania przez maszyny. Linia taka jak '2024-01-15 ERROR User login failed for admin' ukrywa poziom waznosci, typ zdarzenia i nazwe uzytkownika w nieustrukturyzowanym ciagu. Kazde narzedzie do agregacji logow musi uciekac sie do kruchych wyrazen regularnych, aby wyekstrahowac znaczenie, a kazda zmiana formatu logu psuje potok.

Strukturalne logowanie rozwiazuje ten problem, emitujac kazdy wpis logu jako obiekt JSON z dobrze zdefiniowanymi polami. JSONL (JSON Lines) idzie o krok dalej: jeden obiekt JSON na linie, bez opakowywajacej tablicy, bez koncowych przecinkow. Sprawia to, ze logi JSONL sa przyjazne dopisywaniu, strumieniowalne i trywialnie parsowalne przez kazda wieksza platforme logowania. W tym przewodniku dowiesz sie, jak generowac logi JSONL z Python, Node.js i Go oraz jak wysylac je do ELK, Fluentd i trzech glownych dostawcow chmurowych.

W porownaniu z innymi formatami strukturalnymi, takimi jak CSV lub XML, JSONL oferuje najlepszy balans elastycznosci, wsparcia narzedzi i czytelnosci dla czlowieka. Pola moga byc zagniezdzene, typy sa zachowane i nigdy nie musisz definiowac ustalonego schematu z gory. Dlatego JSONL stal sie de facto standardem strukturalnego logowania w nowoczesnej infrastrukturze.

Biblioteki logowania wedlug jezyka

Wiekszosc nowoczesnych bibliotek logowania obsluguje strukturalne wyjscie JSON natywnie lub przez prosta zmiane konfiguracji. Ponizej sa zalecane biblioteki dla Python, Node.js i Go, kazda generujaca wyjscie kompatybilne z JSONL.

Python: structlog

Zalecany

structlog to wiodaca biblioteka strukturalnego logowania dla Python. Opakowuje standardowy modul logging i domyslnie generuje wyjscie JSON. Potok procesorow pozwala wzbogacac, filtrowac i transformowac wpisy logu przed serializacja.

Python: structlog
import structlog
# Konfiguracja structlog dla wyjscia JSONL
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()
# Kazde wywolanie generuje jedna linie JSONL
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

Najszybszy

pino to najszybszy logger Node.js, domyslnie generujacy wyjscie JSONL bez konfiguracji. Osiaga niski narzut dzieki asynchronicznemu zapisowi i minimalnej sciezce serializacji. Pakiet towarzyszacy pino-pretty zapewnia czytelne wyjscie do lokalnego rozwoju.

Node.js: pino
import pino from 'pino';
// pino domyslnie generuje wyjscie JSONL
const log = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
});
// Kazde wywolanie generuje jedna linie JSONL
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"}
// Loggery potomne dziedzicza kontekst
const reqLog = log.child({ requestId: 'req-abc-123' });
reqLog.info({ path: '/api/users' }, 'request.start');

Go: zerolog

Zero alokacji

zerolog to logger JSON bez alokacji dla Go, zaprojektowany do maksymalnej wydajnosci w uslugach o duzej przepustowosci. Zapisuje bezposrednio do io.Writer bez posrednich alokacji, co czyni go idealnym dla aplikacji wrazliwych na opoznienia. Lancuchowe API jest zarowno ergonomiczne, jak i wydajne.

Go: zerolog
package main
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// Konfiguracja wyjscia JSONL do stdout
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
// Kazde wywolanie generuje jedna linie JSONL
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")
}

Integracja z ELK Stack

ELK Stack (Elasticsearch, Logstash, Kibana) to najszerzej wdrazana platforma zarzadzania logami open-source. Logi JSONL integruja sie bezproblemowo, poniewaz Logstash i Filebeat moga parsowac JSON natywnie, eliminujac potrzebe zlozonych wzorow grok.

Filebeat to lekki agent wysylajacy logi, ktory sledzi pliki logow JSONL i przesyla je do Logstash lub Elasticsearch. Ustawienie json.keys_under_root na true promuje pola JSON do pol najwyzszego poziomu Elasticsearch, czynic je bezposrednio wyszukiwalnymi w Kibana.

Konfiguracja 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}"
# Opcjonalnie: wyslij do Logstash zamiast
# output.logstash:
# hosts: ["logstash:5044"]

Jesli musisz transformowac lub wzbogacac logi przed indeksowaniem, Logstash stoi miedzy Filebeat a Elasticsearch. Kodek json parsuje kazda linie JSONL automatycznie. Uzyj filtra mutate do zmiany nazw pol, dodawania tagow lub usuwania wrazliwych danych przed przechowywaniem.

Potok Logstash
# logstash.conf
input {
beats {
port => 5044
codec => json
}
}
filter {
# Parsuj znacznik czasu z pola JSON
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
# Dodaj tag srodowiska
mutate {
add_field => { "environment" => "production" }
remove_field => ["agent", "ecs", "host"]
}
# Kieruj wedlug poziomu logu
if [level] == "error" or [level] == "fatal" {
mutate { add_tag => ["alert"] }
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}

Fluentd i Fluent Bit

Fluentd i jego lekki odpowiednik Fluent Bit to projekty CNCF szeroko stosowane w srodowiskach Kubernetes. Oba obsluguja logi JSONL natywnie i moga przesylac je do praktycznie kazdego celu, od Elasticsearch po S3 po natywne uslugi logowania w chmurze.

Fluent Bit to preferowany kolektor logow dla Kubernetes i srodowisk o ograniczonych zasobach. Zuzywa okolo 450KB pamieci i moze przetwarzac setki tysiecy rekordow na sekunde. Parser json obsluguje wejscie JSONL bez dodatkowej konfiguracji.

Konfiguracja 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 oferuje bogatszy ekosystem wtyczek niz Fluent Bit, z ponad 500 wtyczkami spolecznosci. Uzyj go, gdy potrzebujesz zaawansowanego routingu, buforowania lub transformacji. Wtyczka in_tail z format json czyta pliki JSONL natywnie.

Konfiguracja 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>

Platformy logowania w chmurze

Wszyscy trzej glowni dostawcy chmurowi akceptuja logi w formacie JSONL i automatycznie parsuja pola JSON do przeszukiwalnych, filtrowalnych atrybutow. Eliminuje to potrzebe uruchamiania wlasnej infrastruktury logowania, dajac jednoczesnie te same mozliwosci strukturalnego odpytywania.

AWS CloudWatch Logs

AWS

CloudWatch Logs automatycznie parsuje wpisy logow JSON, czynic kazde pole mozliwym do odpytywania za pomoca CloudWatch Logs Insights. Gdy Twoja aplikacja dziala na ECS, Lambda lub EC2 z agentem CloudWatch, wyjscie JSONL jest indeksowane jako dane strukturalne bez dodatkowej konfiguracji. Uzyj skladni SQL-podobnej Logs Insights do odpytywania milionow wpisow logow w sekundach.

AWS CloudWatch Logs
# Przyklady zapytan CloudWatch Logs Insights
# Znajdz wszystkie bledy z ostatniej godziny
fields @timestamp, level, event, user_id
| filter level = "error"
| sort @timestamp desc
| limit 100
# Agreguj liczbe bledow wedlug typu zdarzenia
fields event
| filter level = "error"
| stats count(*) as error_count by event
| sort error_count desc
# P99 opoznienia wedlug endpointu
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 (dawniej Stackdriver) traktuje ladunki JSON jako strukturalne wpisy logow z pierwszorzedna obsluga. Gdy piszesz JSONL do stdout na Cloud Run, GKE lub Cloud Functions, agent logowania parsuje kazda linie do strukturalnego LogEntry. Pole severity mapuje sie bezposrednio na poziomy waznosci Cloud Logging, a pola jsonPayload staja sie indeksowane i przeszukiwalne.

GCP Cloud Logging
# Przyklad Python dla Cloud Run / Cloud Functions
import json
import sys
def log(severity: str, message: str, **fields):
"""Zapisz wpis logu JSONL kompatybilny z GCP Cloud Logging."""
entry = {
"severity": severity.upper(), # Mapuje na poziom waznosci Cloud Logging
"message": message,
**fields,
}
print(json.dumps(entry), file=sys.stdout, flush=True)
# GCP automatycznie parsuje te jako strukturalne logi
log("INFO", "user.login", user_id="u-42", ip="10.0.0.1")
log("ERROR", "db.timeout", host="db-primary", latency_ms=5200)
# Zapytanie w Cloud Logging:
# jsonPayload.user_id = "u-42"
# severity >= ERROR

Azure Monitor Logs

Azure

Azure Monitor pozyskuje logi JSONL poprzez Azure Monitor Agent lub bezposrednie API. Application Insights automatycznie parsuje slady JSON i niestandardowe zdarzenia. KQL (Kusto Query Language) zapewnia potezne odpytywanie strukturalnych danych logow z obsluga agregacji, analizy szeregu czasowego i wykrywania anomalii.

Azure Monitor Logs
// Zapytania KQL dla logow JSONL w Azure Monitor
// Znajdz ostatnie bledy
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
// Wskaznik bledow w czasie (kubly 5-minutowe)
AppTraces
| where SeverityLevel >= 3
| summarize ErrorCount = count() by bin(TimeGenerated, 5m)
| render timechart
// Najczestsze zdarzenia bledow
AppTraces
| where SeverityLevel >= 3
| extend parsed = parse_json(Message)
| summarize Count = count() by tostring(parsed.event)
| top 10 by Count

Najlepsze praktyki logowania JSONL

Przestrzeganie spojnych konwencji we wszystkich uslugach sprawia, ze logi JSONL sa latwiejsze do odpytywania, korelowania i alertowania. Te najlepsze praktyki pochodza z systemow produkcyjnych przetwarzajacych miliardy wpisow logow dziennie.

Uzywaj spojnych poziomow logow

Przyjmij standardowa skale waznosci i uzywaj jej konsekwentnie we wszystkich uslugach. Najczesciej stosowana konwencja uzywa pieciu poziomow: DEBUG do diagnostyki deweloperskiej, INFO do normalnych operacji, WARN do naprawialnych problemow, ERROR do awarii wymagajacych uwagi i FATAL do nienaprawialnych awarii. Mapuj poziomy numeryczne (jak skala 10-60 pino) na te nazwy ciagow przy pozyskiwaniu dla jednolitego odpytywania.

Uzywaj spojnych poziomow logow
{"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"}

Standaryzuj nazwy pol

Zdefiniuj wspolny schemat dla typowych pol we wszystkich uslugach. Uzywaj snake_case dla nazw pol, ISO 8601 dla znacznikow czasu i spojnego nazewnictwa identyfikatorow korelacji. Jako minimum kazdy wpis logu powinien zawierac: timestamp, level, event (maszynowo czytelna nazwa zdarzenia) i service. Wpisy z zakresu zadan powinny zawierac request_id i user_id do sledzenia.

Standaryzuj nazwy pol
// Zalecany schemat pol
{
"timestamp": "2026-02-15T08:30:00.000Z", // ISO 8601, zawsze UTC
"level": "info", // debug|info|warn|error|fatal
"event": "order.created", // nazwa zdarzenia w notacji kropkowej
"service": "order-api", // usluga emitujaca
"version": "1.4.2", // wersja uslugi
"request_id": "req-abc-123", // ID korelacji
"trace_id": "4bf92f3577b34da6", // ID sladu rozproszonego
"user_id": "u-42", // uwierzytelniony uzytkownik
"duration_ms": 120, // numeryczny, nie string
"order_id": "ord-789", // kontekst domenowy
"items_count": 3 // kontekst domenowy
}

Uwagi dotyczace wydajnosci

Strukturalne logowanie dodaje narzut serializacji do kazdego wywolania logu. W uslugach o duzej przepustowosci moze to stac sie znaczace. Uzyj asynchronicznego logowania (pino, zerolog), aby przeniesc serializacje poza sciezke krytyczna. Unikaj logowania duzych obiektow lub cial zadan/odpowiedzi na poziomie INFO. Uzyj probkowania dla logow o duzym wolumenie. Zapisuj do lokalnych plikow i pozwol sidecarowi (Filebeat, Fluent Bit) obslugiwac wysylke, zamiast wyslac logi przez siec synchronicznie.

Uzyj asynchronicznych loggerow (pino, zerolog), aby uniknac blokowania glownego watku podczas serializacji JSON.

Nigdy nie loguj pelnych cial zadan lub odpowiedzi na poziomie INFO. Uzyj poziomu DEBUG i wlaczaj go tylko podczas rozwiazywania problemow.

Probkuj zdarzenia o duzym wolumenie. Loguj 1 na 100 zadan sprawdzania stanu zamiast wszystkich.

Zapisuj logi do lokalnych plikow i wysylaj je asynchronicznie za pomoca Filebeat lub Fluent Bit. Unikaj synchronicznych wywolan sieciowych w sciezce logowania.

Ustaw maksymalny rozmiar linii logu (np. 16KB), aby zapobiec rozrostowi wpisow blokujacych potok.

Rotuj pliki logow za pomoca logrotate lub wbudowanej rotacji biblioteki, aby zapobiec wyczerpaniu dysku.

Waliduj swoje logi JSONL online

Uzyj naszych darmowych narzedzi przegladarkowych do inspekcji, walidacji i konwersji plikow logow JSONL. Bez przesylania, cale przetwarzanie odbywa sie lokalnie.

Przegladaj logi JSONL w przegladarce

Przegladaj, wyszukuj i waliduj pliki logow JSONL do 1GB. Natychmiastowe ladowanie, przetwarzanie po stronie klienta, 100% prywatnosci.

Czesto zadawane pytania

Logowanie JSONL — logi strukturalne, ELK/Fluentd i ingest...