Jak efektywnie monitorować aplikacje Java w środowisku produkcyjnym za pomocą narzędzi open source

0
5
Rate this post

Nawigacja po artykule:

Po co w ogóle monitorować Javę w produkcji i dlaczego dopiero tam wychodzą demony

„Działa u mnie” kontra „działa na produkcji”

Aplikacja Java potrafi śmigać idealnie na laptopie developera, a po wdrożeniu na produkcję zamienia się w generator ticketów i nieprzespanych nocy. Różnica tkwi nie tylko w konfiguracji, ale przede wszystkim w realnym ruchu, danych i warunkach sieciowych. Na środowisku lokalnym zwykle nie ma:

  • tysięcy równoległych requestów,
  • dziwnych, „brudnych” danych z legacy systemów,
  • opóźnień między regionami chmury,
  • nagłych skoków ruchu po kampanii marketingowej.

Dopiero pod obciążeniem wychodzą na jaw problemy z blokującą bazą, nieoptymalnym GC czy źle dobraną pulą wątków. Bez porządnego monitoringu produkcja staje się rosyjską ruletką: może się uda, a może w piątek o 22:30 ktoś zada niewygodne pytanie „czemu nie działa koszyk?”.

Główne cele monitoringu: szybko wykryć, szybko naprawić, najlepiej zapobiec

Monitoring aplikacji Java w środowisku produkcyjnym nie jest celem samym w sobie. Ma trzy bardzo przyziemne, ale kluczowe zadania:

  • Wczesne wykrycie problemów – zanim zadzwoni klient lub biznes, system alertów powinien zauważyć rosnące błędy, opóźnienia czy zużycie pamięci.
  • Skrócenie MTTR (Mean Time To Repair) – gdy już coś wybuchło, liczy się każda minuta. Dane z metryk, logów i trace’ów pozwalają przejść od „dziwnie wolno działa” do „konkretny endpoint dławi się na zapytaniu do tabeli X”.
  • Prewencja – obserwacja trendów (np. stopniowe zjadanie heap przez lekkie memory leak) daje czas na reakcję zanim dojdzie do awarii.

Bez tych informacji zespół supportu i developerzy skazani są na zgadywanie: „może baza”, „może sieć”, „może GC”, „a może użytkownik źle kliknął”. To ani profesjonalne, ani skuteczne. Monitoring daje wspólny, obiektywny język do rozmowy między dev, ops i biznesem.

Specyfika JVM: problemy, których nie widać w logach HTTP

Java to nie tylko logi requestów HTTP i stack trace z wyjątkiem. Pod spodem działa JVM z własną maszyną pamięci, GC i schedulerem wątków. Typowe produkcyjne „demony”, których nie widać w zwykłych logach aplikacyjnych, to m.in.:

  • zbyt długa lub zbyt częsta praca Garbage Collectora,
  • przepełniony heap albo metaspace,
  • blokady wątków, deadlocki, nadmiar wątków w pulach,
  • nadmierne ładowanie klas (ClassLoading) w dynamicznych środowiskach.

Log HTTP 500 mówi tylko „coś poszło nie tak”. Bez metryk JVM i profilowania pozostaje ręczne odpalanie narzędzi typu jstack, jmap, jcmd na żywym organizmie. A to przypomina operację kardiochirurgiczną wykonywaną przez okno – coś widać, ale komfortu brak.

Anegdota o piątkowych awariach

W wielu firmach krąży podobna historia: system e‑commerce pada regularnie raz w miesiącu, zawsze w piątek wieczorem. Restart pomaga, nikt nie wie, czemu. Po kilku takich epizodach ktoś w końcu podłącza sensowny monitoring: metryki GC, heap, ilość wątków, liczniki requestów. Okazuje się, że raz w miesiącu import gigantycznego katalogu produktów powoduje skokowe zużycie pamięci i lawinę pełnych GC, aż JVM przestaje nadążać z obsługą ruchu. Bez danych to „magia”, z danymi – konkretna akcja: zmiana konfiguracji joba, limitowanie batchy, korekta ustawień GC.

Monitoring nie naprawi aplikacji sam z siebie, ale pokaże, gdzie boli. I to często na tyle wcześnie, że poniedziałkowy stand‑up nie zaczyna się od burzy mózgów „kto jest winny?”.

Co naprawdę trzeba monitorować w aplikacjach Java – warstwy i priorytety

Trzy filary obserwowalności: metryki, logi, trace’y

Efektywne monitorowanie aplikacji Java opiera się o trzy uzupełniające się filary:

  • Metryki – liczniki, histogramy, gaug’e opisujące ilościowo stan systemu: czas odpowiedzi, liczba requestów, wykorzystanie pamięci, liczba błędów.
  • Logi – szczegółowy, tekstowy zapis zdarzeń z kontekstem: parametry, identyfikatory, stack trace’y.
  • Trace’y (tracing rozproszony) – ślad podróży jednego requestu przez wiele usług, baz danych, kolejki, z dokładnymi czasami poszczególnych kroków.

Metryki są świetne do alertowania i oglądania trendów. Logi pomagają w dokładnym dochodzeniu „co się wydarzyło”. Trace’y z kolei pokazują gdzie w łańcuchu zależności ginie czas. Brak któregokolwiek z tych filarów ogranicza możliwości diagnozy, ale nadmiar źle dobranych danych też potrafi sparaliżować.

Perspektywy: JVM, aplikacja, infrastruktura

Porządny monitoring aplikacji Java w produkcji musi agregować dane z trzech perspektyw naraz:

  • JVM: Garbage Collection (częstotliwość, czas pauz), heap, metaspace, liczba wątków, liczba klas, wykorzystanie thread pooli.
  • Aplikacja: czasy odpowiedzi endpointów, throughput (RPS), procent błędów (HTTP 4xx/5xx, wyjątki biznesowe), kolejki wewnętrzne, długość batchy, czasy zapytań do bazy.
  • Infrastruktura: CPU, pamięć fizyczna, I/O dysku, I/O sieciowe, limity kontenerowe (cgroups), stan load balancerów.

