Witam wszystkich, społeczność JavaRush! Dzisiaj porozmawiamy o logowaniu w Javie:
- Co to jest, dlaczego to jest. W jakich przypadkach lepiej jest używać, w jakich przypadkach nie?
- Jakie rodzaje implementacji rejestrowania występują w Javie i co powinniśmy zrobić z tą różnorodnością?
Poziomy rejestrowania. Porozmawiajmy, czym jest appender i jak go poprawnie skonfigurować.
- Logowanie węzłów i jak je poprawnie skonfigurować, aby wszystko działało tak jak chcemy.
Materiał ten przeznaczony jest dla szerokiego grona odbiorców. Będzie to jasne zarówno dla tych, którzy dopiero zapoznają się z Javą, jak i dla tych, którzy już pracują, ale zorientowali się dopiero w
logger.info(“log something”);
Let's Go!
Dlaczego rejestrowanie jest potrzebne?
Przyjrzyjmy się rzeczywistym przypadkom, w których logowanie rozwiązałoby problem. Oto przykład z mojej pracy. Istnieją punkty aplikacji, które integrują się z innymi usługami. Rejestrowanie tych punktów traktuję
jako „alibi” : jeśli integracja nie zadziała, łatwo będzie ustalić, po której stronie powstał problem. Wskazane jest także rejestrowanie ważnych informacji zapisywanych w bazie danych. Na przykład utworzenie użytkownika administratora. To jest dokładnie to, co dobrze byłoby zarejestrować.
Narzędzia do rejestrowania Java
Do znanych rozwiązań logowania w Javie zaliczają się:
- log4j
- LIPIEC - java.util.logging
- JCL – logowanie w Dżakarcie Commons
- Logowanie
- SLF4J - prosta fasada logowania dla Java
Rzućmy okiem na każdy z nich, a w praktycznej części materiału za podstawę przyjmiemy połączenie Slf4j - log4j . Może się to teraz wydawać dziwne, ale nie martw się: pod koniec artykułu wszystko będzie jasne. |
System.err.println
Początkowo oczywiście był
System.err.println (wyjście zapisu do konsoli). Nadal jest używany do szybkiego uzyskania dziennika podczas debugowania. Oczywiście nie ma co tu mówić o jakichkolwiek ustawieniach, więc po prostu o tym pamiętajmy i przejdźmy dalej.
Log4j
Było to już pełnoprawne rozwiązanie, które powstało na potrzeby deweloperów. Okazało się, że jest to naprawdę ciekawe narzędzie. Rozwiązanie to z różnych powodów nigdy nie trafiło do JDK, co bardzo zdenerwowało całą społeczność. log4j miał opcje konfiguracyjne, dzięki którym rejestrowanie mogło być włączone w pakiecie
com.example.type
i wyłączone w podpakietze
com.example.type.generic
. Umożliwiło to szybkie oddzielenie tego, co należało zarejestrować, od tego, co nie było potrzebne. Należy tutaj zauważyć, że
istnieją dwie wersje log4j: 1.2.x i 2.x.x, które nie są ze sobą kompatybilne . log4j dodał taką koncepcję jak
appender , czyli narzędzie za pomocą którego zapisywane są logi i układ - formatowanie logów. Dzięki temu możesz nagrywać tylko to, czego potrzebujesz i jak tego potrzebujesz. Porozmawiamy więcej o programie dołączającym nieco później.
LIPIEC - java.util.logging
Jedną z kluczowych zalet jest rozwiązanie - JUL znajduje się w pakiecie JDK (Java Development Kit). Niestety, w trakcie jego rozwoju za podstawę przyjęto nie popularny log4j, a rozwiązanie IBM, które miało wpływ na jego rozwój. Właściwie w tej chwili jest JUL, ale nikt z niego nie korzysta. Od „tak sobie”: w JUL poziomy logowania różnią się od tego, co jest w Logback, Log4j, Slf4j, co pogarsza zrozumienie między nimi. Tworzenie rejestratora przebiega mniej więcej podobnie. Aby to zrobić, musisz zaimportować:
java.util.logging.Logger log = java.util.logging.Logger.getLogger(LoggingJul.class.getName());
Nazwa klasy jest przekazywana specjalnie, aby wiedzieć, skąd pochodzi rejestrowanie. Od wersji Java 8 możliwe jest przekazywanie plików
Supplier<String>
. Pomaga to policzyć i utworzyć ciąg tylko w momencie, gdy jest to naprawdę potrzebne, a nie za każdym razem, jak to było wcześniej. Dopiero wraz z wydaniem Java 8 programiści rozwiązali ważne problemy, po czym JUL naprawdę stał się użyteczny. Mianowicie metody z argumentami,
Supplier<String> msgSupplier
jak pokazano poniżej:
public void info(Supplier<String> msgSupplier) {
log(Level.INFO, msgSupplier);
}
JCL – logowanie w Dżakarcie Commons
W związku z tym, że przez długi czas nie było branżowego standardu w logowaniu i był okres, kiedy wiele osób tworzyło swój własny, niestandardowy rejestrator, zdecydowali się wypuścić JCL - wspólne opakowanie, które będzie używane nad innymi. Dlaczego? Kiedy do projektu dodano pewne zależności, mogły one użyć innego rejestratora niż rejestrator w projekcie. Z tego powodu dodano je do projektu przejściowo, co stwarzało poważne problemy przy próbie złożenia tego wszystkiego w jedną całość. Niestety wrapper był bardzo ubogi pod względem funkcjonalnym i nie wprowadzał żadnych dodatków. Prawdopodobnie byłoby wygodnie, gdyby wszyscy używali JCL do wykonywania swojej pracy. Ale w rzeczywistości tak nie było, więc używanie JCL nie jest obecnie dobrym pomysłem.
Logowanie
Jak trudna jest ścieżka open source... Logback został napisany przez tego samego programistę co log4j, aby stworzyć jego następcę. Pomysł był taki sam jak log4j. Różnice polegały na tym, że w logbacku:
- poprawiona wydajność;
- dodano natywną obsługę slf4j;
- Rozszerzono opcję filtrowania.
Domyślnie logback nie wymaga żadnych ustawień i rejestruje wszystkie logi z poziomu DEBUG wzwyż. Jeśli konieczna jest konfiguracja, można to zrobić za pomocą konfiguracji XML:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>app.log</file>
<encoder>
<pattern>%d{HH:mm:ss,SSS} %-5p [%c] - %m%n</pattern>
</encoder>
</appender>
<logger name="org.hibernate.SQL" level="DEBUG" />
<logger name="org.hibernate.type.descriptor.sql" level="TRACE" />
<root level="info">
<appender-ref ref="FILE" />
</root>
</configuration>
SLF4J - prosta fasada logowania dla Java
Około 2006 roku jeden z założycieli log4j opuścił projekt i stworzył slf4j – Simple Logging Facade for Java – opakowanie wokół log4j, JUL, common-loggins i logback. Jak widać postęp doszedł do tego, że na opakowaniu utworzono wrapper... Co więcej, jest on podzielony na dwie części: API, które jest wykorzystywane w aplikacji oraz implementację, która jest dodawana jako oddzielne zależności dla każdego typu rejestrowania. Na przykład,
slf4j-log4j12.jar
,
slf4j-jdk14.jar
. Wystarczy podłączyć odpowiednią implementację i gotowe – cały projekt będzie z nią współpracował. Slf4j obsługuje wszystkie nowe funkcje, takie jak formatowanie ciągów do rejestrowania. Wcześniej był taki problem. Załóżmy, że istnieje wpis w dzienniku:
log.debug("User " + user + " connected from " + request.getRemoteAddr());
W obiekcie
user
następuje niejawna konwersja
user.toString()
wynikająca z łączenia ciągów znaków, co wymaga czasu i spowalnia system. I wszystko jest ok jeśli odbugujemy aplikację. Problemy zaczynają się, jeśli poziom logowania dla tej klasy to INFO i wyższy. Oznacza to, że nie należy zapisywać tego dziennika i nie należy wykonywać łączenia ciągów znaków. Teoretycznie o tym powinna decydować sama biblioteka rejestrująca. Co więcej, okazało się to największym problemem pierwszej wersji log4j. Nie dostarczyli normalnego rozwiązania, ale zasugerowali zrobienie tego w ten sposób:
if (log.isDebugEnabled()) {
log.debug("User " + user + " connected from " + request.getRemoteAddr());
}
Oznacza to, że zamiast jednej linii logowania zasugerowali napisanie 3(!). Logowanie powinno minimalizować zmiany w kodzie, a trzy linijki wyraźnie zaprzeczały ogólnemu podejściu. slf4j nie miał problemów ze zgodnością z JDK i API, więc natychmiast pojawiło się piękne rozwiązanie:
log.debug("User {} connected from {}", user, request.getRemoteAddr());
gdzie
{}
oznaczają wstawki argumentów przekazanych w metodzie. Oznacza to, że pierwszy
{}
odpowiada
user
, drugi
{}
-
request.getRemoteAddr()
. Z tego powodu, tylko jeśli poziom logowania pozwala na logowanie, komunikat ten można połączyć w jeden. Po tym, SJF4J szybko zyskał na popularności i jest obecnie najlepszym rozwiązaniem. Dlatego logowanie rozważymy na przykładzie pakietu
slf4j-log4j12
.
Co należy zarejestrować
Oczywiście nie należy rejestrować wszystkiego. Czasami jest to niepotrzebne, a nawet niebezpieczne. Jeśli np. zastawisz czyjeś dane osobowe i w jakiś sposób wyjdzie to na jaw, pojawią się realne problemy, szczególnie w przypadku projektów skierowanych na Zachód. Ale jest też coś, co jest
obowiązkowe do logowania :
- Początek/koniec aplikacji. Musimy wiedzieć, że aplikacja faktycznie uruchomiła się zgodnie z oczekiwaniami i zakończyła się zgodnie z oczekiwaniami.
- Pytania bezpieczeństwa. Tutaj dobrze byłoby rejestrować próby odgadnięcia hasła, rejestrować logowania ważnych użytkowników itp.
- Niektóre stany aplikacji . Na przykład przejście z jednego stanu do drugiego w procesie biznesowym.
- Kilka informacji do debugowania , z odpowiednim poziomem logowania.
- Niektóre skrypty SQL. Istnieją realne przypadki, gdy jest to potrzebne. Ponownie, umiejętnie dostosowując poziomy, można osiągnąć doskonałe rezultaty.
- Wykonane wątki (Thread) mogą być logowane w przypadkach sprawdzenia poprawności działania.
Popularne błędy w logowaniu
Istnieje wiele niuansów, ale oto kilka typowych błędów:
- Nadmierne rejestrowanie. Nie powinieneś rejestrować każdego kroku, który teoretycznie mógłby być ważny. Istnieje zasada: logi mogą obciążać wydajność o nie więcej niż 10%. W przeciwnym razie wystąpią problemy z wydajnością.
- Logowanie wszystkich danych do jednego pliku. To w pewnym momencie sprawi, że odczyt/zapis będzie bardzo trudny, nie wspominając o ograniczeniach rozmiaru plików w niektórych systemach.
- Używanie nieprawidłowych poziomów rejestrowania. Każdy poziom rejestrowania ma wyraźne granice i należy ich przestrzegać. Jeśli granica jest niejasna, możesz uzgodnić, którego poziomu użyć.
Poziomy rejestrowania
|
|
|
x: Widoczne |
|
|
|
|
FATALNY |
BŁĄD |
OSTRZEGAĆ |
INFORMACJE |
ODPLUSKWIĆ |
NAMIERZAĆ |
WSZYSTKO |
WYŁĄCZONY |
|
|
|
|
|
|
|
FATALNY |
X |
|
|
|
|
|
|
BŁĄD |
X |
X |
|
|
|
|
|
OSTRZEGAĆ |
X |
X |
X |
|
|
|
|
INFORMACJE |
X |
X |
X |
X |
|
|
|
ODPLUSKWIĆ |
X |
X |
X |
X |
X |
|
|
NAMIERZAĆ |
X |
X |
X |
X |
X |
X |
|
WSZYSTKO |
X |
X |
X |
X |
X |
X |
X |
Jakie są poziomy rejestrowania? Aby w jakiś sposób uszeregować kłody, konieczne było nadanie im pewnych oznaczeń i wyróżnień. W tym celu wprowadzono poziomy logowania. Poziom ustawia się w aplikacji. Jeżeli wpis należy do poziomu niższego od wyznaczonego, nie jest wprowadzany do dziennika. Na przykład mamy logi, które służą do debugowania aplikacji. W normalnej pracy produkcyjnej (kiedy aplikacja jest wykorzystywana zgodnie z jej przeznaczeniem) takie logi nie są potrzebne. Dlatego poziom rejestrowania będzie wyższy niż w przypadku debugowania. Przyjrzyjmy się poziomom na przykładzie log4j. Inne rozwiązania, z wyjątkiem JUL, wykorzystują te same poziomy. Oto one w kolejności malejącej:
- WYŁ.: nie są zapisywane żadne logi, wszystkie zostaną zignorowane;
- FATAL: błąd, po którym aplikacja nie będzie mogła już działać i zostanie zatrzymana, np. błąd braku pamięci JVM;
- BŁĄD: Poziom błędu, gdy występują problemy wymagające rozwiązania. Błąd nie powoduje zatrzymania aplikacji jako całości. Inne zapytania mogą działać poprawnie;
- WARN: Wskazuje dzienniki zawierające ostrzeżenie. Wystąpiła nieoczekiwana akcja, mimo to system oparł się i zrealizował żądanie;
- INFO: dziennik rejestrujący ważne akcje w aplikacji. To nie są błędy, to nie są ostrzeżenia, to są oczekiwane działania systemu;
- DEBUG: logi potrzebne do debugowania aplikacji. Aby upewnić się, że system robi dokładnie to, czego się od niego oczekuje, lub opisać działanie systemu: „metoda 1 zaczęła działać”;
- TRACE: dzienniki o niższym priorytecie do debugowania, z najniższym poziomem rejestrowania;
- WSZYSTKIE: poziom, na którym będą zapisywane wszystkie logi z systemu.
Okazuje się, że jeśli w jakimś miejscu aplikacji zostanie włączony poziom logowania INFO, rejestrowane będą wszystkie poziomy, począwszy od INFO, aż do FATAL. Jeśli poziom logowania jest FATAL, rejestrowane będą tylko logi z tym poziomem.
Rejestrowanie i wysyłanie logów: Appender
Rozważymy ten proces na przykładzie log4j: zapewnia on szerokie możliwości nagrywania/wysyłania logów:
- do zapisu do pliku - rozwiązanie DailyRollingFileAppender ;
- do odbioru danych do konsoli aplikacji - ConsoleAppender ;
- do zapisywania logów do bazy danych - JDBCAppender ;
- do sterowania transmisją poprzez TCP/IP - TelnetAppender ;
- aby upewnić się, że rejestrowanie nie wpływa na wydajność - AsyncAppender .
Istnieje kilka innych implementacji: pełną listę można znaleźć
tutaj . Nawiasem mówiąc, jeśli wymagany dodatek nie jest dostępny, nie stanowi to problemu. Możesz napisać własny program dołączający, implementując interfejs
Appender , który akceptuje tylko log4j.
Węzły rejestrujące
Do demonstracji użyjemy interfejsu slf4j i implementacji z log4j. Utworzenie rejestratora jest bardzo proste: należy napisać w klasie o nazwie
MainDemo
, w której będzie wykonywane logowanie:
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MainDemo.class);
Spowoduje to utworzenie dla nas rejestratora. Aby dokonać wpisu do dziennika, można skorzystać z wielu metod, które wskazują, na jakim poziomie będą dokonywane wpisy. Na przykład:
logger.trace("Method 1 started with argument={}", argument);
logger.debug("Database updated with script = {}", script);
logger.info("Application has started on port = {}", port);
logger.warn("Log4j didn't find log4j.properties. Please, provide them");
logger.error("Connection refused to host = {}", host);
Mimo że przekazujemy klasę, ostatecznie zapisywana jest pełna nazwa klasy wraz z pakietami. Dzieje się tak, aby można było następnie podzielić rejestrowanie na węzły i skonfigurować poziom rejestrowania oraz moduł dołączający dla każdego węzła. Przykładowo nazwa klasy:
com.github.romankh3.logginglecture.MainDemo
- utworzono w niej rejestrator. I tak można to podzielić na węzły rejestrujące. Głównym węzłem jest null
RootLogger . Jest to węzeł, który odbiera wszystkie logi całej aplikacji. Resztę można przedstawić w sposób pokazany poniżej:
Appendery konfigurują swoją pracę specjalnie na węzłach logowania. Teraz, na przykładzie
log4j.properties , przyjrzymy się, jak je skonfigurować.
Konfiguracja krok po kroku Log4j.properties
Teraz skonfigurujemy wszystko krok po kroku i zobaczymy, co da się zrobić:
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
Ta linia mówi, że rejestrujemy program dołączający CONSOLE, który korzysta z implementacji org.apache.log4j.ConsoleAppender. Ten program dołączający zapisuje dane do konsoli. Następnie zarejestrujmy kolejny appender, który będzie zapisywał do pliku:
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
Należy pamiętać, że dodatki nadal będą musiały zostać skonfigurowane. Kiedy mamy już zarejestrowane programy dołączające, możemy określić, jaki poziom rejestrowania będzie w węzłach i które programy dołączające będą używane.
log4j.rootLogger=DEBUGOWANIE, KONSOLA, PLIK
- log4j.rootLogger oznacza, że skonfigurujemy główny węzeł, który będzie zawierał wszystkie logi;
- po znaku równości pierwsze słowo wskazuje na jakim poziomie i wyższym będą rejestrowane logi (w naszym przypadku jest to DEBUG);
- następnie po przecinku wskazane są wszystkie dodatki, które zostaną użyte.
Aby skonfigurować konkretny węzeł logowania, należy użyć następującego wpisu:
log4j.logger.com.github.romankh3.logginglecture=TRACE, OWN, CONSOLE
gdzie
log4j.logger.
służy do konfiguracji konkretnego węzła, w naszym przypadku tak.
com.github.romankh3.logginglecture.
Porozmawiajmy teraz o skonfigurowaniu appendera CONSOLE:
# CONSOLE appender customisation
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.threshold=DEBUG
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p] : %c:%L : %m%n
Widzimy tutaj, że możemy ustawić poziom, z którego będzie przetwarzany program dołączający. Rzeczywista sytuacja: komunikat o poziomie info został odebrany przez węzeł logujący i przekazany do przypisanego do niego programu dołączającego, ale program dołączający o poziomie warn i wyższym zaakceptował ten log, ale nic z nim nie zrobił. Następnie musisz zdecydować, jaki szablon będzie w wiadomości. W tym przykładzie używam PatternLayout, ale istnieje wiele rozwiązań. Nie zostaną one ujawnione w tym artykule. Przykład konfiguracji modułu dołączającego FILE:
# File appender customisation
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.File=./target/logging/logging.log
log4j.appender.FILE.MaxFileSize=1MB
log4j.appender.FILE.threshold=DEBUG
log4j.appender.FILE.MaxBackupIndex=2
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=[ %-5p] - %c:%L - %m%n
Tutaj możesz skonfigurować, do którego pliku będą zapisywane logi, jak widać
log4j.appender.FILE.File=./target/logging/logging.log
Nagranie trafia do pliku
logging.log
. Aby uniknąć problemów z rozmiarem pliku, możesz ustawić maksimum: w tym przypadku 1 MB. MaxBackupIndex - mówi, ile będzie takich plików. Jeżeli utworzonych zostanie więcej niż ta liczba, pierwszy plik zostanie usunięty. Aby zobaczyć prawdziwy przykład konfiguracji rejestrowania, możesz przejść do
otwartego repozytorium w serwisie GitHub.
Skonsolidujmy wynik
Spróbuj zrobić wszystko samodzielnie:
- Stwórz własny projekt podobny do tego z powyższego przykładu.
- Jeśli masz wiedzę na temat korzystania z Mavena, skorzystamy z niej; jeśli nie, to tutaj znajduje się link do artykułu opisującego, jak podłączyć bibliotekę.
Podsumujmy to
- Rozmawialiśmy o tym, jakie rozwiązania istnieją w Javie.
- Prawie wszystkie znane biblioteki logujące zostały napisane pod kontrolą jednej osoby :D
- Dowiedzieliśmy się, co należy rejestrować, a co nie.
- Ustaliliśmy poziomy rejestrowania.
- Zapoznaliśmy się z węzłami rejestrującymi.
- Przyjrzeliśmy się, czym jest dodatek i do czego służy.
- Krok po kroku skonfigurowaliśmy plik log4j.proterties.
Dodatkowe materiały
- JavaRush: Rejestrowanie. Rozwiń kulkę stectrasu
- JavaRush: Wykład dotyczący rejestratora
- Habr: Rejestrowanie w Javie. Witaj świecie
- Habr: Logowanie w Javie: historia koszmaru
- Youtube: Kursy Golovach. Logowanie. Część 1 , Część 2 , Część 3 , Część 4
- Log4j: dodatek
- Log4j: układ
Zobacz także inne moje artykuły:
GO TO FULL VERSION