Wyciąganie użytecznych informacji z plików dziennika jest krytyczne dla sukcesu Twojej aplikacji Java. Dane dziennika dadzą Ci cenny wgląd w wydajność, stabilność i użyteczność Twojej aplikacji.
Analiza danych dziennika może wydawać się żmudna, ale nie musi taka być. Istnieje wiele narzędzi do odczytywania, przetwarzania i konsolidowania danych dziennika. Podstawowe narzędzia wiersza poleceń, takie jak grep, uniq i sort mogą łączyć i wydobywać użyteczne informacje z plików dziennika. Bardziej zaawansowane parsery logów, takie jak Logstash lub Fluentd, mogą wyodrębnić kluczowe dane z logów w łatwo przeszukiwalne tokeny. Oparte na chmurze usługi logowania, takie jak SolarWinds® Loggly®, przechowują dane dziennika i oferują zaawansowane możliwości analizy, eliminując potrzebę samodzielnego utrzymywania dzienników.
W tej sekcji omówiono niektóre metody i narzędzia do analizy dzienników w celu ulepszenia aplikacji.
- Znajdowanie najczęstszych wyjątków
- Szukanie wyjątków według typu przy użyciu grep
- Szukanie wyjątków według klas przy użyciu grep
- Używanie rozwiązania do zarządzania dziennikami
- Rozwiązywanie problemów produkcyjnych
- Zbierz informacje o problemie
- Identyfikacja przyczyny problemu
- Resolve the Problem and Prevent It From Recurring
- Więcej narzędzi do debugowania produkcji
- jdb
- OverOps
- BTrace
- Chronon
- jhsdb
- Śledzenie transakcji
- Śledzenie unikalnych identyfikatorów
- Śledzenie wywołań metod
- Metody śledzenia w Log4j
- Metody śledzenia w SLF4J
- Zarządzanie wykorzystaniem pamięci
- Odśmiecanie
- Przecieki pamięci
- Dodatkowe zasoby
- See it. Przeanalizuj to. Inspect it. Rozwiąż to
Znajdowanie najczęstszych wyjątków
Znajdowanie najczęstszych wyjątków może pomóc w określeniu obszarów słabej wydajności aplikacji Java. Większość frameworków logowania rejestruje typ wyjątku, komunikat wyjątku i metodę, w której wystąpił wyjątek. Używając Log4j, dziennik wyjątków będzie wyglądał podobnie do jednego z poniższych.
09:54:44.565 ERROR Log4jTest.MathClass - java.lang.ArithmeticException: / by zero10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: myFile (No such file or directory)10:00:10.157 ERROR Log4jTest.Log4jTest - java.io.FileNotFoundException: newFile (No such file or directory)
Jeśli dane wyjściowe zawierają ślady stosu, zobacz sekcję Parsowanie wielowierszowych śladów stosu w tym przewodniku.
Zacznijmy od prostego przykładu z użyciem popularnego narzędzia GNU grep. Następnie pokażemy, jak narzędzie do zarządzania logami może uczynić to jeszcze łatwiejszym.
Szukanie wyjątków według typu przy użyciu grep
Następujące polecenie uniksowe znajdzie wyjątki, wyodrębni typ wyjątku i policzy liczbę jego wystąpień. grep jest popularnym narzędziem wiersza poleceń, które wykonuje dopasowywanie wzorców, podczas gdy uniq i sort odpowiednio grupują i sortują wyniki:
$ grep -o "\w*Exception" myLog.log | sort -r | uniq -c
W tym przykładzie wyrażenie regularne \w*Exception
pasuje do każdego słowa, które kończy się na „Wyjątek”. Flaga -o
mówi grepowi, aby wypisywał tylko te części wyjścia, które pasują do szukanego łańcucha. sort -r
sortuje wynik w odwrotnej kolejności, a uniq -c
grupuje wyniki i liczy liczbę wystąpień. W rezultacie otrzymujemy liczbę wyjątków według typu.
2 FileNotFoundException1 ArithmeticException
Możemy również użyć grep do przeszukania dziennika w poszukiwaniu poszczególnych przypadków tych wyjątków.
$ grep ArithmeticException myLog.log09:54:44.565 ERROR Log4jTest.Log4jTest - java.lang.ArithmeticException: / by zero
To zwraca każdy wiersz zawierający instancję szukanego łańcucha ArithmeticException
, z samym szukanym łańcuchem wyróżnionym na wyjściu.
Szukanie wyjątków według klas przy użyciu grep
Możesz również szukać wyjątków według klasy, w której wystąpiły. Dla jednowierszowych zdarzeń dziennika, poniższe polecenie grupuje liczbę wyjątków według klasy i typu. grep wyodrębnia nazwę klasy, szukając słowa, po którym następuje określony znak (w tym przykładzie użyto myślnika, choć można użyć dowolnego znaku, o ile jest on unikalny dla danego wpisu). Chociaż znak ten nie jest niezbędny, pomaga nam zlokalizować nazwę klasy w zdarzeniu logu. Dzięki operatorowi OR (oznaczonemu przez |), grep wyodrębni również nazwę wyjątku, szukając słowa zawierającego łańcuch „Wyjątek.”
sed jest kolejnym narzędziem wiersza poleceń, które może być użyte do sformatowania danych wyjściowych z grepa. W tym przykładzie, sed usuwa znak specjalny z naszego wyjścia, jak również wszelkie znaki nowej linii. Wynik jest następnie przesyłany do poleceń uniq i sort, aby odpowiednio pogrupować i posortować wyniki.
$ grep -o "w* -|\w*Exception" myLog.log | sed 'N; s/ -n/ /' | sort -r | uniq -c
Wynik przybliża nas do kluczowych obszarów problemowych:
2 Log4jTest FileNotFoundException1 MathClass ArithmeticException
Mając te wyniki, możemy użyć grepa do znalezienia więcej szczegółów na temat problemów występujących w konkretnych klasach. Na przykład, poniższe polecenie wyszukuje wyjątki, które wystąpiły w klasie MathClass
.
$ grep -e "MathClass.*Exception" myLog.log09:54:44.565 ERROR Log4jtest.MathClass - java.lang.ArithmeticException: / by zero
Używanie rozwiązania do zarządzania dziennikami
Większość rozwiązań do zarządzania dziennikami oferuje sposoby grupowania i wyszukiwania wpisów dziennika na podstawie typu dziennika, treści komunikatu, klasy, metody i wątku. Jeśli logi są już parsowane i przechowywane, wiele rozwiązań może tworzyć wykresy i sortować wyjątki według liczby wystąpień. W Loggly jest to operacja typu „wskaż i kliknij”, więc nie trzeba zapamiętywać skomplikowanych komend grep. Loggly indeksuje również rekordy dziennika, dzięki czemu wyszukiwanie i zliczanie jest znacznie szybsze niż w przypadku grepa. Na przykład, możemy użyć eksploratora pól, aby zobaczyć listę wyjątków i ich częstotliwość, a następnie kliknąć na wyjątek, aby wyświetlić wszystkie odpowiednie logi.
Używanie eksploratora pól Loggly, aby szybko znaleźć logi według typu wyjątku.
Loggly oferuje również narzędzia do wizualizacji, które mogą być bardziej informacyjne niż tekstowe dane wyjściowe z grepa. Wykresy takie jak ten mogą pomóc w ustaleniu priorytetów zasobów dla sprintów rozwojowych lub poprawek po wydaniu nowego wydania.
Wykresy wyjątków według częstotliwości w Loggly.
Rozwiązywanie problemów produkcyjnych
W przypadku problemów produkcyjnych czas jest najważniejszy. Krytyczny błąd w Twojej aplikacji nie tylko sprawi, że użytkownicy będą niezadowoleni, ale także obniży sprzedaż i zmniejszy zaufanie do Twojej aplikacji lub usługi.
W większości przypadków rozwiązywanie problemu można podzielić na trzy kluczowe kroki:
- Zbierz informacje o problemie
- Zidentyfikuj przyczynę problemu
- Znajdź rozwiązanie i zapobiegnij ponownemu wystąpieniu problemu
Zbierz informacje o problemie
Pierwszym krokiem jest zebranie informacji o problemie. Zbierz jak najwięcej informacji – zrzuty ekranu, raporty o awariach, dzienniki, linki (dla usług internetowych) itp. Będziesz chciał, aby użytkownik, który doświadczył problemu, dostarczył szczegółowych informacji o zdarzeniu, w tym: kiedy i gdzie w programie wystąpił problem, jego lub jej działania prowadzące do problemu, środowisko operacyjne oraz wszelkie dziwne lub nietypowe zachowania programu przed i po wystąpieniu problemu.
Z tego miejsca możesz rozpocząć zbieranie informacji z dziennika. Jeśli Twoja aplikacja jest usługą hostowaną, zacznij pobierać dzienniki z serwera WWW i serwera aplikacji. Jeśli Twoja aplikacja jest pakietem oprogramowania rozproszonego, poproś użytkownika o dołączenie danych dziennika do jego lub jej raportu błędu. Alternatywnie, jeśli twoja aplikacja wysyła zdarzenia dziennika do scentralizowanego serwera dziennika, wtedy twoje dzienniki będą natychmiast dostępne.
Gdy masz już rozsądną ilość danych dotyczących problemu, możesz zacząć śledzić go w kodzie.
Identyfikacja przyczyny problemu
Po zebraniu informacji o problemie, następnym krokiem jest identyfikacja jego przyczyny. Reprodukcja błędu w środowisku programistycznym jest jednym z najprostszych sposobów na potwierdzenie jego istnienia, ale może być czasochłonna i może nie działać we wszystkich przypadkach. Posiadanie dokładnego zestawu logów doprowadzi Cię prosto do źródła problemu, oszczędzając czas i frustrację.
Raport błędu da Ci ogólne pojęcie o tym, na czym polega problem i gdzie wystąpił. Używając wybranego narzędzia do zarządzania logami, możesz zawęzić swoje poszukiwania do mniejszego zakresu wpisów w logach poprzez wyszukiwanie unikalnego tokena danych, takiego jak nazwa użytkownika, ID sesji lub tekst wiadomości.
Prześledźmy przykładowy scenariusz, aby zademonstrować, jak debugować system. Wyobraźmy sobie, że mamy interfejs webowy do zdalnego logowania się na stronie internetowej. Strona posiada ekran logowania, który przeprowadza podstawowe uwierzytelnienie przy użyciu nazwy użytkownika i hasła. Użytkownicy zgłaszają, że nie mogą zalogować się na stronę. Strona akceptuje ich dane wejściowe, ale następnie kończy się niepowodzeniem z ogólnym błędem.
Przykładowa strona internetowa zgłaszająca błąd po próbie logowania.
Ten komunikat nie daje nam zbyt wielu informacji poza ogólnym wskazaniem powagi dziennika. Przeszukiwanie menedżera dzienników w poszukiwaniu wpisów z „Severe” na poziomie lub w komunikacie może skutkować setkami wyników bez gwarancji, że którykolwiek z nich jest związany z omawianym problemem. Na szczęście nasz Logger
zapisał również nazwę użytkownika, który doświadczył błędu, więc możemy filtrować po nazwie użytkownika „admin”.
Przeglądanie wyjątków Java w Loggly.
Jeśli przewiercimy się w dół, zobaczymy, że przyczyną błędu jest brakująca lub nieprawidłowa tablica. Jest to poważny problem, ponieważ może wskazywać na przypadkowe usunięcie lub uszkodzenie bazy danych.
ERROR: Exception for user admincom.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'test_schema.users' doesn't exist...com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1651) at TestApplication.Test.doPost(Test.java:33) at javax.servlet.http.HttpServlet.service(HttpServlet.java:646) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at...
Przeglądając ślad stosu, widzimy, że usługa nie powiodła się w linii 33 pliku Test.java. Ta linia składa się z polecenia SQL, które pobiera dane o użytkowniku z tabeli test_schema.users.
rs = stmt.executeQuery("SELECT * FROM test_schema.users WHERE username = " + username + " AND Password = " + password);
Gdy spróbujemy uruchomić to zapytanie w interfejsie SQL, okaże się, że tabela „test_schema.users” nie istnieje. Jednakże, baza danych posiada tabelę „test_schema.user”. W pewnym momencie, programista mógł omyłkowo wpisać błędną nazwę tabeli i wypchnąć poprawiony formularz na produkcję. Teraz wiemy, na czym polega problem i gdzie występuje w naszym kodzie.
Używając SolarWinds Loggly, możesz debugować i znaleźć pierwotną przyczynę problemów, śledząc błędy w stosie aplikacji, w wielu zdarzeniach dziennika, a nawet wskazując odpowiednią linię kodu w GitHubie.
Resolve the Problem and Prevent It From Recurring
Teraz, gdy zidentyfikowaliśmy problem i jego przyczynę, ostatnim krokiem jest jego naprawienie. Nasz przykład logowania był wyolbrzymionym przypadkiem z relatywnie łatwym rozwiązaniem, ale możesz natknąć się na bardziej skomplikowane błędy zakorzenione w różnych obszarach twojej aplikacji. Zanim skoczysz po szybką i brudną poprawkę, dokładnie przemyśl, jak twoja zmiana wpłynie na aplikację. Czy jest to naprawdę najlepsze rozwiązanie problemu? Czy jest możliwe, że twoja zmiana będzie kolidować z innym komponentem? Czy ta poprawka utrudni wprowadzenie nowych poprawek lub funkcji w dalszej części drogi? Czy ta poprawka zapobiegnie również podobnym problemom pojawiającym się później?
Na przykład, co by się stało, gdyby dwóm różnym użytkownikom udało się stworzyć dwa oddzielne konta z tą samą nazwą użytkownika i hasłem? Mógłbyś wymusić unikalną nazwę użytkownika dla wszystkich użytkowników, ale jak wpłynęłoby to na strukturę Twojej bazy danych, Twój kod uwierzytelniający i Twoją istniejącą bazę użytkowników? Możesz dodać nowe unikalne pole, takie jak adres e-mail i uczynić je wymaganym, ale co jeśli niektórzy z Twoich obecnych użytkowników nie podali adresów e-mail? Co jeśli niektóre adresy e-mail są współdzielone przez wiele kont? Jak te nowe reguły wpłyną na rejestrację, wyszukiwanie i strony administracyjne?
Będziesz także chciał rozważyć opóźnienie pomiędzy stworzeniem poprawki a jej wdrożeniem. W środowisku korporacyjnym, Twój kod będzie musiał być przejrzany przez innych programistów, zintegrowany z bazą kodu, zbudowany, przetestowany przez QA, zainscenizowany i może przejść przez kilka kolejnych kroków zanim trafi na produkcję. Twoja firma może mieć specjalne protokoły dla krytycznych lub wrażliwych na czas błędów, których musisz przestrzegać. Kiedy już znajdziesz rozwiązanie, umieść je w swojej dokumentacji, a także wszelkie rozsądne sposoby obejścia problemu. W ten sposób klienci mogą nadal korzystać z produktu, a zespół wsparcia technicznego może zredukować liczbę zduplikowanych zgłoszeń błędów, podczas gdy poprawka trafia na produkcję.
Po wdrożeniu poprawki kontynuuj monitorowanie aplikacji produkcyjnej, aby zweryfikować, czy problem został rozwiązany. Na przykład, po wdrożeniu poprawki dla problemu z bazą danych w powyższym przykładzie, nie powinniśmy już widzieć zdarzeń z „Table 'test_schema.users’ doesn’t exist”. Jeśli błędy będą się powtarzać lub jeśli zaczniemy widzieć nowe błędy, wiemy, że poprawka nie zadziałała. Idealnie byłoby, gdybyśmy zobaczyli wzór taki jak ten pokazany na poniższym zrzucie ekranu, gdzie liczba błędów spada do zera natychmiast po wdrożeniu poprawki.
Wykres liczby logów zawierających błąd w Loggly.
Więcej narzędzi do debugowania produkcji
Pomimo że logowanie jest wypróbowaną i prawdziwą metodą rozwiązywania problemów i debugowania aplikacji, te narzędzia mogą pomóc w uzyskaniu większego wglądu w działanie aplikacji.
jdb
jdb, Java Debugger, jest narzędziem wiersza poleceń do debugowania klas Java. jdb jest łatwy w użyciu i jest dołączony do Java Development Kit. Użycie jdb tworzy nową maszynę wirtualną Java (JVM), pozwalając na debugowanie klasy bez wpływu na inne działające programy. Można również użyć jdb do debugowania działających aplikacji poprzez dodanie następujących parametrów do polecenia java:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n
Gdy JVM startuje, przypisuje numer portu dla przychodzących połączeń jdb. Następnie możesz dołączyć do działającej instancji JVM używając jdb -attach:
$ jdb -attach
Możesz tego użyć, na przykład, do podłączenia debuggera do działającej instancji produkcyjnej. Bądź ostrożny z używaniem punktów przerwania w tym scenariuszu, ponieważ może to wstrzymać aktywny wątek. Może to mieć konsekwencje, jeśli na przykład klient używa aplikacji podczas debugowania. Aby uzyskać więcej informacji, zobacz dokumentację Java na temat jdb.
OverOps
OverOps jest zestawem narzędzi do monitorowania aplikacji, analizowania kodu i wykrywania problemów. W przeciwieństwie do oprogramowania rejestrującego, które opiera się na danych wyjściowych generowanych przez działającą aplikację, OverOps łączy się bezpośrednio z maszyną wirtualną Java, aby odwzorować bazę kodu aplikacji, odczytać zmienne i zarejestrować błędy. Pozwala to na wychwycenie większej ilości błędów i zapisanie większej ilości danych niż nawet ramy logowania aplikacji. OverOps wykorzystuje model oprogramowania jako usługi (SaaS), gdzie metryki są zbierane i przechowywane na serwerach w chmurze OverOps. Można jednak wdrożyć go również lokalnie. W obu przypadkach można przeglądać dane za pomocą interfejsu opartego na przeglądarce internetowej.
BTrace
BTrace to narzędzie do śledzenia, które pozwala monitorować wszystkie aspekty aplikacji, od nazw klas do błędów. BTrace używa podejścia programowania zorientowanego na aspekt, obejmującego użycie adnotacji, które określają gdzie i jak BTrace monitoruje aplikację. Na przykład poniższy skrypt BTrace monitoruje i rejestruje każde wywołanie pakietu javax.swing
.
import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*; @BTrace public class AllMethods { @OnMethod( clazz="/javax\.swing\..*/", method="/.*/" ) public static void m(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { print(Strings.strcat("entered ", probeClass)); println(Strings.strcat(".", probeMethod)); }}
Więcej informacji o BTrace można znaleźć w repozytorium BTrace GitHub i Wiki.
Chronon
Chronon umożliwia przewijanie i odtwarzanie całego przepływu wykonywania aplikacji. Zapisuje każdą indywidualną zmianę dokonaną podczas życia aplikacji, pozwalając odtworzyć stan aplikacji w dowolnym momencie. Nagrania są zapisywane do pliku, co ułatwia przenoszenie historii wykonania z maszyny produkcyjnej na maszynę deweloperską do testowania.
Chronon składa się z Chronon Recording Server, który pozwala na zdalne nagrywanie aplikacji Java; Embedded Chronon, który osadza rejestrator wewnątrz aplikacji; oraz Time Travelling Debugger, który pozwala na odtwarzanie nagrań.
jhsdb
jhsdb (Java HotSpot Debugger) jest zestawem narzędzi do debugowania, analizowania i profilowania domyślnej maszyny JVM dostarczanej zarówno z OpenJDK jak i Oracle JDK. jhsdb umożliwia dołączanie się do uruchomionych procesów Java, robienie zrzutów śladów stosu, a nawet analizowanie uszkodzonych maszyn JVM. Możesz użyć go do uzyskania dostępu do sterty, pamięci podręcznej kodu, statystyk odśmiecania i innych. Aby dowiedzieć się więcej, zobacz stronę dokumentacji jhsdb.
Śledzenie transakcji
Gdy pojawia się problem, ważne jest, aby wiedzieć, gdzie problem się rozpoczął i jak wpłynął na resztę aplikacji. Jest to wystarczająco trudne w monolitycznej aplikacji, ale staje się jeszcze trudniejsze w rozproszonej architekturze zorientowanej na usługi, gdzie pojedyncze żądanie może uderzyć w dziesiątki usług. Nie zawsze jest oczywiste, która usługa zawiera główną przyczynę błędu, lub jak wpłynął on na inne usługi. Śledzenie dostarcza danych potrzebnych do prześledzenia ścieżki wykonania aplikacji i dotarcia do dokładnej przyczyny problemu.
W rozdziale Debugowanie problemów produkcyjnych omówiliśmy przykład, w którym użytkownik miał problemy z zalogowaniem się do aplikacji internetowej z powodu nieprawidłowej tabeli w bazie danych. W tej części pokażemy, jak śledzenie transakcji odegrało główną rolę w rozwiązaniu tego problemu.
Śledzenie unikalnych identyfikatorów
Aby prześledzić sekwencję zdarzeń dziennika, potrzebujesz jakiegoś sposobu na unikalną identyfikację powiązanych dzienników. Środowisko z wieloma użytkownikami może generować setki identycznych dzienników, co utrudnia wyszukiwanie na podstawie znacznika czasu lub Logger
. Łatwiejszym rozwiązaniem jest dołączenie unikalnego identyfikatora do powiązanych wpisów dziennika. Identyfikator ten może być nazwą użytkownika, identyfikatorem sesji, kluczem API lub uniwersalnym identyfikatorem UUID (Universally Unique Identifier). Na szczęście ThreadContext doskonale nadaje się do tego zadania.
W przykładzie Debugowanie problemów produkcyjnych mieliśmy internetowy interfejs użytkownika, który był hostowany w serwlecie Tomcat, który łączył się z bazą danych MySQL. Użytkownicy wprowadzali swoje dane uwierzytelniające na stronie internetowej, a po naciśnięciu przycisku wyślij, serwlet wykonywał zapytanie porównujące ich dane uwierzytelniające z danymi przechowywanymi w bazie danych. Jeśli użytkownik został pomyślnie uwierzytelniony, był przekierowywany na stronę główną. Jeśli wystąpił błąd, szczegóły dotyczące błędu były rejestrowane, a użytkownicy otrzymywali ogólny komunikat.
Byliśmy w stanie rozwiązać ten problem poprzez włączenie nazw użytkowników do komunikatu dziennika. To pozwoliło nam szybko wyszukać zdarzenia dziennika związane z użytkownikiem admin. Opierając się na tym samym przykładzie, możemy użyć ThreadContext.put()
do mapowania nazwy użytkownika na Logger
. Gdy użytkownicy prześlą swoje dane uwierzytelniające, serwlet wykona metodę doPost()
, która doda nazwy użytkowników do ThreadContext:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {ThreadContext.put("username", request.getParameter("username"));logger.info("Entering doPost().");...}
Użycie Log4j z wzorcem %p : %m%n
skutkuje następującym wpisem:
INFO : Entering doPost().
Możemy dodać podobne zdarzenia dla dostępu do bazy danych MySQL, opuszczenia metody doPost i wykonania innych czynności. W ten sposób, jeśli użytkownik wywoła wyjątek, wiemy dokładnie, co robił, gdy wyjątek wystąpił.
Śledzenie wywołań metod
Wiele frameworków logowania zapewnia natywne metody śledzenia ścieżki wykonania aplikacji. Metody te różnią się nieznacznie pomiędzy frameworkami, ale mają ten sam ogólny format.
-
traceEntry()
oznacza początek metody. -
traceExit()
oznacza koniec metody. W przypadku metod, które zwracają obiekt, można jednocześnie zwracać obiekt i rejestrować zdarzenie za pomocąreturn logger.exit(object)
. -
throwing()
zaznacza wyjątek, którego obsługa jest mało prawdopodobna, taki jakRuntimeException
. -
catching()
zaznacza wyjątek, który nie zostanie ponownie rzucony.
Informacje specyficzne dla frameworka na temat tych metod można znaleźć w dokumentacji loggerów Log4j, Logback i java.util.logging
.
Metody śledzenia w Log4j
Jako przykład użycia Log4j, zastąpimy metody Logger.info() w naszym serwlecie metodami śledzenia.
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { logger.entry(); ... logger.exit();}
Zmienimy również Appender's PatternLayout
, aby pokazywała nazwę klasy, metody i numer linii (wzorce konwersji %class
, %M
i %line
).
<PatternLayout pattern="%p %class %M %line: %m%n" />
Metody śledzące logują zdarzenia na poziomie TRACE
, co oznacza, że będziemy musieli zmienić poziom Logger's
z DEBUG
na TRACE
. W przeciwnym razie komunikaty dziennika zostaną stłumione.
<Loggers> <Root level="trace"> <AppenderRef ref="Console"/> </Root></Loggers> TRACE DatabaseApplication.Login doPost 26: entry...TRACE DatabaseApplication.Login doPost 59: exit
Metody śledzące udostępniają również własne Markers
. Na przykład metody Logger.entry()
i Logger.exit()
wyświetlają odpowiednio ENTER
i EXIT
.
ENTER 14:47:41.074 TRACE DatabaseApplication.Login - EnterEXIT 14:47:41.251 TRACE DatabaseApplication.Login - Exit
Metody śledzenia w SLF4J
Użytkownicy SLF4J mogą skorzystać z MDC do śledzenia zdarzeń dziennika. Podobnie jak w przypadku Log4j, wartości MDC mogą być używane z Appenderem przy użyciu wzorca konwersji %X.
SLF4J zapewnia również metody entry()
, exit()
, throwing()
i catching()
poprzez klasę XLogger
(Extended Logger). Możesz utworzyć instancję XLogger
za pomocą XLoggerFactory.getXLogger()
.
package DatabaseApplication;import org.slf4j.Logger;import org.slf4j.LoggerFactory; import org.slf4j.ext.XLogger;import org.slf4j.ext.XLoggerFactory; public class Login extends HTTPServlet { final static XLogger xlogger = XLoggerFactory.getXLogger(Login.class.getName()); final static Logger logger = LoggerFactory.getLogger(Login.class.getName()); @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { xlogger.entry(); .. xlogger.exit(); }}
Dodaj wzorce konwersji %class i %line do logback.xml.
<configuration> <appender name="Console" class="ch.qos.Logback.core.ConsoleAppender"> <encoder> <pattern>%-5level %class{36} %M %L: %msg%xEx%n</pattern> </encoder> </appender> <root level="trace"> ... </root></configuration>
Wynikowe wpisy dziennika są identyczne z tymi tworzonymi przez Log4j.
Zarządzanie wykorzystaniem pamięci
Zarządzanie pamięcią jest często pomijane w językach wyższego poziomu, takich jak Java. Podczas gdy średnia ilość pamięci w nowoczesnych urządzeniach wzrasta, wysokie użycie pamięci może mieć duży wpływ na stabilność i wydajność aplikacji. Gdy maszyna wirtualna Javy nie będzie w stanie dłużej alokować pamięci z systemu operacyjnego, Twój program może zakończyć działanie i ulec awarii. Wiedza o tym, jak zarządzać pamięcią, pozwoli uniknąć problemów w miarę wzrostu rozmiaru aplikacji.
Na przykład wyobraźmy sobie, że chcemy przechowywać zestaw liczb zaczynających się od jeden. Użytkownik podaje górny limit, a program zapisuje każdą liczbę całkowitą od „1” do tego limitu. Użyjemy pętli while z licznikiem, aby dodać każdą liczbę do tablicy.
import java.util.Scanner;...System.out.print("Please enter the maximum size of the array: ");Scanner scanner = new Scanner(System.in);int limit = scanner.nextInt(); ArrayList intArray = new ArrayList(); int count = 1;while (count <= limit) { intArray.add(count);}
Mógłbyś zauważyć, że count
nie inkrementuje się w pętli. Jest to duży problem; jeśli użytkownik wprowadzi dowolną liczbę większą od zera, tablica będzie rosła, aż JVM wykorzysta całą dostępną pamięć i program się zawiesi.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:2245)at java.util.Arrays.copyOf(Arrays.java:2219)at java.util.ArrayList.grow(ArrayList.java:242)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)at java.util.ArrayList.add(ArrayList.java:440)at MemoryTest.main(MemoryTest.java:10)
Odśmiecanie
Aby zmniejszyć zużycie pamięci, wirtualna maszyna Javy wykonuje okresowy proces czyszczenia, znany jako odśmiecanie. Garbage collection wyszukuje bloki pamięci, które nie są już używane, i udostępnia je do ponownego wykorzystania. Poniższe zasoby wyjaśniają proces odśmiecania w Javie w sposób bardziej szczegółowy.
- Java Garbage Collection Basics (Oracle)
- HotSpot Virtual Machine Garbage Collection Tuning Guide (Oracle)
- Understanding G1 GC Logs (Poonam Bajaj)
- Garbage Collection Tuning Guide (Atlassian)
Garbage collector generuje informacje diagnostyczne, które mogą być przydatne do profilowania wydajności aplikacji. Informacje te można rejestrować, przekazując -Xlog:gc do maszyny JVM podczas uruchamiania aplikacji. Po każdym uruchomieniu, garbage collector drukuje statystyki dotyczące procesu odśmiecania w formacie Unified JVM Logging. Oto przykład.
Using G1 Periodic GC disabled GC(0) Pause Young (Normal) (G1 Evacuation Pause) 7M->1M(64M) 8.450ms
Using G1
mówi, która metoda zbierania śmieci jest używana. Kolektor Garbage-First (G1) jest często domyślnie włączony na komputerach wieloprocesorowych z dużą ilością pamięci RAM. Periodic GC disabled
wskazuje, że proces odśmiecania nie będzie powtarzany. Trzecia linia mówi nam, że jest to przerwa ewakuacyjna, w której obiekty są kopiowane między regionami pamięci na podstawie tego, czy są one nadal używane. Pause Young
mówi nam, że proces wyczyścił młodą generację, czyli miejsce, w którym alokowane są nowe obiekty. W rezultacie całkowite użycie pamięci przez obiekty w młodej generacji spadło z 7M do 1M z 64M zaalokowanych, a proces zajął 8.450ms
.
Dzięki temu, że garbage collector drukuje informacje diagnostyczne na konsoli, możemy zobaczyć, jak ilość zaalokowanej pamięci (rozmiar sterty) rośnie w czasie. Dla uproszczenia usuniemy czasy CPU.
$ java -Xlog:gc MemoryLeakDemo Using G1 Periodic GC disabled GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 25M->23M(64M) 4.372ms GC(1) Concurrent Cycle GC(1) Pause Remark 50M->34M(64M) 0.250ms GC(1) Pause Cleanup 94M->94M(124M) 0.057ms GC(1) Concurrent Cycle 169.695ms GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 94M->94M(124M) 3.738ms GC(3) Concurrent Cycle...
Wykres pokazujący stopniowy wzrost wykorzystania sterty JVM w czasie.
Do każdego wpisu można dodać znaczniki czasu za pomocą -XX:+PrintGCDateStamps
i -XX:+PrintGCTimeStamps
. Możesz również rejestrować dane wyjściowe garbage collectora do pliku przekazując-Xlog:gc:file
: do JVM podczas uruchamiania aplikacji.
Jeśli monitorujesz swoje pliki logów za pomocą rsyslog, możesz przekazać je do scentralizowanego systemu logowania, gdzie zostaną przetworzone i przygotowane do analizy w czasie rzeczywistym. Oto jak wygląda wykres osi czasu w Loggly. Pokazuje on całkowity rozmiar sterty, jak również rozmiar sterty przed i po uruchomieniu garbage collectora.
Przecieki pamięci
Przeciek pamięci to sytuacja, w której program alokuje pamięć szybciej, niż może ją zwolnić. Najłatwiejszym sposobem na wykrycie wycieku pamięci jest sytuacja, gdy program przestaje reagować, staje się niestabilny lub powoduje OutOfMemoryErrors
. W Javie znajdziesz więcej przypadków kolekcji Full GC
wraz ze wzrostem użycia pamięci.
Więcej informacji na temat działania garbage collectora i wskazywania wycieków pamięci można znaleźć w rozdziale Troubleshoot Memory Leaks w dokumentacji Javy.
Dodatkowe zasoby
Cut Debugging Time Without Writing a Single Line of Code (Loggly)-Guide to using Takipi
The Ultimate Guide: 5 Methods for Debugging Production Servers at Scale (High Scalability)-Narzędzia i techniki do debugowania problemów produkcyjnych
Memory Management
Garbage Collection Tuning Guide (Oracle)-Poradnik jak zrozumieć i dostroić garbage collection w Javie
Understanding Java Garbage Collection (CUBRID Blog)-Poradnik na temat garb
See it. Przeanalizuj to. Inspect it. Rozwiąż to
Zobacz, co ma znaczenie.
START BEZPŁATNY TRIAL