Logback & Log4j2 충돌: SLF4J 바인딩 오류 해결 가이드
Java 애플리케이션에서 로깅을 안전하게 구성하려면 SLF4J 추상화와 실제 구현체 사이의 관계를 정확히 이해해야 합니다. Logback과 Log4j2를 동시에 클래스패스에 올리면 예기치 않은 바인딩 충돌이 발생할 수 있습니다. 이 가이드는 제공된 예제 코드를 바탕으로 원인을 진단하고, 실무에서 바로 적용 가능한 해결 방법을 단계별로 안내합니다. Logback & Log4j2 충돌: SLF4J 바인딩 오류 해결 가이드의 핵심은 '하나의 로깅 구현체 선택'입니다.
1. 💥 문제 진단: 이중 바인딩 충돌
먼저 제공된 pom.xml 의존성을 확인하면 문제의 실마리가 명확해집니다.
제공된 Maven 의존성 (pom.xml)
<!-- Log4j2 바인딩: SLF4J를 Log4j2로 연결 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
<!-- Logback (자체적으로 SLF4J 구현체를 포함) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
문제의 원인:
SLF4J는 런타임에 클래스패스에서 단일 구현체와만 연결되어야 합니다. 현재 설정은 그 규칙을 위반하고 있습니다.
- org.apache.logging.log4j의 log4j-slf4j-impl는 SLF4J 호출을 Log4j2로 전달합니다.
- ch.qos.logback의 logback-classic은 자체적으로 SLF4J 바인딩을 제공하여, SLF4J 구현체 역할을 수행합니다.
결과적으로 클래스패스에 Log4j2 바인딩과 Logback 바인딩이 동시에 존재하여 SLF4J의 "Multiple Bindings Found" 경고가 출력되며, 어떤 구현체가 실제로 로그를 처리할지 예측하기 어려워집니다. 이 상태를 그대로 두면 로그 출력의 일관성이 깨지고 디버깅이 복잡해집니다. Logback & Log4j2 충돌: SLF4J 바인딩 오류 해결 가이드를 따라 하나의 구현체로 통일해야 합니다.
2. ✅ 해결책: 하나의 구현체만 선택
간단한 원칙입니다. Logback 또는 Log4j2 중 하나만 남기고 나머지 관련 의존성을 제거하거나 제외(Exclusion)하세요. 이렇게 하면 SLF4J 바인딩 충돌을 근본적으로 해소할 수 있습니다.
옵션 1: Log4j2를 로깅 구현체로 사용 (권장)
Logback 관련 의존성을 완전히 제거하고, Log4j2의 핵심 모듈을 명시적으로 포함합니다. Log4j2를 선택하면 log4j-core와 log4j-api가 함께 필요합니다.
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>...</version>
</dependency>
<!-- Log4j2 바인딩 (유지) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
<!-- Log4j2 코어 구현체 (필수) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
<!-- ❌ logback-classic 의존성 제거! -->
</dependencies>
옵션 2: Logback을 로깅 구현체로 사용
반대로 Logback을 유지하고 싶다면 log4j-slf4j-impl 의존성을 제거하면 됩니다. Logback은 자체적으로 SLF4J 바인딩을 제공하므로 별도의 바인딩 라이브러리가 필요하지 않습니다.
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>...</version>
</dependency>
<!-- Logback (SLF4J 구현체 포함) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ❌ log4j-slf4j-impl 의존성 제거! -->
</dependencies>
3. 💻 개발 코드 및 설정 파일 분석
3.1. Logger 객체 생성 (Class)
애플리케이션 코드에서 SLF4J API를 사용하고 있다면, 구현체를 바꿔도 소스 코드 변경은 거의 필요 없습니다. 이는 SLF4J의 중요한 장점입니다: 구현체 변환이 런타임 의존성만으로 가능하다는 점입니다.
// Logback이든, Log4j2든, 이 코드는 변하지 않습니다.
protected Logger logger = LoggerFactory.getLogger(WebSocketChatClient.class.getName());
3.2. Log4j2 설정 (log4j2.xml)
Log4j2를 선택하면 제공된 log4j2.xml 설정을 그대로 사용할 수 있습니다. 이 설정은 콘솔 출력과 포맷을 정의하며, 루트 로거의 레벨을 조정해 전체 로그 출력을 제어합니다.
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<!-- 모든 로그에 대해 debug 레벨 이상을 console로 출력하도록 설정 -->
<Root level="debug" additivity="false">
<AppenderRef ref="console" />
</Root>
</Loggers>
</Configuration>
위 구성은 디버그 이상 수준의 로그를 콘솔에 출력하도록 설정합니다. 필요에 따라 레벨과 앱엔더 구성을 조정해 사용하세요.
함께 보면 좋은 엔터프라이즈 사례
🚀 이 주제, 우리 서비스에 어떻게 적용할까요?
Logback & Log4j2 충돌: SLF4J 바인딩 오류 해결 가이드를 실제 서비스와 조직에 녹여보고 싶다면, 현재 아키텍처와 운영 방식을 한 번 점검해 보는 것부터 시작해 보세요. 팀 위키나 기술 블로그, 사내 스터디 주제로도 아주 좋습니다.
이 글이 도움이 됐다면, 비슷한 엔터프라이즈 사례 글들도 함께 살펴보면서 우리 조직에 맞는 운영 상용구를 정의해 보세요.
댓글
댓글 쓰기