spring boot logging
spring boot의 로깅을 알아볼려한다. 예전에 백기선님이 스프링 캠프에서 발표한 내용을 참고하여 정리했다.
예전에 한번 봤었는데 기억이 가물가물에서 아예 정리를 해야겠다.
spring은 기본적으로 JCL을 사용한다.
spring bean 라이브러리에
DefaultSingletonBeanRegistry
클래스의 일부분
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
...
protected final Log logger = LogFactory.getLog(getClass());
...
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean " + beanName + "");
}
하지만 근래에는 JCL보다 slf4j를 더 많이 쓴다고 관련해서는 백기선님 발표자료를 참고.
JCL과 slf4j는 같은 추상 로깅 라이브러리다.
동작방식이 약간만 다를뿐...
그럼 어떻게 로깅을 통합하고 관리하는지 알아보자.
일단 slf4j에는 기본적으로 3가지 라이브러리가 있다.
첫 번째론
slf4j api
라이브러리가 있다.
이 라이브러리는 그냥 기본 인터페이스 껍대기라 한다.
거기서보면 실제 클래스패스에서 log 라이브러리를 가져오는 곳으로 보이는 함수가 있다.
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
logback classic 에 보면
org/slf4j/impl/StaticLoggerBinder
클래스가 있다.
아무튼 일단 slf4j api는 껍대기 인터페이스라 생각하면 되겠다.
두번째론
slf4j binding
이라는 라이브러리다.
실직적으로 slf4j api 의 구현체들이다.
위에서 언급한 logback classic 도 binding 이다.
logback classic 의 Logger 클래스의 일부분이다.
public final class Logger implements org.slf4j.Logger, LocationAwareLogger,
AppenderAttachable<ILoggingEvent>, Serializable {
...
public void debug(String format, Object arg) {
filterAndLog_1(FQCN, null, Level.DEBUG, format, arg, null);
}
}
이외에도
slf4j-log4j12 : log4j binding
slf4j-jdk14 : java.util.logging binding 기본 자바 패키징
slf4j-jcl : 아파치 common logging binding
등이 있다.
logback만 logback-classic이다. 흠
세번째는
slf4j bridge
라이브러리다.
레거시들을 위한 라이브러리다.
예전에 개발할때는 직접적으로 로그들을 선택해서 쓰거나 혹은 JCL을 썼다.
그래서 그 라이브러리들을 slf4j로 호출하도록 해주는 라이브러리이다.
그래야 내가 원하는 로그들로 갈테니...
아주 좋은 라이브러리다.
만약에 어떤 라이브러리에서 log4j를 직접적으로 쓴다고 가정하자.
그럼 log4j -> slf4j-log4j-bridge -> slf4j-api -> slf4j-binding(내가 원하는 로그 binding) -> 내가 원하는 로그 라이브러리(logback, jul, jcl)
이런 방식이다.
만약 같은 종류의 브릿지와 바인딩을 쓰면 어떻게 되나
만약 log4j를 쓴다고 가정하면 log4j -> slf4j-log4j(bridge) -> slf4j-api -> slf4j-log4j(binding) -> log4j -> sl4j->log4j(bridge) -> slf4j-api -> slf4j-log4j(binding)..... ????
무한로프에 빠지게 된다. 같은 종류의 브릿지와 바인딩을 쓰면 안된다.
그것만 주의하자!!
아래는 스프링 boot의 기본 로깅 이다.
한개의 binding 라이브러리와 세개의 bridge 라이브러리가 존재한다.
동일한 브릿지와 바인더는 보이지 않는다.
만약 boot의 기본 라이브러리인 logback 말고 log4j를 쓰고 싶다면 어떻게 할까
저기 발표내용에도 나와있지만
spring boot logging
여기에 자세히 나와있다.
<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>
을 추가 해주자
그럼 위와 같이 변경이 되어있을 것이다.
slf4j-log4j 에도
org/slf4j/impl/StaticLoggerBinder
클래스가 존재한다.
log4j를 쓰면서 JCL 코드를 호출 해보았다.
SLF4JLocationAwareLog
클래스를 호출하였다.
slf4-bridge다. 정확한 명칭은 jcl-over-slf4j 이다. 그리고 slf4j-api의 구현체인
Log4jLoggerAdapter
클래스를 호출하였다.
그런다음에 실질적인 logj4를 호출 하였다.
순서를 대략 이렇다.
info 기존이다.
jcl-over-slf4j(bridge) 라이브러리의 SLF4JLocationAwareLog 클래스
public void info(Object message) {
logger.log(null, FQCN, LocationAwareLogger.INFO_INT, String.valueOf(message), null, null);
}
slf4j-log412(binding) 의 Log4jLoggerAdapter 클래스(slf4j-api 구현체)
public void log(Marker marker, String callerFQCN, int level, String msg, Object[] argArray, Throwable t) {
Level log4jLevel = toLog4jLevel(level);
logger.log(callerFQCN, log4jLevel, msg, t);
}
log4j 의 Category 클래스
public void log(String callerFQCN, Priority level, Object message, Throwable t) {
if(repository.isDisabled(level.level)) {
return;
}
if(level.isGreaterOrEqual(this.getEffectiveLevel())) {
forcedLog(callerFQCN, level, message, t);
}
}
이런 방식으로 로그를 찍는다. 저기 백기선님이 설명을 잘해주셔서 도움이 많이 되었다.
만약 우리가 라이브러리를 만든다면 실직적으로 slf4j-api만 사용하면 된다.
나머지만 해당 어플리케이션 개발자가 무엇을 쓸지 선택하기만 하면 된다.
이렇게 로그에 대해서 알아봤다.
추가 : 만약 동일한 브릿지 바인딩이 클래스 패스에 있을경우 서버 시작시 에러를 낸다. jcl, log4j 테스트했음!