Przewodnik dla programistów Java

Kluczowym elementem procesu tworzenia oprogramowania jest prawidłowe rejestrowanie zdarzeń. W gąszczu różnorodnych narzędzi do logowania w Javie, wybór odpowiedniego, łatwego w obsłudze rozwiązania jest niezwykle ważny. Jednocześnie wybrana platforma musi cechować się wydajnością, dawać możliwość rozbudowy i personalizacji. Log4j2, darmowa biblioteka do logowania w Javie, spełnia te kryteria.

Zastosowanie Log4j2 w dowolnej aplikacji otwiera drzwi do zaawansowanych opcji, takich jak filtrowanie, wsparcie dla lambda Java 8, wyszukiwanie właściwości oraz niestandardowe poziomy dziennika. Przyjrzyjmy się, jak możesz dodać Log4j2 do swojego projektu i jakie funkcje mogą wspomóc Cię w rozwoju.

Czym jest Log4j2?

Rejestrowanie to nic innego, jak zapisywanie istotnych informacji, nazywanych dziennikami, które można później analizować i wykorzystywać. Dzienniki przydają się do szybkiego debugowania kodu aplikacji, pozwalają zrozumieć przepływ programu oraz pomagają w rozwiązywaniu problemów i błędów występujących w środowisku produkcyjnym.

Oprócz diagnostyki, dzienniki wykorzystywane są również w celach audytowych, na przykład do śledzenia, czy komunikat został wysłany do użytkownika.

Log4j2 jest jedną z najpopularniejszych bibliotek do logowania w Javie. Stanowi ona następcę bardzo popularnej biblioteki Log4j. Stworzony przez Apache Software Foundation i stanowiący część Apache Logging Services, Log4j2 jest oprogramowaniem darmowym i open-source (FOSS), rozpowszechnianym na licencji Apache w wersji 2.0.

Log4j2 bazuje na solidnych fundamentach oryginalnego Log4j. Używanie Loggera jest korzystniejsze niż proste instrukcje `System.out.println()`. Pozwala na kontrolę, które wiadomości mają być wyświetlane, jednocześnie pomijając inne. Dobre logowanie jest kluczowe w środowisku produkcyjnym, gdzie debuggery nie są dostępne.

Jak dodać Log4j2 do swojego projektu?

Istnieje kilka sposobów na dodanie Log4j2 do projektu Java. Zaleca się korzystanie z Javy 8 lub nowszej, aby móc w pełni wykorzystać możliwości Log4j2.

Omówimy różne metody dodawania Log4j2, w zależności od Twoich potrzeb.

Dodawanie Log4j2 do projektów przy użyciu Apache Maven

Jeśli Twój projekt korzysta z systemu kompilacji Apache Maven, zależności Log4j2 należy dodać do pliku `pom.xml`.

<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.20.0</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.20.0</version>
  </dependency>
</dependencies>

Aby ułatwić utrzymanie spójności wersji w różnych elementach, Log4j2 udostępnia plik `pom.xml` w postaci BOM (Bill of Materials). Dodając go w ramach zarządzania zależnościami, unikniesz konieczności określania wersji poszczególnych artefaktów.

<!-- Add the BOM to the dependencyManagement -->
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-bom</artifactId>
      <version>2.20.0</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

<!-- Once the BOM is added, the versions are not required -->
<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
  </dependency>
</dependencies>

Dodawanie Log4j2 do projektów przy użyciu Apache Gradle

Jeśli w Twoim projekcie używane jest narzędzie do budowania Apache Gradle, zależności Log4j2 dodaj do pliku `build.gradle`.

dependencies {
  implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
  implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
}

Używając Gradle w wersji 5.0 lub nowszej, możesz zaimportować zestawienie materiałów (BOM) Log4j2 Maven, co pozwoli Ci zachować spójne wersje zależności. Możesz to osiągnąć, dodając poniższy fragment do pliku `build.gradle`.

