본문 바로가기

OOP

[구조패턴] 퍼사드(fasade) 패턴과 slf4j

퍼사드(fasade) 패턴이란

- 여러 라이브러리, 프레임워크 또는 클래드들의 복합한 집합을 단순화시켜주는 인터페이스를 제공하는 구조의 디자인 패턴

대표적인 예로는 스프링 부트의 slf4j 가 있다.

 


 

퍼사드 패턴 적용의 장점

- 대표적으로 퍼사드 패턴이 적용된 프레임워크인 slf4j는 log4j, log4j2, logback 등 을 코드 수정 없이 적합한 프레임워크를 slf4j 만의 인터페이스를 통해 info, error 등의 로그 기능을 통합하여 단순하게 사용할 수 있게 해준다.

따라서 slf4j 를 사용하여 Spring Boot 의 로깅 시스템을 구현한다는 가정을 했을경우 log4j2를 사용도중 심각한 보안 이슈가 터졌을 경우 자바 또는 코틀린 코드 수정이 없이 설정 파일 추가만으로 logback 으로 빠르게 이전할 수 있다.

https://kanoos-stu.tistory.com/49 (log4j2 보안문제)

 

[Spring Boot] log4j2 보안 문제 해결 방안 - CVE-2021-44228

발단 및 문제점 2021년 12월 10일 오전 9시 40분 경, 아파치 소프트웨어 재단의 자바로 제작된 Log4j2 에 보안 문제가 발생하였다. 이 문제는 하트블리드과는 비교도 안 될만큼 역사상 최악의 보안 결

kanoos-stu.tistory.com

 

참고로 slf4j 는 interface 이므로 구현이 된 로깅 프레임워크(logback, log4j) 등 없이 단독으로 사용할 수는 없다.

 


slf4j 를 사용하여 로그를 남기는 방법

 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClass {
  private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

  public void doSomething() {
    logger.info("Doing something");
    //logger.debug("Doing something");
    //logger.warn("Doing something");
    //logger.error("Doing something");
    // ...
  }
}

 


 

slf4j 가 퍼사드패턴을 통해 logback bind 받는 과정(코드)

 

1. 자바 또는 코틀린 코드로 slf4j 의 LoggerFactory.getLogger(string) 정적 메서드를 통해 Logback Logger 호출

val logger: Logger = LoggerFactory.getLogger(this::class.java)

 

2. org.slf4j 패키지 LoggerFactory 클래스의 387 줄의 getLogger 메서드에서 getLogger(string) 내부 메서드 호출

public static Logger getLogger(Class<?> clazz) {
    Logger logger = getLogger(clazz.getName());
    if (DETECT_LOGGER_NAME_MISMATCH) {
        Class<?> autoComputedCallingClass = Util.getCallingClass();
        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
            Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                            autoComputedCallingClass.getName()));
            Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
        }
    }
    return logger;
}

 

3. org.slf4j 패키지 LoggerFactory 클래스의 361 줄의 getLogger 메서드에서 getILoggerFactory() 내부 메서드 호출

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

 

4. org.slf4j 패키지 LoggerFactory 클래스의 412 줄의 getILoggerFactory 메서드에서

logback 의존성이 추가되어 초기화 되었다면(SUCCESSFUL_INITIALIZATION)

StaticLoggerBinder.getSingleton().getLoggerFactory() 메서드 호출

초기화가 되지 않았다면 (UNINITIALIZED) performInitialization() -> bind() 메서드를 통해 binding 되어 slf4j 의 구현체로 logback 이 설정되게 된다.

 

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                performInitialization();
            }
        }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
        // support re-entrant behavior.
        // See also http://jira.qos.ch/browse/SLF4J-97
        return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

 

5. org.slf4j.impl (logback) 패키지의 getLoggerFactory() 메서드에서 logback 내부에 설정된 Logger 를 반환한다.

public ILoggerFactory getLoggerFactory() {
    if (!initialized) {
        return defaultLoggerContext;
    }

    if (contextSelectorBinder.getContextSelector() == null) {
        throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
    }
    return contextSelectorBinder.getContextSelector().getLoggerContext();
}

 

위의 코드에서 logback 이 slf4j 에게 바인딩 되는 부분은 logback 라이브러리에 org.slf4j.impl 패키지에 생성된 StatucBinder 클래스들을 생성해놨기 때문이다.