Przykład: rosnące opóźnienia API mogą być wynikiem:

  • zbyt powolnego GC (warstwa JVM),
  • blokującego wywołania do zewnętrznego serwisu (warstwa aplikacji),
  • nasycenia CPU na nodzie Kubernetes (warstwa infrastruktury).

Dopiero łączenie tych perspektyw pozwala szybko wykluczać hipotezy i kierować się w stronę prawdziwej przyczyny.

Złota czwórka sygnałów dla aplikacji Java

Środowiska SRE i DevOps często mówią o „Four Golden Signals”. W kontekście aplikacji Java w produkcji można je zdefiniować następująco:

  • Latency – czas odpowiedzi requestów (p95, p99 ważniejsze niż średnia).
  • Throughput – ile requestów, komunikatów z kolejki czy jobów obsługuje system w jednostce czasu.
  • Errors – odsetek odpowiedzi z błędami (HTTP 5xx, nieobsłużone wyjątki), ale też specyficzne błędy biznesowe.
  • Saturation – stopień nasycenia zasobów: CPU, heap, wątki, kolejki, połączenia do bazy.

Jeśli monitoring w sposób ciągły prezentuje te cztery sygnały dla najważniejszych usług, większość krytycznych incydentów da się wykryć i zdiagnozować znacznie szybciej. Reszta metryk (cache hit ratio, rozkład statusów HTTP, itp.) już „tylko” doprecyzowuje obraz.

W tym miejscu przyda się jeszcze jeden praktyczny punkt odniesienia: Zarządzanie procesami JVM w Linuxie – ps, top, jps, jstack w akcji.

Must‑have kontra nice‑to‑have – jak nie utonąć w danych

Najczęstszy błąd przy wdrażaniu monitoringu aplikacji Java w produkcji to chęć mierzenia wszystkiego. Kończy się na dziesiątkach dashboardów, których nikt nie otwiera i na setkach alertów, które lądują w spamie. Zamiast tego lepiej podejść iteracyjnie:

  • na start: metryki „złotej czwórki” + podstawowe metryki JVM,
  • w drugim kroku: specyficzne metryki biznesowe (np. liczba zamówień na minutę, skuteczność logowania),
  • na końcu: detale typu rozbicie według regionów, typów klientów, AB‑testów.

Monolit Spring Boot kontra mikroserwisy – różne priorytety

W monolicie Spring Boot monitoring koncentruje się zwykle na:

  • performancie głównych endpointów HTTP,
  • czasie zapytań do bazy danych,
  • GC, heap, wątkach, otwartych połączeniach,
  • kolejkach wewnętrznych (np. asynchroniczne eventy).

W świecie mikroserwisów dochodzą dodatkowe wyzwania:

  • łańcuchy zależności między usługami (A woła B, B woła C, C woła zewnętrzny system),
  • problemy sieciowe (time‑outy, circuit breakery, retry’e),
  • monitorowanie komunikacji asynchronicznej (Kafka, RabbitMQ),
  • korelacja requestów między serwisami (traceId, baggage w OpenTelemetry).

Monolit można monitorować jeszcze „po staremu”, z samymi metrykami i logami. Przy mikroserwisach tracing rozproszony staje się właściwie obowiązkowy, jeśli celem jest realna obserwowalność, a nie tylko statystyki „ile 500‑tek dziennie”.

Krajobraz narzędzi open source do monitoringu Javy – co jest czym i jak się łączy

Mapa narzędzi: metryki, logi, trace’y, APM

Świat open source oferuje dzisiaj bardzo bogaty wybór narzędzi do monitoringu aplikacji Java. Najważniejsze klocki to:

  • Prometheus – system zbierania metryk w modelu pull (scraping endpointów ekspozycji metryk, np. /actuator/prometheus).
  • Grafana – wizualizacja metryk, budowanie dashboardów, alerting, integracja z wieloma źródłami danych.
  • Loki – system logów zoptymalizowany pod zapytania w stylu PromQL (LogQL), wygodny do prostych, skalowalnych rozwiązań.
  • Elasticsearch / OpenSearch – silnik wyszukiwania i analizy logów (często z Kibana lub OpenSearch Dashboards jako interfejsem).
  • Jaeger / Tempo – systemy do przechowywania i wizualizacji trace’y (OpenTracing/OpenTelemetry).
  • OpenTelemetry – standard instrumentacji metryk, logów i trace’y, z bibliotekami dla Javy i wielu innych języków.
  • Java Flight Recorder (JFR) – wbudowany w JVM mechanizm nagrywania szczegółowych zdarzeń (low‑overhead profiler).
  • Glowroot, Pinpoint, Apache SkyWalking – open source’owe APM-y dedykowane dla JVM i mikroserwisów.

Te komponenty można łączyć w różne konfiguracje – od bardzo prostych, po pełne „observability platform” z metrykami, logami, trace’ami i profilowaniem.

Monitoring a APM – gdzie kończą się metryki, a zaczyna „magia”

Klasyczny monitoring opiera się głównie na metrykach i logach: liczysz requesty, mierzysz czas, przeszukujesz logi, ustawiasz alerty. APM (Application Performance Monitoring/Management) idzie krok dalej:

  • automatyczna instrumentacja kodu (bez modyfikacji źródeł),
  • transakcyjne ścieżki requestów (podobnie do tracingu),
  • mapy zależności usług,
  • profilowanie zapytań do bazy, cache, zewnętrznych usług,
  • często gotowe analizy i sugestie „bottle‑necków”.

Komercyjne APM‑y (New Relic, AppDynamics, Datadog) są wygodne, ale kosztowne. Open source’owe odpowiedniki (Glowroot, Pinpoint, SkyWalking) dają podobne możliwości, za to wymagają więcej wysiłku przy instalacji i utrzymaniu. W wielu projektach rozsądny kompromis to połączenie Prometheus + Grafana + OpenTelemetry + Jaeger, a profilowanie robić za pomocą JFR i dodatkowych narzędzi w razie potrzeby.

