자바 로깅 개요 — 파편화된 생태계 이해하기

자바 로깅은 JUL, Log4j, Logback, SLF4J가 얽힌 복잡한 역사를 가졌습니다. 파사드와 구현의 분리, 로그 레벨, 왜 직접 println을 쓰면 안 되는지, 그리고 생태계 전체 지형을 정리해 이후 글들의 토대를 놓습니다.

· 6 min read · PALDYN Team

지난 글에서 뮤테이션 테스트로 테스트의 품질을 검증하는 법까지 다루며 테스트 묶음을 마쳤습니다. 이번 글부터는 운영의 또 다른 핵심인 로깅으로 넘어갑니다. 로깅은 단순해 보이지만, 자바 진영의 로깅 생태계는 십수 년에 걸쳐 여러 라이브러리가 등장하고 경쟁하며 얽힌 탓에 처음 보면 혼란스럽습니다. JUL, Log4j 1, Log4j 2, Logback, 그리고 이들을 묶는 SLF4J까지 — 이 지형도를 먼저 이해하면 이후의 개별 도구들이 제자리를 찾습니다.

왜 System.out.println은 안 되는가

가장 먼저 드는 의문은 “그냥 System.out.println을 쓰면 안 되나?”입니다. 안 됩니다. println은 출력 위치(콘솔)가 고정되어 있고, 메시지에 시각·스레드·클래스 같은 맥락을 자동으로 붙여 주지 않으며, 무엇보다 끄고 켤 수 없습니다. 개발 중 찍은 디버그 출력이 운영에 그대로 쏟아지고, 그걸 없애려면 코드를 고쳐 재배포해야 합니다. 로깅 프레임워크는 이 모든 문제를 레벨·출력 대상·포맷의 분리로 해결합니다.

로그 레벨

로그 레벨의 위계

로깅의 핵심 개념은 레벨입니다. 메시지마다 심각도를 부여하고(TRACE < DEBUG < INFO < WARN < ERROR), 설정한 레벨 이상만 출력합니다. 운영에서는 레벨을 INFO로 두어 TRACE·DEBUG를 버리고, 특정 버그를 추적할 때 해당 패키지만 DEBUG로 낮춰 상세 로그를 켭니다. 코드를 고치지 않고 설정만 바꿔 로그의 양과 상세도를 조절할 수 있는 것, 이것이 println이 줄 수 없는 가치입니다.

log.trace("요청 헤더 전체: {}", headers);  // 평소엔 안 보임
log.debug("조회 결과 {}건", rows.size());   // 개발/진단 시
log.info("주문 {} 생성 완료", orderId);     // 운영 기본
log.warn("재시도 {}회째", attempt);          // 주의
log.error("결제 실패", exception);           // 오류 + 스택트레이스

파사드와 구현의 분리

자바 로깅 생태계 — 파사드와 구현의 분리

자바 로깅 생태계를 이해하는 열쇠는 파사드(facade)와 구현(implementation)의 분리입니다. 파사드는 로깅 API, 즉 코드가 호출하는 인터페이스입니다. 구현은 실제로 로그를 포맷하고 파일이나 콘솔에 쓰는 엔진입니다.

이를 분리하는 이유는 교체 가능성입니다. 애플리케이션 코드와 라이브러리들이 모두 파사드인 SLF4J에만 의존하면, 실제 엔진은 Logback이든 Log4j 2든 자유롭게 바꿔 끼울 수 있습니다. 특히 여러 라이브러리가 제각각 다른 로깅 프레임워크를 쓰더라도, SLF4J가 이를 하나의 출력으로 모아 줍니다. 라이브러리는 SLF4J API로 로그를 남기고, 최종 애플리케이션이 어떤 구현을 쓸지 결정하는 구조입니다.

생태계의 주요 인물들

이후 글들에서 하나씩 깊게 다루겠지만, 전체 지형을 미리 정리하면 다음과 같습니다.

SLF4J     : 사실상 표준 파사드(API). 코드는 여기에만 의존
Logback   : SLF4J 제작자가 만든 기본 구현. 스프링 부트 기본값
Log4j 2   : 비동기 로깅 등 고성능 구현. SLF4J 바인딩 제공
JUL       : java.util.logging. JDK 내장이라 의존성 없음
Log4j 1   : 오래된 1세대. EOL, 더 이상 쓰지 않음

스프링 부트로 개발한다면 기본 조합은 SLF4J + Logback입니다. 코드에서는 org.slf4j.Logger로 로그를 남기고, 출력 형식·파일 분할·레벨은 Logback 설정에서 다룹니다.

정리

자바 로깅은 파사드(SLF4J)와 구현(Logback, Log4j 2, JUL)의 분리로 이해해야 합니다. 코드는 println 대신 로깅 API를 써서 레벨로 출력을 제어하고, SLF4J라는 공통 인터페이스에 의존함으로써 구현을 자유롭게 교체하며 여러 라이브러리의 로그를 하나로 모읍니다. 이 지형도를 머리에 두면 이후의 개별 도구가 어디에 들어맞는지 보입니다. 다음 글에서는 이 생태계의 중심인 SLF4J를 자세히 들여다봅니다.


지난 글: 뮤테이션 테스트 — PIT로 테스트의 품질을 검증하기

다음 글: SLF4J — 로깅 파사드의 표준


읽어주셔서 감사합니다. 😊