Wyjątki i logowanie (SLF4J)
Dobra obsługa wyjątków i przemyślane logowanie decydują o tym, jak szybko diagnozujemy problemy na produkcji. Ta lekcja zbiera house rules ProfessNet dla Javy: własne wyjątki, globalna obsługa i logowanie przez SLF4J.
Łap konkretne wyjątki
Nie łapiemy gołego Exception, nie połykamy błędów. Łapiemy konkretny typ
i obsługujemy go świadomie.
// źle — połknięty wyjątek, błąd znika
try {
scanner.scan(forest);
} catch (Exception e) {
// cisza
}
// dobrze — konkretny typ, log z kontekstem, sensowna reakcja
try {
scanner.scan(forest);
} catch (ProbeTimeoutException e) {
log.warn("Timeout podczas skanu forestu {}", forest, e);
throw new ScanFailedException("Probe nie odpowiedział", e);
}
Standard ProfessNet: nigdy pusty
catch. Albo obsługujemy wyjątek, albo opakowujemy i propagujemy z zachowaniem przyczyny (cause).
Własne wyjątki domenowe
Tworzymy hierarchię wyjątków odzwierciedlającą domenę. Zwykle dziedziczymy po
RuntimeException (unchecked), żeby nie zaśmiecać sygnatur.
public class ScanFailedException extends RuntimeException {
public ScanFailedException(String message, Throwable cause) {
super(message, cause);
}
}
Globalna obsługa w Spring
W Spring Boot wyjątki mapujemy na odpowiedzi HTTP centralnie przez
@RestControllerAdvice — kontrolery zostają czyste.
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log =
LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(ProbeNotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(ProbeNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ApiError("PROBE_NOT_FOUND", e.getMessage()));
}
@ExceptionHandler(ScanFailedException.class)
public ResponseEntity<ApiError> handleScanFailed(ScanFailedException e) {
log.error("Skan nie powiódł się", e);
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
.body(new ApiError("SCAN_FAILED", "Operacja nie powiodła się"));
}
}
Logowanie przez SLF4J
Logujemy przez fasadę SLF4J (LoggerFactory.getLogger). Używamy logowania
parametryzowanego ({}) — wydajnego i bezpiecznego.
private static final Logger log = LoggerFactory.getLogger(InventoryService.class);
// dobrze — parametry, leniwa ewaluacja
log.info("Rozpoczęto skan forestu {} ({} hostów)", forest, hostCount);
// źle — konkatenacja stringów (zawsze buduje string, nawet gdy log wyłączony)
log.info("Rozpoczęto skan forestu " + forest);
Standard ProfessNet: logujemy przez SLF4J, zawsze parametrami
{}, nigdy przez konkatenację. Wyjątek przekazujemy jako ostatni argument — wtedy SLF4J zaloguje pełny stack trace.
Poziomy logowania
| Poziom | Kiedy |
|---|---|
ERROR | błąd wymagający uwagi (awaria operacji) |
WARN | sytuacja nietypowa, ale obsłużona (timeout, retry) |
INFO | istotne zdarzenia biznesowe (start skanu, wynik) |
DEBUG | szczegóły diagnostyczne (wyłączone na produkcji) |
Wskazówka: nigdy nie loguj sekretów ani danych wrażliwych — haseł, tokenów, pełnych danych osobowych. Logi trafiają do systemów, które mają inny krąg dostępu niż baza.
Konkretne wyjątki, własna hierarchia domenowa, globalny handler i logowanie SLF4J z parametrami — to przepis na serwis, który łatwo diagnozować. Błędy mają być widoczne i czytelne, nigdy ukryte.