Typowe stacki open source dla Javy

Kilka często spotykanych kombinacji:

CelMetrykiLogiTrace’yUwagi
Prosty monitoring małego systemuPrometheusLokiBrak / Jaeger basicMinimum, ale już daje sensowny obraz
System o większej skali + analizy logówPrometheusElasticsearch / OpenSearchJaeger lub TempoKlasyczny zestaw: Prometheus + ELK + Jaeger
Mikroserwisy z naciskiem na tracingPrometheus (przez OTel)Loki / OpenSearchJaeger / Tempo (OpenTelemetry)Silne o

Integracja narzędzi w spójny pipeline

Same narzędzia to dopiero połowa sukcesu. Druga połowa to sensowna integracja: od aplikacji, przez eksport metryk i logów, po system wizualizacji i alertingu. Dobrze ułożony pipeline wygląda zwykle tak:

  1. aplikacja Java wystawia metryki (Micrometer + /actuator/prometheus), generuje logi w formacie strukturalnym i wysyła trace’y (OpenTelemetry),
  2. agent lub sidecar (Fluent Bit, Vector, Filebeat, OTel Collector) zbiera logi/metryki/trace’y z node’a lub kontenera,
  3. Prometheus scrapuje metryki, a logi i trace’y trafiają do wyspecjalizowanych backendów (Loki/Elasticsearch/Tempo/Jaeger),
  4. Grafana łączy dane w jedną warstwę wizualizacji i alertingu.

W mniejszych systemach część z tych kroków można uprościć (np. pomijając kolektory logów na rzecz prostego sidecara), byle nie rezygnować z kluczowych elementów: centralnego zbierania danych i jednego „miejsca prawdy” dla zespołu.

OpenTelemetry jako „klej” między światami

OpenTelemetry pełni dziś rolę swego rodzaju uniwersalnego adaptera. Zamiast pisać integracje osobno dla Prometheusa, Jaegera i kolejnych backendów, instrumentuje się aplikację raz, a eksport danych konfiguruje na poziomie agenta lub kolektora.

W typowym scenariuszu dla Javy:

  • do aplikacji dodawany jest agent OpenTelemetry Java (JAR podpinany przez -javaagent),
  • agent automatycznie instrumentuje popularne biblioteki (Spring, JDBC, gRPC, Kafka, servlet container),
  • trace’y i metryki są wysyłane do OTel Collectora (często w tym samym klastrze Kubernetes),
  • Collector rozsyła dane do właściwych systemów: trace’y do Jaegera/Tempo, metryki do Prometheusa lub bezpośrednio do Grafany/InfluxDB, logi do Loki/Elasticsearch.

Zaletą takiego podejścia jest możliwość zmiany backendu bez ruszania aplikacji. Jeśli dziś system używa Jaegera, a jutro Tempo, wystarczy zmienić konfigurację Collectora. Zespół deweloperski nie musi przy każdej zmianie stacka observability rekompilować usług.

Kobieta z laptopem idzie między lustrzanymi serwerami w data center
Źródło: Pexels | Autor: Christina Morillo

Metryki JVM i aplikacji – jak wycisnąć soki z Micrometer, JMX i Prometheusa

Micrometer jako standard w świecie Spring Boot

Dla aplikacji opartych o Spring Boot praktycznie domyślnym wyborem jest Micrometer. To cienka warstwa abstrakcji nad backendami metryk (Prometheus, Datadog, New Relic, Influx, itp.), która:

  • ekspozycję metryk robi za nas (np. endpoint /actuator/prometheus),
  • daje spójne API dla liczników, mierników i histogramów,
  • automatycznie wystawia metryki JVM i Springa (health, datasource, cache, scheduler).

W praktyce oznacza to, że zamiast myśleć o formacie Prometheusa, programista definiuje metrykę wysoko-poziomowo:


Counter ordersCounter = Counter
  .builder("orders_placed_total")
  .description("Liczba złożonych zamówień")
  .tag("channel", "web")
  .register(meterRegistry);

ordersCounter.increment();

Micrometer zajmuje się resztą: nazwą, etykietami, kompatybilnością z backendem. Przy migracji z Prometheusa na inny system przeważnie wystarczy zmienić zależność i konfigurację, bez dotykania samego kodu biznesowego.

Jakich metryk JVM nie wolno ignorować

JVM generuje dziesiątki metryk, ale kilka z nich ma szczególne znaczenie podczas inspekcji problemów produkcyjnych. W panelu „JVM” w Grafanie zwykle lądują:

  • GC pause time (sumaryczny i percentyle) – długie pauzy to główny winowajca „przywieszek”,
  • allocation rate (ile bajtów/s trafia na heap) – przyspieszony przyrost alokacji zwykle prowadzi do częstego GC,
  • heap usage per pool (eden, survivor, old) – pozwala wychwycić np. stałe „pełzanie” old gen,
  • metaspace usage – wycieki klas/pakietów (np. przy dynamicznym ładowaniu),
  • thread count (daemon/non-daemon) – gwałtowny wzrost liczby wątków bywa sygnałem wycieków lub błędnej konfiguracji pooli,
  • file descriptors / open sockets – limit FD w systemie lub kontenerze potrafi wywrócić całą Jave do góry nogami.

Większość z nich jest dostępna od ręki przez Micrometer (jvm.gc.*, jvm.memory.*, jvm.threads.*). W starszych lub „gołych” aplikacjach można je wyciągnąć bezpośrednio przez JMX, korzystając np. z Prometheus JMX Exporter podpiętego jako agent.

Własne metryki biznesowe – co mierzyć poza GC