dependencies {
  implementation platform('org.apache.logging.log4j:log4j-bom:2.20.0')

  implementation 'org.apache.logging.log4j:log4j-api'
  runtimeOnly 'org.apache.logging.log4j:log4j-core'
}

W wersjach Gradle od 2.8 do 4.10, nie ma możliwości bezpośredniego importu BOM Maven. Należy skorzystać z dodatkowej wtyczki do zarządzania zależnościami.

plugins {
  id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

dependencyManagement {
  imports {
    mavenBom 'org.apache.logging.log4j:log4j-bom:2.20.0'
  }
}

dependencies {
  implementation 'org.apache.logging.log4j:log4j-api'
  runtimeOnly 'org.apache.logging.log4j:log4j-core'
}

Dodawanie Log4j2 do samodzielnych aplikacji bez narzędzia do budowania

Jeśli Twój projekt nie korzysta z narzędzia do budowania, potrzebne artefakty Log4j2 możesz pobrać z oficjalnej strony.

Po pobraniu, upewnij się, że ścieżka klas Twojej aplikacji zawiera następujące pliki JAR:

  • `log4j-api-2.20.0.jar`
  • `log4j-core-2.20.0.jar`

Jakie są komponenty w Log4j2?

Aby w pełni zrozumieć i wykorzystać Log4j2, warto zapoznać się z zasadami jego działania. Pod powierzchnią Log4j2 składa się z kilku kluczowych elementów. Przyjrzyjmy się im po kolei.

#1. LoggerContext

LoggerContext stanowi centralny punkt systemu rejestrowania. Przechowuje wszystkie rejestratory używane w aplikacji, a także zawiera odniesienie do konfiguracji.

#2. Konfiguracja

Konfiguracja zawiera wszystkie informacje niezbędne do prawidłowego działania systemu logowania. Obejmuje to rejestratory, programy dołączające, filtry i inne. W Log4j2 konfigurację można zdefiniować przy użyciu różnych formatów plików, takich jak XML, JSON i YAML, a także programowo, za pomocą API Log4j2.

Konfiguracja jest automatycznie przeładowywana, gdy tylko jakakolwiek właściwość ulegnie zmianie, bez potrzeby ponownego uruchamiania aplikacji.

#3. Rejestrator

Podstawowym elementem systemu Log4j2 jest Logger. Rejestratory są uzyskiwane w kodzie aplikacji za pomocą instrukcji `LogManager.getLogger()` i służą do generowania logów. Komunikaty dziennika mogą być generowane na różnych poziomach istotności, takich jak debug, info, warn, error i fatal.

#4. LoggerConfig

LoggerConfig odpowiada za zachowanie konkretnego Loggera. Definiuje ustawienia rejestrowania zdarzeń generowanych przez ten konkretny rejestrator, pozwalając na konfigurację poziomów, dołączanie programów i stosowanie filtrów.

#5. Filtr

W Log4j2 można selektywnie przetwarzać zdarzenia dziennika za pomocą filtrów. Filtry działają w oparciu o określone kryteria i można je stosować do rejestratorów lub programów dołączających. Filtry kontrolują, które zdarzenia przechodzą dalej w potoku rejestrowania. Dzięki nim można precyzyjnie dostosować logowanie, zapewniając, że przetwarzane są tylko istotne informacje.

#6. Dodatek

Miejsce docelowe każdego komunikatu dziennika określa dodatek (Appender). Pojedynczy rejestrator może mieć wiele dodatków. Zdarzenie dziennika zostanie wysłane do każdego z nich. Log4j2 oferuje wiele gotowych dodatków. Przykładowo ConsoleAppender służy do wyświetlania wiadomości w konsoli, a FileAppender do zapisywania ich w pliku. Każdy Appender potrzebuje własnego układu, który określa format komunikatu.

#7. Układ

W Log4j2 układ (Layout) określa, jak będzie wyglądał końcowy komunikat dziennika. Layout jest powiązany z dodadkiem. Podczas gdy dodatek określa cel wyjścia, układ definiuje sposób prezentacji wiadomości.

5 najważniejszych cech Log4j2

Log4j2 jest pełen funkcji, które wyróżniają go spośród innych platform do logowania w Javie. Od asynchronicznych rejestratorów po obsługę lambda Java 8, Log4j2 ma znaczną przewagę. Omówmy kilka kluczowych cech tego frameworka.

#1. Rozszerzanie funkcjonalności za pomocą wtyczek

W Log4j 1.x tworzenie rozszerzeń wymagało wielu modyfikacji kodu. Log4j2 rozwiązuje ten problem, wprowadzając system wtyczek.

Nową wtyczkę można zadeklarować za pomocą adnotacji `@Plugin` w klasie. Dzięki wtyczkom można tworzyć własne komponenty, takie jak filtry i dodatki, a także łatwo dodawać komponenty innych firm.

#2. Wsparcie dla Javy 8 Lambda

Wraz z wersją 2.4 Log4j2 wprowadzono obsługę wyrażeń lambda języka Java 8. Za pomocą lambd logikę rejestrowania można definiować bezpośrednio, eliminując potrzebę stosowania wielu wierszy kodu lub anonimowych klas wewnętrznych. Zapewnia to, że kosztowne metody nie są wywoływane niepotrzebnie, co czyni kod bardziej przejrzystym i wydajnym.

Rozważmy przypadek, w którym rejestrujesz wynik kosztownej operacji tylko wtedy, gdy włączony jest poziom debugowania. Przed wprowadzeniem lambd, wyglądałoby to tak:

if (logger.isDebugEnabled()) {
    logger.debug("The output of the given operation is: {}", expensiveOperation());
}

W przypadku Log4j2, to samo można osiągnąć w następujący sposób:

logger.debug("The output of the given operation is: {}", () -> expensiveOperation()

Metoda `exprensiveOperation()` jest wywoływana tylko wtedy, gdy poziom debugowania jest włączony, bez konieczności jawnego sprawdzania warunku.

#3. Rejestratory asynchroniczne

Każde zdarzenie dziennika wiąże się z operacją wejścia/wyjścia, co obciąża system. Log4j2 wprowadza rejestratory asynchroniczne, działające w oddzielnym wątku od wątku aplikacji. Używając asynchronicznych rejestratorów, wątek wywołujący odzyskuje kontrolę natychmiast po wywołaniu metody `logger.log()`, pozwalając na kontynuowanie logiki aplikacji, zamiast czekać na zakończenie zdarzenia rejestrowania. Wykorzystanie tego mechanizmu poprawia przepustowość rejestrowania. Możesz wybrać, czy wszystkie rejestratory mają być asynchroniczne, czy też używać kombinacji zachowań synchronicznych i asynchronicznych.

#4. Logowanie bez śmieci

W Javie odśmiecanie (Garbage Collection) jest procesem automatycznego zwalniania nieużywanych obiektów w aplikacji. Mimo że nie musisz zajmować się nim ręcznie, ma on swoje obciążenie. Jeśli aplikacja tworzy zbyt wiele obiektów w krótkim czasie, proces odśmiecania może zająć więcej zasobów systemowych niż jest to konieczne.

Niektóre biblioteki logowania, w tym poprzednie wersje Log4j, tworzyły wiele tymczasowych obiektów podczas procesu logowania, zwiększając nacisk na moduł odśmiecania i obniżając wydajność systemu.

Od wersji 2.6, Log4j2 działa w trybie „bezśmieciowym”, który jest domyślny. Obiekty są ponownie wykorzystywane, a tworzenie obiektów tymczasowych jest znacznie ograniczone.

Poniższe ilustracje pokazują, jak wersja 2.6 Log4j2 zmniejsza problem niepotrzebnych obiektów w porównaniu z wersją 2.5:

W Log4j2 w wersji 2.5 podczas procesu logowania tworzonych jest wiele obiektów tymczasowych; źródło: apache.org

W Log4j2.6 nie ma obiektów tymczasowych tworzonych podczas procesu logowania; źródło: apache.org

#5. Wyszukiwania

W Log4j2 za pomocą odnośników możesz dodawać informacje kontekstowe do dzienników. Pozwalają one na dodawanie danych z różnych źródeł, takich jak właściwości systemowe, zmienne środowiskowe lub wartości zdefiniowane przez użytkownika. W ten sposób, dołączasz istotne informacje, pobierane dynamicznie, co zwiększa użyteczność dzienników.

Rozważmy sytuację, w której chcesz rejestrować identyfikator sesji użytkownika w każdym wierszu dziennika. Pozwoli to na wyszukanie wszystkich dzienników powiązanych z daną sesją.

Prymitywnym sposobem byłoby dodawanie identyfikatora sesji jawnie, co jednak jest trudne w utrzymaniu i łatwo o pominięcie. Wkrótce możesz zapomnieć o dodawaniu identyfikatora, tracąc istotne informacje.

logger.info("The user data has been fetched for session id {}", sessionId);
...
logger.info("The transaction has been processed for session id {}", sessionId);
...
logger.info("Request has been successfully processed for session id {}", sessionId);

Lepszym rozwiązaniem jest użycie kontekstu wątku. Identyfikator sesji można dodać do kontekstu wątku w kodzie aplikacji, a następnie użyć go w konfiguracji Log4j2, eliminując konieczność umieszczania go w komunikatach dziennika.

ThreadContext.put("sessionId", sessionId);

Po dodaniu wartości, można jej użyć w konfiguracji za pomocą słowa kluczowego `ctx`:

<File name="Application" fileName="application.log">
  <PatternLayout>
    <pattern>%d %p %c{1.} [%t] $${ctx:sessionId} %m%n</pattern>
  </PatternLayout>
</File>

Jak tworzyć niestandardowe poziomy dziennika w Log4j2?

Poziomy logowania w Log4j2 służą do kategoryzowania zdarzeń dziennika na podstawie ich wagi i istotności. Umożliwiają kontrolę, które wiadomości ostatecznie pojawią się w danych wyjściowych. Poziom rejestrowania można konfigurować w pliku konfiguracji.

Wstępnie skonfigurowane poziomy dziennika w Log4j2 i ich wartości przedstawiają się następująco:

OFF 0 FATAL 100 ERROR 200 WARN 300 INFO 400 DEBUG 500 TRACE 600 ALL MAX WARTOŚĆ

Jeśli poziom dziennika jest ustawiony na określony poziom, wyświetlane są wszystkie linie dziennika dla tej wartości i wartości od niej mniejszych. Pozostałe są ignorowane.

Przykładowo, ustawiając poziom rejestrowania na OSTRZEŻENIE, wyświetlone zostaną komunikaty OSTRZEŻENIE, BŁĄD i KRYTYCZNY, a pozostałe komunikaty będą pominięte. Jest to szczególnie przydatne, gdy ten sam kod uruchamiamy w różnych środowiskach.

Podczas pracy w środowisku programistycznym, możesz ustawić poziom dziennika na INFO lub DEBUG, aby wyświetlać więcej informacji i wspomagać proces programowania. W środowisku produkcyjnym, ustawienie na BŁĄD pozwoli Ci skupić się na problemach, zamiast analizować zbędne linie dziennika.

W pewnych sytuacjach, oprócz gotowych poziomów dziennika, możesz zechcieć dodać swój własny, niestandardowy poziom. Log4j2 pozwala na to w prosty sposób. Zobaczmy, jak możesz to zrobić i wykorzystać w swojej aplikacji.

#1. Dodawanie niestandardowego poziomu dziennika przy użyciu pliku konfiguracyjnego

Możesz dodać niestandardowe poziomy rejestrowania, deklarując je w pliku konfiguracji.

W poniższym przykładzie zdefiniowany jest niestandardowy poziom dziennika o nazwie NOTYFIKACJA z wartością 450, co umieści go pomiędzy INFO (z wartością 400) a DEBUG (z wartością 500). Oznacza to, że jeśli poziom jest ustawiony na NOTYFIKACJA, komunikaty INFO będą rejestrowane, a komunikaty DEBUG będą pomijane.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <CustomLevels>
    <CustomLevel name="NOTICE" intLevel="450" />
  </CustomLevels>
 
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout pattern="%d %-7level %logger{36} - %msg%n"/>
    </File>
  </Appenders>
  <Loggers>
    <Root level="trace">
      <AppenderRef ref="MyFile" level="NOTICE" />
    </Root>
  </Loggers>
</Configuration>

#2. Dodanie niestandardowego poziomu dziennika w kodzie

Oprócz zadeklarowania poziomów w pliku konfiguracyjnym, możesz to zrobić w swoim kodzie.

final Level VERBOSE = Level.forName("VERBOSE", 550);

Powyższy kod tworzy nowy poziom dziennika o nazwie VERBOSE. Znajdzie się on pomiędzy DEBUG (z wartością 500) a TRACE (z wartością 600). Jeśli rejestrator jest ustawiony na poziom VERBOSE, komunikaty VERBOSE i wyższe będą rejestrowane, włączając w to DEBUG, a komunikaty TRACE zostaną pominięte.

#3. Używanie niestandardowego poziomu dziennika w kodzie

Niestandardowe poziomy rejestrowania muszą zostać zadeklarowane przed użyciem, w pliku konfiguracyjnym lub w kodzie. Po deklaracji, można z nich swobodnie korzystać.

Poniższy przykład kodu pokazuje, jak zadeklarować niestandardowy poziom o nazwie UWAGA i jak go użyć:

final Level NOTICE = Level.forName("NOTICE", 550);

final Logger logger = LogManager.getLogger();
logger.log(NOTICE, "a notice level message");

Chociaż powyższy kod wygeneruje wymagany komunikat z nowym poziomem, jawne przekazywanie poziomu może być problematyczne. Możesz skorzystać z wygenerowania kodu źródłowego, aby uzyskać metody pomocnicze do rejestrowania na niestandardowych poziomach. W ten sposób będziesz mógł użyć metody `logger.notice()`, podobnie jak w przypadku `logger.debug()` czy `logger.error()`.

Log4j2 dostarcza narzędzie do tworzenia własnych, rozszerzonych rejestratorów. Poniższe polecenie wygeneruje plik Java o nazwie `CustomLogger.java`, zawierający istniejące metody logowania wraz z nowo wygenerowanymi metodami dla poziomu NOTYFIKACJA:

java -cp log4j-core-2.20.0.jar org.apache.logging.log4j.core.tools.ExtendedLoggerGenerator \
        com.example.CustomLogger NOTICE=450 > com/example/CustomLogger.java

Po wygenerowaniu pliku, możesz użyć tej klasy w swoim kodzie do tworzenia nowych rejestratorów, które będą zawierać dodatkowe metody dla niestandardowego poziomu dziennika, rozszerzając tym samym ich funkcjonalność.

final Logger logger = CustomLogger.create(ValueFirstSmsSender.class);

//this new method is similar to using logger.debug()
logger.notice("a notice level message");

Podsumowanie

Log4j2 to potężna platforma do logowania w Javie, oferująca szeroki zakres funkcji, konfiguracji, ulepszeń wydajności i wiele więcej. Logi są kluczową częścią procesu tworzenia oprogramowania, dlatego solidna struktura, taka jak Log4j2, znacząco zwiększa możliwości aplikacji.

Elastyczność i rozszerzalność Log4j2 pozwalają na dokładne rejestrowanie zdarzeń w aplikacji, a dzienniki stają się potężnym narzędziem do debugowania i audytu. Dzięki swoim funkcjom i usprawnieniom, Log4j2 jest preferowanym wyborem w różnorodnych projektach oprogramowania.

Możesz być również zainteresowany tymi środowiskami Java IDE i kompilatorami online.


newsblog.pl