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
Zalecanystructlog 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.
import structlog# Konfiguracja structlog dla wyjscia JSONLstructlog.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 JSONLlog.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
Najszybszypino 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.
import pino from 'pino';// pino domyslnie generuje wyjscie JSONLconst log = pino({level: 'info',timestamp: pino.stdTimeFunctions.isoTime,});// Kazde wywolanie generuje jedna linie JSONLlog.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 kontekstconst reqLog = log.child({ requestId: 'req-abc-123' });reqLog.info({ path: '/api/users' }, 'request.start');
Go: zerolog
Zero alokacjizerolog 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.
package mainimport ("os""github.com/rs/zerolog""github.com/rs/zerolog/log")func main() {// Konfiguracja wyjscia JSONL do stdoutzerolog.TimeFieldFormat = zerolog.TimeFormatUnixlog.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()// Kazde wywolanie generuje jedna linie JSONLlog.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.
# 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}"# 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.
# logstash.confinput {beats {port => 5044codec => json}}filter {# Parsuj znacznik czasu z pola JSONdate {match => ["timestamp", "ISO8601"]target => "@timestamp"}# Dodaj tag srodowiskamutate {add_field => { "environment" => "production" }remove_field => ["agent", "ecs", "host"]}# Kieruj wedlug poziomu loguif [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.
[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 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.
# 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>
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
AWSCloudWatch 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.
# Przyklady zapytan CloudWatch Logs Insights# Znajdz wszystkie bledy z ostatniej godzinyfields @timestamp, level, event, user_id| filter level = "error"| sort @timestamp desc| limit 100# Agreguj liczbe bledow wedlug typu zdarzeniafields event| filter level = "error"| stats count(*) as error_count by event| sort error_count desc# P99 opoznienia wedlug endpointufields 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 (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.
# Przyklad Python dla Cloud Run / Cloud Functionsimport jsonimport sysdef 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 logilog("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
AzureAzure 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.
// Zapytania KQL dla logow JSONL w Azure Monitor// Znajdz ostatnie bledyAppTraces| 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 bledowAppTraces| 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.
{"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.
// 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.