Same cykle GC nie powiedzą, czy klienci są w stanie zrobić zakupy. Potrzebne są metryki, które opisują sens systemu. Kilka typowych przykładów:

  • liczba złożonych zamówień,
  • liczba błędnych logowań,
  • liczba uruchomionych procesów batchowych / jobów,
  • liczność elementów w wewnętrznych kolejkach (np. BlockingQueue),
  • czas realizacji kluczowych scenariuszy biznesowych (np. „od kliknięcia kup do płatności zaakceptowanej”).

Jeśli te metryki są wpięte w alerting, zespół dowiaduje się o problemie zanim dzwoni dział sprzedaży. Zdarzało się, że GC był w porządku, CPU zajęte w 30%, a aplikacja od godziny nie przetwarzała żadnego zamówienia, bo zakleszczył się jeden z procesów workflow – było to widoczne wyłącznie po „spłaszczonej” metryce liczby zamówień na minutę.

Histogramy, percentyle i pułapka średniej

Prometheus + Micrometer umożliwiają rejestrowanie histogramów i quantyli. Dobrze skonfigurowane histogramy są kluczowe dla sensownego alertingu:

  • średnia latencji najczęściej kłamie – wystarczy kilka bardzo wolnych requestów i wynik przestaje coś znaczyć,
  • prawdziwy obraz dają p95, p99, a czasem nawet p99.9 (szczególnie w systemach B2B i finansowych),
  • w Micrometer trzeba świadomie dobrać buckets histogramów, tak by pasowały do spodziewanego zakresu (nie ma sensu mierzyć do 10 minut, jeśli SLA to 300 ms).

Przykładowa konfiguracja histogramu w Micrometerze może wyglądać tak:


Timer.builder("http_server_requests_duration")
  .publishPercentiles(0.5, 0.95, 0.99)
  .publishPercentileHistogram()
  .maximumExpectedValue(Duration.ofSeconds(5))
  .register(meterRegistry);

Po wygenerowaniu odpowiedniej ilości danych można w Grafanie zbudować wykresy percentyli i na ich podstawie ustawiać alerty (np. „p99 > 2s przez 5 minut” zamiast „średnia > 500 ms”). To robi różnicę między alertami, które przeszkadzają, a takimi, które naprawdę sygnalizują problem.

JMX i Prometheus – gdy nie ma Spring Boota

Nie każda aplikacja Java żyje w wygodnym świecie Spring Boot + Actuator. W starszych serwisach, serwerach aplikacyjnych czy własnych frameworkach często jedynym interfejsem jest JMX. W takim przypadku sensowną drogą jest:

  1. uruchomienie procesu z agentem jmx_exporter (JAR od Prometheusa),
  2. skonfigurowanie mapowania MBeanów na nazwy metryk Prometheusa w pliku YAML,
  3. udostępnienie endpointu HTTP z metrykami (np. na porcie 9404),
  4. dodanie joba w Prometheusie, który scrapuje endpoint agenta.

Mapowanie MBeanów na metryki bywa na początku żmudne, ale robi się to raz. Zestaw predefiniowanych konfiguracji dla Tomcata, Jetty, Kafka czy Cassandra często da się znaleźć w repozytoriach społeczności. Nikt nie powiedział, że trzeba zaczynać od zera.

Logi, które pomagają, a nie hałasują – od struktury do centralizacji

Strukturalne logi zamiast „ściany tekstu”

Klasyczny stacktrace w konsoli to za mało, gdy logów są gigabajty. Analityka logów w Elasticsearchu, Lokim czy OpenSearchu działa naprawdę dobrze dopiero wtedy, gdy wpis ma sensowną strukturę. W praktyce sprowadza się to do:

  • logowania w formacie JSON (lub bardzo konsekwentnym key=value),
  • dodawania do każdego loga podstawowych pól: timestamp, level, service, environment, traceId, spanId, thread,
  • unikania „logów zlepionych” z kilku wartości bez separatorów,
  • rozbijania długich struktur (np. payloadów JSON) na osobne pola zamiast wklejania całości jako stringa.

Dzięki temu zapytania typu „pokaż wszystkie ERROR-y z serwisu payment-service z traceId = X” stają się trywialne. Bez struktury wyszukiwanie „co się właściwie wydarzyło” przypomina czytanie logów z serwera IRC sprzed dekady.

Poziomy logowania i higiena komunikatów

Najskuteczniejszym sposobem na zabicie wydajności systemu logów jest spamowanie INFO i DEBUG przy każdym obrocie pętli. W aplikacjach produkcyjnych kilka zasad naprawdę się sprawdza:

  • ERROR – sytuacje, które wymagają reakcji człowieka (lub przynajmniej incydentu),
  • WARN – anomalie, które nie wywalają funkcjonalności, ale mogą się w przyszłości zemścić,
  • INFO – kluczowe zdarzenia biznesowe i techniczne (start/stop, główne kroki procesów),
  • DEBUG – szczegóły użyteczne podczas diagnostyki, domyślnie wyłączone w produkcji,
  • TRACE – dokładne śledzenie przepływu, używane tylko ad hoc.

Przy dobrze dobranych poziomach można w czasie incydentu włączyć DEBUG dla jednego pakietu lub klasy, bez zalewania całego systemu logami. W Spring Boot konfigurację robi się wprost w application-prod.yml:


logging:
  level:
    root: INFO
    com.example.payment: DEBUG

Jedno polecenie zmiany ConfigMapy w Kubernetesie i można śledzić konkretny fragment systemu znacznie dokładniej, nie podnosząc globalnego poziomu logowania do szalonego DEBUG.

Centralizacja logów – od sidecara do DaemonSetu

Logi, których nie da się przeszukać centralnie, istnieją głównie po to, żeby zajmować miejsce na dysku. Dlatego tak szybko pojawia się pytanie „jak je zbierać”:

