이번 포스팅은 log를 찍을때 중복으로 binding, bridge 가 있을 경우에 어떻게 되는지 테스트 해볼라고 한다.
저번 spring boot logging 에서 봤듯이 spring boot의 logger 동작 방식을 살펴봤었다.
물론 spring boot 기반으로 테스트 해본다. 설정하기도 귀찮아서…

spring boot의 프로젝트를 생성 후에 메이븐을 추가하자.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j</artifactId>
</dependency>

spring boot 로그 설정을 log4j로 하면 log4j binding 가 생기고 jcl과 jul 은 bridge라이브러리가 생긴다.
그리고 별다른 셋팅은 필요 없다. 소스는 아래와 같이만 한다.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringLogTestApplication.class)
public class SpringLogTestApplicationTests {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Test
    public void test() {

    }

}

그냥 어떻게 되는지만 확인하면 되니까 딱히 소스는 필요 없다.

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.21</version>
</dependency>

위와 같이 log4j의 bridge를 추가해보자. 그럼 동일한 브릿지와 바인딩이 존재 할 것이다. 그런후에 실행해보자!
그럼 다음과 같이 에러가 난다.

SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. 
SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.

동일한 브릿지와 바인딩이 있으므로 에러가 난다.
org.slf4j.impl.Log4jLoggerFactory 이 클래스에 가보면 이런 코드가 있다.

    static {
        try {
            Class.forName("org.apache.log4j.Log4jLoggerFactory");
            String part1 = "Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. ";
            String part2 = "See also " + LOG4J_DELEGATION_LOOP_URL + " for more details.";

            Util.report(part1);
            Util.report(part2);
            throw new IllegalStateException(part1 + part2);
        } catch (ClassNotFoundException e) {
            // this is the good case
        }
    }

위의 org.apache.log4j.Log4jLoggerFactory 클래스는 log4j 브릿지 라이브러리에 있는 클래스이다. 만약 이 클래스가 없다면 무시하고 있으면 에러를 내뱉는다. 그러므로 실행 자체가 되지 않는다.

이번에는 binding 라이브러리가 중복으로 클래스 패스에 있으면 어떻게 될까?
기존의 log4j의 브릿지를 제거하고 아래와 같이 logback 의 binding을 메이븐에 추가하자

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.7</version>
</dependency>

다시 실행해보자.

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/hellowd/.m2/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/hellowd/.m2/repository/ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

위와 같이 에러가 발생한다.
org.slf4j.LoggerFactory 클래스에 가보면 아래와 같은 메서드가 있다.

    private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
        if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
            Util.report("Class path contains multiple SLF4J bindings.");
            for (URL path : binderPathSet) {
                Util.report("Found binding in [" + path + "]");
            }
            Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
        }
    }

SLF4J bindings 여러개 있다고 하는듯하다.

    private static void reportActualBinding(Set<URL> binderPathSet) {
        // binderPathSet can be null under Android
        if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
            Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
        }
    }

위의 메서드에서 실제 둘중 하나를 바인딩을 한다. 보통은 클래스패스에서 읽어 들일때 첫번째로 읽어들인 값을 바인딩 한다. 아까 위에서 본 로그를 보면 SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory] 이와 같은 로그를 확인할 수 있다. log4j 를 바인딩 했다는 것을 알 수 있다.

근데 조금 이상한 것은 log4j 바인딩 됐을때는 실행도 안되고 에러가 나지만 logback 이 바인딩 됐을때는 경고 메시지만 출력하고 실행은 된다.
더욱 거지 같은건 내가 선택을 할 수 없다. (물론 선택해서 테스트를 못해서 거지 같다고 한거뿐이다.)행여나 방법이 있을지도 모르긴한데 필자는 현재 모른다. 찾았다. 메이븐일 경우에 먼저 선언하면 된다.
logback이 바인딩 되었을 경우 로그이다.

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/hellowd/.m2/repository/ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/hellowd/.m2/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

어느 것이 먼저 읽어들일지는 아무도(?) 모른다.
이렇게 중복으로 브릿지와 바인딩이 있을 경우에 어떻게 되는지 테스트를 해보았다.