카테고리 없음

Spring(6) - Logger

HOONY_612 2021. 8. 19. 11:08
반응형

안녕하세요. 이번 포스팅은 Logger에 대한 포스팅을 해보려고 합니다.

원래 System.out.println()함수로 거의 출력을 확인했습니다. 그러나 이것은 잘못된 방식이라는 것을 알았습니다.

모든 실무부분에서는 Log라는 것을 이용하여 상태를 출력해 확인한다고합니다.

그럼 Log에 대해서 자세히 알아보겠습니다.

 

Logger란 ?

Logger는 프로그램 개발 중 발생할 수 있는 오류를

디버깅 또는 프로그램상태를 모니터링하기 위해 필요한 정보를 기록하는 것.

 

System.out.println을 사용하지 않는 이유

System.out.println는 출력 레벨을 정해 줄 수 없고 로그를 파일에 담기 까다롭습니다.

그래서 실제로는 다양한 로그 프레임워크를 이용하여 로그를 기록합니다.

프레임워크의 중심에는 SLF4J가 있습니다. 이것은 추상화 된 인터페이스로 역할을 합니다.

그래서 SLF4J를 중심으로 다양한 프레임워크를 사용 할 수 있습니다.

초기에는 아래의 그림과 같이 JCL인터페이스를 사용했지만 메모리 누수, 클래스 로더의 문제 등으로

현재는 SLF4J그것의 구현체인 LogBack프레임워크를 사용하고 있습니다.

 

 

기본적으로 스프링부트는 Logback프레임워크를 사용하고 있습니다.

spring-boot-starter-web을 build하면 자동으로 logback-classic, logback-core라이브러리가 생성됩니다.

그럼 @Slf4j를 선언하면 어떤 일이 일어나는 것일까요?

선언 시 Lombok이라는 라이브러리를 이용합니다.

그리고 라이브러리 내에서는 아래와 같은 예시로 Logger를 생성해줍니다.

public class LogExample {
       private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
}

그럼 밑의 LoggerLoggerFactory의 정체를 알아볼까요?

 

 

1. Logger

 

Logger은 인터페이스로 되어있습니다. 추상화 된 메소드는 여러개가 있지만 크게 5가지로 나뉩니다.

바로 로그의 상태를 지정할 수 있습니다. 이러한 인터페이스를 LoggerFactory가 구현합니다.

 

public interface Logger {
	public String getName();
	public void trace(String msg);
	public void debug(String msg);
	public void info(String msg);
	public void warn(String msg);
	public void error(String msg);
}

 

2. LoggerFactory

 

LoggerFactory는 클래스입니다. 저희는 getLogger를 이용하여 Logger라는 객체를 얻어와야합니다.

그래서 LoggerFactory내의 아래의 메소드를 사용하게 됩니다.

그러면 getLogger(String logger)를 통해서 SubstituteLoggerFactory클래스를 생성하게 됩니다.

 

<getLogger>

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;
 }

<SubstituteLoggerFactory>

public class SubstituteLoggerFactory implements ILoggerFactory {

    boolean postInitialization = false;
    
    final Map<String, SubstituteLogger> loggers = new HashMap<String, SubstituteLogger>();

    final LinkedBlockingQueue<SubstituteLoggingEvent> eventQueue = new LinkedBlockingQueue<SubstituteLoggingEvent>();

    synchronized public  Logger getLogger(String name) {
        SubstituteLogger logger = loggers.get(name);
        if (logger == null) {
            logger = new SubstituteLogger(name, eventQueue, postInitialization);
            loggers.put(name, logger);
        }
        return logger;
    }

    public List<String> getLoggerNames() {
        return new ArrayList<String>(loggers.keySet());
    }

    public List<SubstituteLogger> getLoggers() {
        return new ArrayList<SubstituteLogger>(loggers.values());
    }

    public LinkedBlockingQueue<SubstituteLoggingEvent> getEventQueue() {
        return eventQueue;
    }

    public void postInitialization() {
    	postInitialization = true;
    }
    
    public void clear() {
        loggers.clear();
        eventQueue.clear();
    }
}

최종적으로는 synchronized public Logger getLogger(String name)라는 함수에 의해서