Dzięki temu dashboardy odpowiadają na prawdziwe pytania, a nie są tylko ścianą kolorowych wykresów „bo wszyscy tak mają”. Jeśli brakuje inspiracji, blogi takie jak praktyczne wskazówki: programowanie często pokazują realne przypadki użycia metryk w projektach Java.

  • w prostych instalacjach – kontener logujący na stdout, a warstwa orkiestracji (Docker, Kubernetes) przekierowuje logi do pliku,
  • sidecar (np. Fluent Bit) w tym samym Podzie zbiera logi z /var/log/containers i wysyła do Loki/Elasticsearch,
  • w większych klastrach – DaemonSet z Fluent Bit/Vector na każdym node’cie, który sczytuje logi wszystkich kontenerów.

W wielu firmach migracja wygląda etapami: najpierw centralny Elasticsearch + Filebeat, potem wymiana Beatów na Fluent Bit lub Vector, na końcu pojawia się dodatkowo Loki, bo „nie każdy log musi trafiać do ciężkiego ES-a”. Chodzi o to, żeby kolekcja logów nie była jednorazowym projektem, ale elementem, który można rozwijać w miarę potrzeb.

Korelacja logów z metrykami i trace’ami

Największa moc logów ujawnia się, gdy da się je powiązać z metrykami i trace’ami. Przykładowy scenariusz:

  1. w Grafanie widać skok błędów HTTP 500 w serwisie orders-service,
  2. kliknięcie w konkretny punkt na wykresie przenosi do panelu z trace’ami,
  3. w ramach jednego trace’a dostępne są logi zewnętrznych wywołań (np. do systemu płatności), powiązane przez traceId / spanId,
  4. z pojedynczego loga można przejść do szerszego kontekstu – wszystkich błędów w tym trace’u lub wokół tej samej sekundy.

Do tego potrzebne są spójne identyfikatory (traceId, spanId) propagowane w logach i nagłówkach HTTP. Popularne biblioteki loggingu dla Springa (np. spring-cloud-sleuth w starszych wersjach, obecnie mechanizmy OTel) potrafią dokleić te pola do MDC, a następnie do logów. Bez tego korelacja danych kończy się ręcznym kopiowaniem fragmentów stacktrace’ów między narzędziami – a w produkcji mało kto ma na to cierpliwość.

Redakcja logów – dane wrażliwe i RODO w praktyce

W produkcji log nie jest już tylko „wiadomością dla programisty”, ale pełnoprawnym dokumentem, który może zawierać dane osobowe, numery kart czy tokeny. Zanim trafi do scentralizowanego systemu, trzeba go potraktować jak dane produkcyjne, a nie jak notatki na marginesie zeszytu.

Kilka prostych zasad oszczędza później nerwów prawnikom i zespołowi bezpieczeństwa:

  • nie logować pełnych numerów kart, PESEL-i, haseł, tokenów (nawet „na chwilę, do debugowania”),
  • maskować wrażliwe fragmenty – np. tylko ostatnie 4 cyfry karty, skrócone identyfikatory,
  • anonimizować identyfikatory użytkowników tam, gdzie nie są potrzebni z imienia i nazwiska,
  • czyścić pola typu Authorization, Set-Cookie, X-Api-Key w request/response loggerach.

W praktyce wygląda to często tak, że logger HTTP (np. Logbook w Javie) dostaje zestaw reguł maskujących:


Logbook logbook = Logbook.builder()
  .sink(new DefaultSink(httpLogWriter, new JsonHttpLogFormatter()))
  .condition(Conditions.requestTo("/api/**"))
  .bodyFilter(BodyFilters.replaceJsonStringProperty("password", "***"))
  .headerFilter((name, value) -> 
      "authorization".equalsIgnoreCase(name) ? "***" : value)
  .build();

Dla przypadków, w których log musi przejść przez kilka środowisk (dev, test, preprod, prod), dochodzi jeszcze kwestia retencji. Dla środowisk niższych zwykle wystarcza kilka dni, dla produkcji – tyle, ile wymuszają procesy biznesowe i compliance. Dłuższa retencja = większa szansa, że w backupie sprzed roku ktoś znajdzie coś, co już dawno nie powinno istnieć.

Ostatnia rzecz: dostęp. Kibana lub Grafana z logami wystawiona „dla całej firmy” bez ról i uprawnień kończy się tym, że logi stają się nowym ERP-em – każdy coś w nich szuka. Dobrze ustawione role (np. tylko wybrane serwisy lub maskowanie wybranych pól po stronie UI) trzymają porządek.

Tracing rozproszony i OpenTelemetry – jak zobaczyć, którędy naprawdę idzie request

Po co trace, skoro mam logi i metryki?

Logi mówią „co się stało”, metryki „jak często i jak bardzo boli”. Trace odpowiada na pytanie „którędy to przeszło i gdzie utknęło”. W mikroserwisach lub systemach z kilkoma backendami bez trace’ów debugowanie incydentu wygląda jak przesłuchanie – każdy serwis „zeznaje” co innego.

Tracing rozproszony pozwala:

  • zobaczyć pełną ścieżkę requestu przez wszystkie serwisy, kolejki i bazy,
  • zmierzyć czas w każdej operacji (spanie) – np. ile trwa wywołanie bazy vs. logiki aplikacyjnej,
  • powiązać trace z logami i metrykami – jednym kliknięciem przejść z błędu HTTP 500 do konkretnego zapytania SQL.

Przy prostym monolicie można jeszcze przeżyć bez tego, przy kilkudziesięciu serwisach – nie bardzo. A jeśli do tego dochodzi asynchroniczność (Kafka, RabbitMQ), trace jest często jedyną spójną „opowieścią” o tym, co faktycznie się wydarzyło.

OpenTelemetry w Javie – auto-instrumentacja vs. biblioteka

OpenTelemetry (OTel) dla Javy daje dwa główne sposoby integracji:

  • auto-instrumentacja – uruchomienie aplikacji z agentem JVM, który „wstrzykuje” tracing do popularnych bibliotek (Spring, JAX-RS, JDBC, Kafka, gRPC),
  • instrumentacja ręczna – użycie API OTel w swoim kodzie, gdzie potrzebne są dodatkowe spany lub atrybuty.

