1. Po co potrzebne jest logowanie
Logowanie ≠ wypisywanie na konsolę
Kiedy dopiero zaczynasz programować, wydaje się, że do szukania problemów wystarczy wszędzie wstawić System.out.println("Tu byłem!"). To działa, dopóki program jest mały i uruchamiasz go na własnym komputerze. Ale wyobraź sobie duży projekt, serwer działający 24/7 albo aplikację używaną przez tysiące ludzi — przecież nie będziesz siedzieć każdemu użytkownikowi za plecami i patrzeć na jego konsolę.
Logowanie — to nie tylko wypisywanie komunikatów. To usystematyzowany zapis informacji o pracy aplikacji: błędów, ostrzeżeń, zdarzeń biznesowych, szczegółów technicznych. Logi zapisuje się do plików, baz danych, mogą być wysyłane przez sieć — i pozwalają analizować działanie programu po jego wykonaniu lub w trakcie działania.
Do czego służą logi
- Diagnostyka problemów: jeśli coś poszło nie tak, logi to twój główny pomocnik. Na ich podstawie można zrozumieć, gdzie i dlaczego wystąpił błąd.
- Audyt i bezpieczeństwo: logi rejestrują ważne działania użytkowników, co pomaga badać incydenty.
- Debugowanie: czasem błędy ujawniają się tylko w określonych warunkach i bez logów nie da się ich uchwycić.
- Monitorowanie: z logów można odczytać stan aplikacji, jej wydajność i „zdrowie”.
Przykład z życia
Gdyby samoloty nie miały „czarnych skrzynek” (logów lotu), dochodzenie przyczyn wypadków byłoby niemal niemożliwe. W programowaniu logi to twoje czarne skrzynki.
2. Podstawowe poziomy logowania
W logowaniu przyjęło się używać poziomów (levels) komunikatów. To jak sygnalizacja świetlna: czerwone — niebezpieczeństwo, żółte — ostrożnie, zielone — wszystko w porządku.
Główne poziomy (od najbardziej „głośnego” do najbardziej „cichego”):
| Poziom | Opis |
|---|---|
|
Krytyczny błąd, aplikacja nie może kontynuować pracy |
|
Ostrzeżenie: coś poszło nie tak, ale program działa |
|
Informacyjny komunikat o zdarzeniach rutynowych |
|
Szczegółowe informacje do debugowania (widoczne tylko dla programistów) |
|
Najbardziej szczegółowe informacje — do głębokiej diagnostyki |
Przykład:
- Użytkownik nie mógł się zalogować z powodu błędnego hasła — to WARN.
- Zepsuła się baza danych — to ERROR.
- Aplikacja wystartowała — to INFO.
- Wypisywanie wartości zmiennej w pętli — to DEBUG lub TRACE.
Jak wybierać poziom
Nie warto wszystkiego pisać na poziomie ERROR — inaczej utoniesz w fałszywych alarmach. Używaj poziomów świadomie: tylko naprawdę krytyczne awarie powinny być błędami.
3. Standardowe logowanie w Javie (java.util.logging)
Java jest dostarczana z wbudowanym systemem logowania — pakietem java.util.logging (w skrócie JUL). To narzędzie dostępne zawsze, nawet jeśli nie dołączyłeś żadnych bibliotek.
Główne klasy:
- Logger — główna klasa do zapisywania logów.
- Level — wyliczenie poziomów logowania (SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST).
- Handler — wskazuje, dokąd pisać logi (plik, konsola itd.).
- Formatter — odpowiada za format komunikatów.
Przykład użycia JUL
import java.util.logging.Logger;
import java.util.logging.Level;
public class LoggingExample {
// Pobieramy logger na podstawie nazwy klasy
private static final Logger logger = Logger.getLogger(LoggingExample.class.getName());
public static void main(String[] args) {
logger.info("Aplikacja została uruchomiona");
logger.warning("To jest ostrzeżenie!");
logger.severe("A to już błąd!");
int x = 42;
logger.fine("Zmienna debug x=" + x); // Domyślnie nie jest wyświetlana
}
}
Ważna uwaga: domyślnie JUL wypisuje tylko poziomy INFO i wyższe. Aby zobaczyć fine i inne szczegółowe komunikaty, trzeba ustawić odpowiedni poziom logowania.
Konfiguracja przez plik
JUL można konfigurować przez plik logging.properties (zwykle leży w katalogu JRE). Można tam określić:
- Minimalny poziom dla loggera
- Dokąd pisać logi (plik, konsola)
- Formatowanie komunikatów
Przykładowa linia w logging.properties:
.level=INFO
4. Zewnętrzne biblioteki logowania
Log4j (Apache)
Log4j — jedna z najbardziej znanych bibliotek logowania dla Javy. Jest elastyczna, wydajna, obsługuje różne formaty, zapis asynchroniczny, rotację plików i wiele więcej.
Przykład najprostszej konfiguracji Log4j 2 (XML):
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Przykład użycia Log4j 2 w kodzie:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jExample {
private static final Logger logger = LogManager.getLogger(Log4jExample.class);
public static void main(String[] args) {
logger.info("Hello from Log4j!");
logger.error("To błąd z użyciem Log4j");
}
}
Aby to działało, należy dodać zależności Log4j do projektu (np. przez Maven lub Gradle).
SLF4J: fasada logowania
SLF4J (Simple Logging Facade for Java) — to nie jest oddzielny system logowania, lecz „warstwa pośrednia” między twoim kodem a konkretną implementacją (Log4j, Logback, JUL itd.).
Fasada jest potrzebna, aby pisać kod raz, a w razie potrzeby zmieniać rzeczywisty system logowania bez przepisywania kodu, nawet jeśli różne biblioteki używają różnych loggerów.
Przykład użycia SLF4J:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jExample {
private static final Logger logger = LoggerFactory.getLogger(Slf4jExample.class);
public static void main(String[] args) {
logger.info("Witaj, logowanie przez SLF4J!");
logger.warn("Uwaga: coś może pójść nie tak");
logger.error("Błąd z użyciem SLF4J");
}
}
Jak to działa?
- Piszesz kod przez SLF4J.
- Do classpath dodajesz potrzebną implementację (np. Logback lub Log4j).
- SLF4J sam „przekazuje” wywołania do wybranej implementacji.
Współpraca SLF4J z innymi loggerami: SLF4J może działać nad JUL, Log4j, Logback itd., co pozwala łatwo migrować bez przepisywania kodu.
5. Praktyka: porównujemy System.out.println i logowanie
Przykład 1: System.out.println
public class PrintlnExample {
public static void main(String[] args) {
System.out.println("Aplikacja została uruchomiona");
System.out.println("Wystąpił błąd: coś poszło nie tak");
}
}
Co jest nie tak:
- Brak poziomu komunikatu (błąd, informacja, ostrzeżenie — wszystko wygląda tak samo).
- Brak czasu, nazwy klasy, wątku.
- Wszystkie komunikaty trafiają do jednego „worka”.
- Nie można elastycznie skonfigurować wyjścia (np. wypisywać tylko błędy).
Przykład 2: Logowanie przez SLF4J + Logback
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingVsPrintln {
private static final Logger logger = LoggerFactory.getLogger(LoggingVsPrintln.class);
public static void main(String[] args) {
logger.info("Aplikacja została uruchomiona");
logger.error("Wystąpił błąd: coś poszło nie tak");
}
}
Zalety:
- Każdy komunikat jest opatrzony czasem, poziomem, nazwą klasy i wątkiem.
- Można filtrować komunikaty według poziomu.
- Można pisać logi do pliku, wysyłać przez sieć, formatować, archiwizować.
- Logowanie jest bezpieczne dla wątków (istotne w aplikacjach wielowątkowych).
Demonstracja różnicy
|
Logowanie (SLF4J/Logback) |
|---|---|
| Aplikacja została uruchomiona | 12:34:56 [main] INFO LoggingVsPrintln - Aplikacja została uruchomiona |
| Wystąpił błąd: coś poszło nie tak | 12:34:56 [main] ERROR LoggingVsPrintln - Wystąpił błąd: coś poszło nie tak |
6. Jak dodać logowanie do swojego projektu
Dla standardowego loggera (JUL)
Wszystko jest już w JDK, można od razu używać:
import java.util.logging.Logger;
public class MyApp {
private static final Logger logger = Logger.getLogger(MyApp.class.getName());
public static void main(String[] args) {
logger.info("To komunikat informacyjny");
}
}
Dla Log4j lub SLF4J
- Dodaj zależności (np. przez Maven).
Dla Log4j 2:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.20.0</version> </dependency>Dla SLF4J + Logback:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.7</version> </dependency> - Utwórz plik konfiguracyjny (np. logback.xml dla Logback lub log4j2.xml dla Log4j).
- Używaj loggera w swoim kodzie (zob. przykłady powyżej).
7. Miniaplikacja: dodajemy logowanie
Załóżmy, że mamy prostą aplikację — kalkulator wiersza poleceń. Dodajmy do niej logowanie.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
public class CalculatorApp {
private static final Logger logger = LoggerFactory.getLogger(CalculatorApp.class);
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
logger.info("Kalkulator uruchomiony");
try {
System.out.print("Wprowadź pierwszą liczbę: ");
int a = Integer.parseInt(scanner.nextLine());
logger.debug("Wprowadzono pierwszą liczbę: {}", a);
System.out.print("Wprowadź drugą liczbę: ");
int b = Integer.parseInt(scanner.nextLine());
logger.debug("Wprowadzono drugą liczbę: {}", b);
int sum = a + b;
logger.info("Suma liczb: {}", sum);
System.out.println("Wynik: " + sum);
} catch (NumberFormatException ex) {
logger.error("Błąd wprowadzania liczby", ex);
System.out.println("Błąd: wprowadź poprawną liczbę!");
} catch (Exception ex) {
logger.error("Nieznany błąd", ex);
System.out.println("Wystąpił błąd!");
}
logger.info("Kalkulator zakończył pracę");
}
}
Co się tutaj dzieje:
- Wszystkie kluczowe zdarzenia są rejestrowane w logu.
- Błędy są logowane z poziomem ERROR i pełnym stack trace’em.
- Wszystkie informacje są dostępne do analizy po zakończeniu działania programu.
8. Typowe błędy przy pracy z logowaniem
Błąd nr 1: używanie tylko System.out.println do debugowania i diagnostyki.
To wygodne na starcie, ale kompletnie nie nadaje się do prawdziwych aplikacji. Nie możesz zarządzać poziomem komunikatów, nie zobaczysz czasu, nie przeanalizujesz logów, gdy program działa na serwerze.
Błąd nr 2: logowanie zbyt dużej ilości informacji na poziomie ERROR.
Jeśli wszystko jak leci zapisujesz jako błąd, przestaniesz rozróżniać, co naprawdę ważne, a co jest tylko informacją do debugowania.
Błąd nr 3: logowanie danych wrażliwych (hasła, tokeny, numery kart).
Logi muszą być bezpieczne! Nie zapisuj tam tego, czego inni nie powinni widzieć.
Błąd nr 4: brak konfiguracji logowania.
Jeśli nie ustawisz poziomów, formatu i miejsca przechowywania logów — możesz niespodziewanie „utonąć” w gigabajtach niepotrzebnych informacji albo w ogóle nie zauważyć błędu.
Błąd nr 5: niewykorzystywanie fasady logowania (SLF4J) w dużych projektach.
Gdy projekt rośnie i przechodzisz z jednej biblioteki na inną — bez fasady to będzie bolesne. SLF4J pozwala łatwo zmienić „silnik” logowania bez przepisywania kodu.
GO TO FULL VERSION