SLF4J & Log4j
SLF4J는 "로깅 인터페이스(Facade)"고,
Log4j는 "로깅 구현체"임
역할 차이
구분 SLF4J Log4j
뭐임? | 로깅의 껍데기 (추상화된 인터페이스) | 실제 로그 찍는 놈 (구현체) |
직접 로그 찍음? | ❌ | ✅ |
유연함? | ✅ (뒤에 구현체 갈아끼기 쉬움) | ❌ (자기 방식만 고집함) |
목적 | 다양한 로깅 시스템에 대응 | 로깅 기능 자체 제공 |
종속성 구조 | 구현체가 필요함 (Log4j, Logback 등) | 단독 사용 가능 |
비유하면?
SLF4J = 콘센트 인터페이스
Log4j, Logback = 실제 플러그를 꽂는 전자제품
내가 SLF4J를 쓰면, 뒤에 어떤 제품(Log4j든 Logback이든)을 꽂아도 작동함
그래서 "코드는 SLF4J로 짜고, 구현체는 갈아끼워라" 이런 말 나오는 거임
예전에는 Log4j만 썼음
Spring 2.x 시절엔 대부분 그냥 Log4j 직접 씀
근데 단점이 많았음:
- 다른 로깅 시스템과 호환성 별로
- 코드가 Log4j에 종속됨
- 성능 이슈 있음
그래서 SLF4J가 나옴
“야, 공통 껍데기 하나 만들어서, 걍 그거 쓰게 하자”
그래서 Log4j도, Logback도, JUL도 다 SLF4J로 연결 가능해짐
SLF4J + Log4j 조합도 가능
"Logger는 SLF4J로 만들고, 실제 로그는 Log4j로 찍어라"
이렇게 설정할 수도 있음
Maven 예시
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- Log4j를 SLF4J용 구현체로 쓰는 브릿지 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.36</version>
</dependency>
<!-- 실제 Log4j 엔진 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
이러면 코드에서 SLF4J만 써도, 뒤에서 Log4j가 로그 찍어줌
근데 Log4j는 지금은 좀 쓰지 마라
Log4j는 2021년에 심각한 보안 이슈 (Log4Shell) 터졌음
- 악성 요청 하나로 서버 장악 가능
- 이걸로 전세계 수많은 시스템 털렸음
그래서 지금은 대부분 Logback 쓰거나
Log4j 2.x (보안패치 된 버전) 씀
결론 요약
항목 SLF4J Log4j
뭐냐 | 로깅 인터페이스 | 로깅 구현체 |
직접 로그 찍냐 | ❌ | ✅ |
코드 종속성 | 낮음 (유연함) | 높음 (바꾸기 힘듦) |
실무 추천 | SLF4J + Logback | Log4j는 버전 2 이상만 고려 |
실무에서는 거의 대부분 이렇게 씀:
SLF4J + Logback 조합
SLF4J의 Logger & LoggerFactory
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
여기서 이 둘 다 SLF4J 패키지임
즉, Simple Logging Facade for Java라는 라이브러리에서 제공하는 클래스
구조 요약
클래스 역할
Logger | 로그 찍는 객체 (인터페이스) |
LoggerFactory | Logger 객체 만들어주는 공장 클래스 (팩토리 패턴) |
SLF4J?
SLF4J는 "로깅의 껍데기"임 (Facade)
- 직접 로그를 찍는 게 아니라, 뒤에 어떤 로깅 구현체가 붙냐에 따라 실행됨
- 예시로 붙일 수 있는 놈들:
- Logback (SLF4J 공식 구현체)
- Log4j
- java.util.logging (JUL)
- commons-logging 등등
즉,
- 코드에서 SLF4J의 Logger만 쓰면 됨
- 뒤에 붙는 실제 구현체는 네가 설정해서 고를 수 있음
- 이게 SLF4J의 핵심
의존성은 어디서 ?
Maven이나 Gradle에서 이거 안 넣으면 못 씀
Maven 기준 (pom.xml)
<!-- SLF4J API (껍데기) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- 구현체 (Logback 사용 예시) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
이렇게 둘 다 넣어야 동작함
- slf4j-api: Logger 인터페이스만 존재
- logback-classic: 실제 로그를 찍는 구현체
구현체 없으면?
- SLF4J는 그냥 인터페이스만 있어서,
- 구현체가 없으면 "Logger는 있지만 찍을 방법이 없다"는 상태가 됨
- 그래서 프로젝트에 logback-classic, log4j-slf4j-impl, jul-to-slf4j 중 하나는 무조건 있어야 로그가 나감
결론 요약
구분 내용
Logger, LoggerFactory | SLF4J의 인터페이스 클래스 |
실질 로그 찍는 놈 | logback, log4j 등 구현체가 찍음 |
왜 이렇게 분리함? | 구현체 바꿔끼기 쉽게 하려고 (유지보수 유리) |
의존성 필수 | slf4j-api, 그리고 구현체 1개 이상 필요 |
SYSOUT vs LOGGER
그럼 System.out.println()이랑 비교해서 뭐가 좋은데?
항목 System.out.println() Logger
로그레벨 | ❌ 없음 | ✅ 있음 (info/debug/error 등) |
로그 파일 저장 | ❌ 불가 | ✅ 가능 |
운영환경 제어 | ❌ 불가 | ✅ 레벨 조절 가능 |
성능 | 느림 (IO 직접) | 빠름 (버퍼처리, 비동기 가능) |
Logger란?
Logger는 로그를 찍기 위한 객체
개발할 때 System.out.println()으로 출력하는 습관이 있는데, 그거보다 훨씬 강력하고 실용적인 방식임
SLF4J(Simple Logging Facade for Java)는 로깅 추상화 라이브러리라서, 다양한 구현체(Logback, Log4j 등)를 내부적으로 선택해서 쓸 수 있게 해줌
Logger 쓰는 이유?
이유 설명
레벨별 로그 분리 | info, debug, error 같은 레벨로 구분 가능 |
로그 관리 | 로그 파일 저장, 날짜별 분리, 용량 제한 등 가능 |
퍼포먼스 | System.out보다 효율적임 (멀티스레드 환경에서도 안정적) |
운영 환경 | 콘솔에 안 찍고 파일로 저장하는 게 일반적임 |
Logger 코드 분석
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
역할
- SLF4J에서 제공하는 LoggerFactory를 이용해 Logger 객체 생성
- 로그를 찍을 클래스 정보를 넘겨주면 됨 (HomeController.class)
- static final로 선언해서 클래스 단위로 공용으로 사용
사용된 Logger 함수들
코드에서 사용한 메서드는 총 3가지임
1. logger.info(String msg)
- 정보성 로그 찍을 때 사용
- 운영 환경에서도 보통 이 레벨까지는 출력함
logger.info("서비스 실행됨");
this.logger.info(rs.getString("ctn")); // 결과값 출력
this.logger.info(con.toString()); // 커넥션 객체 정보 출력
this.logger.info("테스트 진행중"); // 상태 확인 로그
2. logger.debug(String msg)
- 디버깅용 로그임
- 개발 단계에서 디테일한 정보 볼 때 쓰임
- 보통 운영 환경에서는 이 로그는 안 나감 (로그 레벨 설정에 따라 출력 여부 결정됨)
this.logger.debug("오류발생"); // 오류 디버깅 로그
3. logger.error(String msg)
- 에러 로그 찍을 때 사용
- 예외, 심각한 문제 발생 시 기록함
- 운영 환경에서도 기본적으로 출력하게 설정함
this.logger.error(e.toString()); // 예외 객체의 메시지를 문자열로 출력
로깅 레벨 정리
레벨 설명 출력 대상 (운영 기준)
trace | 가장 상세한 로그 (보통 안 씀) | ❌ |
debug | 개발 단계 디버깅용 | ❌ |
info | 일반 정보성 메시지 | ✅ |
warn | 경고 (문제 될 수 있는 상황) | ✅ |
error | 에러 발생 | ✅ |
로그 설정은 어디서 ?
보통 logback.xml 또는 log4j.xml 파일에서 로그 레벨, 출력 포맷, 파일 저장 여부 등을 설정함
예시 (logback.xml):
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
실무 팁
- System.out.println()은 쓰지 마라 → 로거로 통일
- 로깅 메세지는 항상 레벨 적절히 나눠서 사용
- 로그 찍을 때는 가독성 좋게, 필요한 정보만 적절히 포함
- Exception은 logger.error("에러내용", e);처럼 객체도 같이 넘겨주는 게 좋음 (스택트레이스까지 나옴)