Do startu najszybsza jest auto-instrumentacja. Wystarczy dodać JAR agenta i kilka zmiennych środowiskowych:


java -javaagent:opentelemetry-javaagent.jar 
  -Dotel.service.name=orders-service 
  -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 
  -Dotel.metrics.exporter=none 
  -Dotel.logs.exporter=none 
  -jar app.jar

Po odpaleniu w typowym Spring Boocie pojawią się trace’y HTTP, JDBC oraz wywołań HTTP do innych serwisów (np. przez RestTemplate / WebClient). W wielu projektach to już daje 80% wartości – bez dotykania kodu.

Instrumentacja ręczna przydaje się wtedy, gdy:

  • masz ważne procesy biznesowe, które nie są zwykłymi requestami HTTP,
  • chcesz otagować spany dodatkowymi atrybutami (np. orderId, customerSegment),
  • masz złożone przepływy asynchroniczne, których agent nie ogarnie z automatu.

Tracer tracer = GlobalOpenTelemetry.getTracer("com.example.payment");

Span span = tracer.spanBuilder("payment.authorize")
  .setAttribute("payment.method", "CREDIT_CARD")
  .setAttribute("order.id", orderId)
  .startSpan();

try (Scope scope = span.makeCurrent()) {
  paymentGateway.authorize(order);
  span.setStatus(StatusCode.OK);
} catch (Exception ex) {
  span.recordException(ex);
  span.setStatus(StatusCode.ERROR, ex.getMessage());
  throw ex;
} finally {
  span.end();
}

Propagacja kontekstu – bez tego trace się rozpada

Sam traceId w jednym serwisie niewiele daje, jeśli nie podróżuje razem z requestem. Dlatego OTel stosuje headery propagacji, np. traceparent (W3C). Agent załatwia to za Springa, ale w ręcznych lub starszych integracjach trzeba zadbać o:

  • doklejanie nagłówków trace do wywołań HTTP / gRPC / wiadomości w kolejce,
  • odczytywanie nagłówków po stronie odbiorcy i kontynuowanie trace’a (tworzenie child-spanów),
  • przepychanie kontekstu przez wątki, executory i pulę tasków.

Powszechny błąd: przechwycenie requestu, wrzucenie pracy do ExecutorService i utrata kontekstu trace’a, bo ThreadLocal nie przechodzi „magicznie” między wątkami. OTel ma do tego gotowe wrappery, np. Context.taskWrappingExecutor(executor), które zawijają zadania w kontekst aktualnego trace’a.

OTel Collector i backendy – gdzie to wszystko ląduje

Standardowa ścieżka w świecie open source wygląda mniej więcej tak:

  1. aplikacja Java wysyła trace’y i metryki do OTel Collector (najczęściej OTLP/gRPC),
  2. Collector przyjmuje dane, robi sampling, wzbogacanie, transformacje,
  3. Collector eksportuje trace’y do Jaegera, Tempo lub innego storage’u, a metryki do Prometheusa / OTLP.

Konfiguracja Collectora jest YAML-owa i składa się z pipelines. Przykładowo:


receivers:
  otlp:
    protocols:
      grpc:
      http:

exporters:
  jaeger:
    endpoint: jaeger-collector:14250
    tls:
      insecure: true

processors:
  batch: {}

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger]

Collector daje też wygodne miejsce na reguły samplingowe: np. zachowuj wszystkie trace’y z błędami, a z ruchu poprawnego – tylko co setny. Dzięki temu koszty storage’u nie rosną wykładniczo wraz z obciążeniem systemu.

Wybór backendu trace’ów – Jaeger, Tempo, Zipkin

Do wizualizacji i przechowywania trace’ów najczęściej pojawiają się trzy opcje:

  • Jaeger – klasyk, stabilny, dobrze udokumentowany,
  • Tempo – lekki backend od Grafany, zoptymalizowany pod S3/obiekty, fajnie integruje się z Grafaną,
  • Zipkin – prosty, ale rzadziej wybierany w nowych projektach.

W klastrach Kubernetesa często wygrywa Jaeger lub Tempo, bo łatwo je wpiąć w istniejący stack monitoringowy. Ważne jest, by backend trace’ów dało się:

  • spiąć z Grafaną (jako datasource),
  • spiąć z logami (linki z traceId do wyszukiwania w Loki/ES),
  • skalować horyzontalnie, gdy ruch rośnie.

Ciekawy efekt uboczny wprowadzenia trace’ów: nagle widać, jak bardzo „czatna” jest architektura – ile jest hopów, gdzie robione są niepotrzebne wywołania i które serwisy są tak naprawdę „krytyczne”, choć nikt tak o nich nie myślał.

Na koniec warto zerknąć również na: Chmura hybrydowa a projekty Java – kiedy warto? — to dobre domknięcie tematu.

Głębsze grzebanie w JVM: Java Flight Recorder, JMC i profilery open source

Java Flight Recorder – czarna skrzynka JVM

Java Flight Recorder (JFR) to wbudowany w JVM mechanizm „czarnej skrzynki”: zbiera zdarzenia z GC, JIT, wątków, IO, locków i zdarzeń aplikacyjnych, przy niewielkim narzucie wydajności. Kluczowa zaleta – można go zostawić włączonego w produkcji, zamiast czekać, aż „uda się zreprodukować na devie”.

JFR można uruchomić przy starcie procesu:


java -XX:StartFlightRecording=name=prod,settings=profile,dumponexit=true,filename=recording.jfr 
     -jar app.jar

Albo dynamicznie, przez jcmd:


jcmd <pid> JFR.start name=debug settings=profile delay=10s duration=5m filename=/tmp/debug.jfr

Plik .jfr da się później zaciągnąć na lokalną maszynę i przeanalizować w Java Mission Control (JMC) albo bardziej skryptowo, korzystając z API JFR w Javie.

Java Mission Control – analiza bez zgadywania