Logger를 생성하게 됩니다. 그리고 그것을 반환받게 되는 것이죠. 이렇게 Logger를 반환받았습니다.

그럼 이렇게 Logger를 완성시켜 준 SLF4J에 대해서 알아보겠습니다.

 

System.out.println("log = " + log.getClass());
//log = class ch.qos.logback.classic.Logger

 

SLF4J구조

 

기본적으로 SpringBoot는 APISlf4j, Binding(구현체)Logbag프레임워크를 사용하고 있습니다.

일단 Slf4j는 인터페이스이므로 다양한 프레임워크와 Binding작업이 필요합니다.

아래의 .jar파일로 바인딩되어집니다. 그리고 프레임워크를 바꾸려면 jar파일을 바꿔주면 됩니다.

 

To switch logging frameworks, just replace slf4j bindings on your class path. For example, to switch from java.util.logging to log4j, just replace slf4j-jdk14-1.7.32.jar with slf4j-log4j12-1.7.32.jar.

 

 

그리고 주의해야 할 점은 하나의 Slf4j API하나의 Bingding만 존재해야한다는 것입니다.

예를 들어 Logback을 보시면 slf4j-api.jar에 하나의 바인딩만 존재하는 것을 볼 수 있습니다.

SLF4J는 크게 3가지 모듈로 구성됩니다.

첫 번째는 다른 로깅프레임워크가 접근 할 경우 Slf4j로 동작하도록 만들어 주는 어댑터역할을 하는 Bridge,

Slf4j의 API, 그것을 구현해 실제 동작을하는 Binding으로 3가지로 나뉩니다.

위에서 Binding과 API에 대한 개념을 설명했으니 Bridge모듈에 대해서 알아보겠습니다.

 

Bridge

 

그림과 같이 Slf4j가 아닌 다른 프레임워크가 접근하였을 때 Bridge를 통하여 Slf4j로 동작하도록만들 수 있게 역할을합니다.

순서는 다른 로깅 API → Bridge → SLF4J API로 동작하게 됩니다.

그럼 Bridge의 종류에 대해서 살펴볼까요?

jcl-over-slf4j.jar JCL API에 의존하는 클래스들을 손상시키지 않고 JCL로 들어오는 호출을 JCL-over-SLF4J를 이용해서 SLF4J API를 호출한다. 즉, 이 모듈을 사용하면 JCL을 사용하는 기존 소프트웨어와의 호환성을 손상시키지 않으면서 프로젝트를 SLF4J로 단편적으로 마이그레이션할 수 있다.
log4j-over-slf4j.jar 모듈을 사용하면 log4j 호출을 SLF4J API로 리다이렉션 할 수 있다.
jul-to-slf4j.jar 모듈을 사용하면 java.util.loggingg호출을 SLF4J API로 리다이렉션 할 수 있다.

 

이렇게 전체적인 Slf4j의 구조를 살펴봤습니다. 그럼 필요한 Dependency를 한 번 정리해보겠습니다.

 

Dependency

Slf4j API 인터페이스

// https://mvnrepository.com/artifact/org.apache.maven/maven-plugin-api
implementation 'org.apache.maven:maven-plugin-api:3.8.1'

Slf4j Binding(.jar)

//boot-starter-web 빌드 시 자동으로 생성
implementation 'org.springframework.boot:spring-boot-starter-web'
logback-classic-1.2.4.jar
//log4j
implementation 'org.slf4j:log4j-over-slf4j:2.0.0-alpha4'

Logging Framework지정

logback-core-1.2.4.jar로 지정. 실제 로깅 동작 할 프레임워크를 지정해줍니다.

 

Bridge모듈 지정

log4j-over-slf4j, jcl-over-slf4j, jul-to-slf4j 외부 프레임워크에 알맞게 지정해주면됩니다.

 

 

 

이렇게 Slf4j의 동작 및 Logger, LoggerFactory에 대해서 살펴봤습니다.

이제 로그를 사용할 때 원리를 알고 사용할 수 있어서 더 많은 활용을 할 수 있을거라 기대됩니다.

독자들도 이 글을 읽고 많은 도움되었으면 좋겠습니다. 긴 글 읽어주셔서 감사합니다.

 

반응형