Java/04core11 min

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

PoziomKiedy
ERRORbłąd wymagający uwagi (awaria operacji)
WARNsytuacja nietypowa, ale obsłużona (timeout, retry)
INFOistotne zdarzenia biznesowe (start skanu, wynik)
DEBUGszczegół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.