Java Mission Control to narzędzie GUI do analizy nagrań JFR. W praktyce ułatwia m.in.:

  • analizę zachowania GC (częstotliwość, pauzy, rozmiary generacji),
  • identyfikację hotspotów CPU (które metody naprawdę zjadają czas),
  • wykrywanie blokad – gdzie wątki wiszą na synchronized czy lockach,
  • podgląd wątków i stacków w danym fragmencie czasu.

Typowy scenariusz produkcyjny: użytkownicy zgłaszają „przywieszki” aplikacji, metryki sugerują sporadyczne skoki czasu odpowiedzi, ale CPU i pamięć wyglądają w miarę normalnie. Nagrany JFR pokazuje np. cykliczne pauzy GC generowane przez konkretny fragment kodu, który produkuje tony krótkotrwałych obiektów albo – równie przyjemnie – długotrwałe locki na jakimś globalnym zasobie.

Profilery open source – async-profiler, perf, Java Flight Recorder w akcji

Do głębszego profilowania w produkcji popularny jest async-profiler. Działa w trybie samplingowym, może profilować CPU, alokacje i blokady, a przy tym ma dużo mniejszy wpływ na aplikację niż stare profilery „stop-the-world”.

Uruchomienie bywa zaskakująco proste (o ile jest dostęp do maszyny):


./profiler.sh -d 60 -e cpu -f /tmp/profile.svg <pid>

Wynikiem jest flame graph w formacie SVG. Wystarczy otworzyć w przeglądarce i już widać, które metody świecą się najszerzej – tam zwykle siedzi główny koszt.

Kiedyś standardem był VisualVM ssh-tunelowany do produkcji. Dziś dużo częściej używa się podejścia „zrób zrzut, ściągnij i analizuj offline” – czy to przez async-profiler, JFR, czy nawet wbudowane w JDK narzędzia (jstack, jmap). Mniej ryzyka, bardziej powtarzalny proces.

Profilowanie alokacji i „gorących” obiektów

Samo CPU nie zawsze mówi prawdę. Aplikacja może cierpieć na intensywne alokacje krótkotrwałych obiektów, co obciąża GC i degraduje throughput. Async-profiler ma tryb alloc, który pokazuje, kto najbardziej „spamuje” heap:


./profiler.sh -d 60 -e alloc -f /tmp/alloc.svg <pid>

Na flame graphie widać ścieżki alokacji i typy obiektów. Typowy przykład z życia: ktoś postanowił logować każdy request/response jako sformatowanego JSON-a, a konwerter generuje przy każdym wywołaniu tony tymczasowych struktur. Przy małym ruchu nie widać różnicy, przy dużym – GC zaczyna dominować czasy CPU.

Bezpieczne debugowanie produkcji – kilka praktyk

Grzebanie w JVM w środowisku produkcyjnym wymaga dyscypliny. Kilka zasad, które ratują skórę, gdy coś pójdzie nie tak:

  • zawsze mierzyć overhead – zacząć od krótkich nagrań (30–60 s), zanim włączy się dłuższe profilowanie,
  • unikać starych agentów, które robią heavy instrumentation (pełne bytecode weave’y z rewritingiem wszystkiego jak leci),
  • oddzielać narzędzia „continuous” (JFR, lekkie metryki) od „ad hoc” (profilery, eksperymentalne agenty),
  • Najczęściej zadawane pytania (FAQ)

    Po co monitorować aplikacje Java na produkcji, skoro „u mnie działa” na środowisku dev?

    Na środowisku deweloperskim aplikacja obsługuje kilku testerów, czyste dane i brak poważnych skoków ruchu. Na produkcji dochodzą tysiące równoległych requestów, „brudne” dane z legacy, opóźnienia sieciowe między regionami i nagłe piki po kampaniach marketingowych. Dopiero tam wychodzą problemy z GC, pulami wątków czy blokującą bazą.

    Bez monitoringu produkcja zamienia się w zgadywankę: „może baza”, „może sieć”, „może GC”. Monitoring daje konkrety: które endpointy zwalniają, jak pracuje JVM, gdzie zjada pamięć i które zasoby są na granicy. To różnica między reagowaniem po telefonie od klienta a wychwyceniem problemu z wyprzedzeniem.

    Jakie metryki JVM i aplikacji Java są absolutnym „must-have” do monitoringu?

    Na start wystarczy dobrze opanować kilka kluczowych obszarów. Po stronie JVM są to: czas i częstotliwość Garbage Collection, wykorzystanie heap i metaspace, liczba wątków oraz podstawowe statystyki dotyczące thread pooli i ładowania klas.

    Po stronie aplikacji monitoruj przede wszystkim: czasy odpowiedzi endpointów (p95, p99), throughput (RPS, liczba jobów na minutę), procent błędów (HTTP 4xx/5xx, wyjątki biznesowe) oraz krytyczne integracje – czasy zapytań do bazy, kolejki, zewnętrzne API. Reszta metryk to już „luksusy”, które można dokładać etapami.

    Co to są „Four Golden Signals” dla aplikacji Java i jak je zastosować w praktyce?

    „Four Golden Signals” to cztery najważniejsze sygnały stanu systemu: latency (opóźnienia), throughput (przepustowość), errors (błędy) i saturation (nasycenie zasobów). W świecie Javy przekłada się to na: p95/p99 czasu odpowiedzi, liczbę requestów lub komunikatów z kolejki, odsetek odpowiedzi z błędami oraz poziom wykorzystania CPU, pamięci heap, wątków i połączeń do bazy.

    W praktyce do każdego kluczowego serwisu ustaw osobny dashboard i alerty oparte wyłącznie na złotej czwórce. Jeśli p95 skacze, saturation rośnie, a errors idą w górę – masz wystarczający sygnał, że trzeba działać, zanim zaczniesz przekopywać się przez bardziej szczegółowe metryki.

    Jakie narzędzia open source sprawdzają się do monitorowania Javy w produkcji?

    Typowy, skuteczny stos open source składa się z trzech elementów: systemu metryk (Prometheus, VictoriaMetrics), warstwy wizualizacji (Grafana) oraz narzędzi do logów i trace’ów (np. Loki lub Elasticsearch + OpenTelemetry/Jaeger/Tempo). Po stronie samej aplikacji dobrze współpracują z tym Micrometer (Spring Boot) lub eksportery JMX.

    Dodatkowo przydają się klasyczne narzędzia diagnostyczne JVM: jps, jstack, jmap, jcmd w połączeniu z ps/top/htop na Linuksie. To taki „zestaw narzędzi awaryjnych”, gdy dashboardy mówią „jest źle”, ale trzeba jeszcze wejść z lupą w konkretny proces.

    Czym różni się monitoring monolitu Spring Boot od monitoringu mikroserwisów Java?

    W monolicie skupiasz się głównie na tym, jak działa jedna duża aplikacja: czasy odpowiedzi kluczowych endpointów, zapytania do bazy, GC, heap, wątki, kolejki wewnętrzne. Zależności są prostsze, więc łatwiej znaleźć winowajcę, gdy coś zwalnia.

    W mikroserwisach obraz komplikuje sieć. Dochodzi tracing rozproszony (śledzenie jednego requestu przez kilka usług), problematyczne retry’e i time‑outy, kolejki (Kafka, RabbitMQ) oraz circuit breakery. Tu bez wprowadzenia traceId i sensownego tracingu rozproszonego diagnozowanie „czemu checkout muli” potrafi trwać dłużej niż sam sprint.

    Jak ustawić alerty, żeby monitoring Javy nie zamienił się w „alert fatigue”?

    Dobrym punktem startu są tylko alerty oparte na złotej czwórce sygnałów i kilku podstawowych metrykach JVM. Przykładowo: p95 czasu odpowiedzi powyżej progu przez określony czas, skok błędów 5xx, długotrwałe wysokie zużycie heap lub CPU, rosnąca liczba wątków bez spadków.

    Resztę metryk traktuj jako materiał do analizy ad‑hoc, a nie generator powiadomień. Alerty biznesowe (np. brak nowych zamówień przez X minut) dodawaj dopiero wtedy, gdy techniczne sygnały są już ogarnięte. I regularnie przeglądaj listę alertów – jeśli zespół odruchowo je ignoruje, to znak, że system jest źle ustawiony, a nie że „tak ma być”.

    Najważniejsze punkty

  • Rzeczywiste problemy aplikacji Java wychodzą dopiero w produkcji, pod prawdziwym ruchem, „brudnymi” danymi i obciążeniem, którego nie da się wiarygodnie zasymulować na laptopie developera.
  • Monitoring ma trzy praktyczne cele: jak najwcześniej wykryć kłopoty, maksymalnie skrócić czas naprawy (MTTR) oraz na podstawie trendów zapobiegać awariom, zanim klient zauważy, że coś się pali.
  • Sama obserwacja logów HTTP i wyjątków nie wystarczy – wiele krytycznych problemów Javy (GC, heap, metaspace, blokady i nadmiar wątków, class loading) dzieje się w JVM i pozostaje niewidoczne bez dedykowanych metryk i profilowania.
  • Bez spójnego monitoringu produkcja zamienia się w zgadywankę „może baza, może sieć, może GC”, a rozmowy między dev, ops i biznesem opierają się na opiniach zamiast na twardych danych – dokładnie tak rodzą się mity o „magicznych” piątkowych awariach.
  • Skuteczna obserwowalność opiera się na trzech filarach: metrykach (alerty, trendy), logach (szczegółowe dochodzenie) oraz trace’ach rozproszonych (gdzie dokładnie ginie czas w łańcuchu usług); brak któregokolwiek z nich mocno utrudnia diagnozę.
  • Monitoring musi łączyć trzy perspektywy naraz: JVM (GC, wątki, heap), aplikacji (czasy odpowiedzi, błędy, zapytania do bazy) i infrastruktury (CPU, pamięć, sieć, limity kontenerów); dopiero zestawienie tych warstw pozwala szybko dojść do prawdziwej przyczyny problemu.
  • Opracowano na podstawie

  • Java Performance: The Definitive Guide. O’Reilly Media (2014) – Praktyki monitorowania JVM, GC, wątków i profilowania w produkcji
  • Java Performance Companion. Addison-Wesley Professional (2016) – Zaawansowane techniki diagnostyki JVM, GC, heap, wątków
  • OpenTelemetry Specification. Cloud Native Computing Foundation – Standard zbierania metryk, logów i trace’y w aplikacjach, w tym Javie
  • Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide. Oracle – Oficjalne zalecenia dot. GC, heap, pauz i diagnostyki JVM
  • The Log: What every software engineer should know about real-time data’s unifying abstraction. LinkedIn (2013) – Rola logów i strumieni zdarzeń w obserwowalności systemów
  • The Art of Monitoring. James Turnbull (2016) – Projektowanie systemów monitoringu, dobór metryk, unikanie szumu alertów
  • Linux Performance and Tuning Guidelines. IBM – Narzędzia ps, top i inne do analizy procesów JVM na Linuksie

Poprzedni artykułLetnie podróże samochodem: jakie zapachy są najmniej męczące na długiej trasie
Marta Jaworski
Marta Jaworski przygotowuje na Saszetkizapachowe.pl inspiracje sezonowe i pomysły DIY na naturalne saszetki oraz mieszanki zapachowe. Łączy doświadczenie w pracy z surowcami roślinnymi z podejściem „najpierw bezpieczeństwo”: opisuje proporcje, czas maceracji, sposoby suszenia i przechowywania, by zapach był stabilny, a składniki nie pleśniały. Każdy przepis testuje w domu na różnych tkaninach i w kilku wariantach intensywności. W poradach podkreśla, kiedy lepiej zrezygnować z olejków eterycznych i jak ograniczać ryzyko alergii.