slf4j에 대해서 자세히 설명하는 좋은 블로그가 많이 존재하여 여기서는 loback-spring 을 보고 이해 할 수 있는 정도로만 다루고 넘어가려고 한다.
slf4j는 다양한 로깅 프레임워크에 대한 추상화(인터페이스) 역할을 하기 때문에 배포시에는 원하는 구현체를 선택해서 사용해야 한다. Spring에서는 기본적으로 logback을 사용한다.
실제 로깅을 구성하는 요소
로그 이벤트를 쓰는 작업을 Appender에 위임하여 어디에 출력할지 결정하게 된다.
Appender 에 포함되어 사용자가 지정한 형식으로 표현될 로그 메세지를 변환하는 역할을 담당한다.
아래와 같은 간단한 예시를 통해서 알아보도록 하자.
<configuration>
<timestamp key="BY_DATE" datePattern="yyyy-MM-dd"/>
<property name="LOG_PATTERN"
value="[%d{yyyy-MM-dd HH:mm:ss}:%-4relative] %green([%thread]) %highlight(%-5level) %boldWhite([%C.%M:%yellow(%L)]) - %msg%n"/>
<springProfile name="!prod">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
Property는 변수를 저장하는 공간이며 Log pattern을 통해서 출력 형식을 정하고 로그 색깔 또한 지정할 수 있다.
SpringProfile은 실행시에 입력받은 Profile에 맞춰 로깅 형식을 지정해 줄 수 있다. 또 한 위에서 알아본 Layout이 로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream에 쓰는 작업을 해준다.
참고 링크를 확인해보면 Log를 다루기 위해서 @ 어노테이션을 사용했을 때 일어나는 일에 대해서 나타내고 있다. 그중에서 우리는 @Slf4j는 아래와 같이 컴파일 시에 코드가 추가 되는 것을 알 수 있다.
private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
쿼리 문장을 Console창에서 확인하고자 하는 경우 해당 쿼리 결과를 log를 찍는 방법과 log4jdbc를 사용해서 해당 쿼리를 logging 할 수 있다.
자세한 방법은 해당 링크를 참고하면 해당 프로젝트에서 쉽게 logging 할 수 있을 것이다. 그 중에서 아래와 같이 sql 및 sql time을 잴 수 있는 log를 쉽게 생성할 수 있다는 점만 집고 넘어가자.
<logger name="jdbc.sqlonly" level="OFF" />
<logger name="jdbc.sqltiming" level="${LOG_LEVEL}" additivity="false"/>
static logger를 가지고 있는 클래스를 상속하는 것과 @Slf4j는 로깅 기능을 구현하는 두 가지 다른 방법이 있다.
이 방법은 일반적으로 로깅을 위해 static 필드로 구성된 로거를 사용하는 전통적인 방법이다.
자식 클래스는 상속된 로거를 통해 로그를 기록하거나 조작할 수 있지만 이 방법은 몇 가지 단점을 가지고 있다.
상속을 통해 로거를 사용하므로, 상속 계층 구조에 제약이 생긴다. 로깅 기능을 사용하려는 클래스가 이미 다른 클래스를 상속하고 있다면, 추가로 상속받을 수 없다.
로거가 static 필드로 정의되므로, 동시에 여러 인스턴스에서 로거를 사용할 수 없다.
앞에서 알아봤듯이 @Slf4j는 Lombok 라이브러리에서 제공하는 애너테이션으로, 로깅 기능을 자동으로 추가해주는 기능이다.
클래스에 @Slf4j 어너테이션을 추가하면, 해당 클래스 내에서 로거를 사용할 수 있다. 이 방식의 장점은 다음과 같다.
상속이 필요하지 않게 된다. @Slf4j를 사용하려는 클래스는 어떤 클래스를 상속받아도 상관없다. 이는 상속 계층 구조에 제약이 없음을 의미한다.
클래스의 어느 곳에서나 로거를 사용할 수 있다. 로거는 클래스의 인스턴스에 종속되지 않으므로, 멀티스레드 환경에서도 안전하게 사용할 수 있다.
사실 이 부분에 대해서 기록하고자 앞에 서두를 길게 작성하였다.
간단하게 start.spring.io에서 프로젝트를 받고 별도로 lombok을 설치해 주었다.
@Slf4j
@RestController
public class LogTestController extends BaseLog{
@RequestMapping("/log-test")
private String logTest(){
String name = "Spring";
log.info(name);
return "ok";
}
}
위와 같이 Controller를 설정해준 다음 상속을 통한 logger 구현을 위해 다음과 같이 설정을 해준다.
// BaseLog.class
public class BaseLog {
private final Logger Logger = LoggerFactory.getLogger(this.getClass());
public LogFunction log = new LogFunction(Logger);
}
//LogFunction.class
@Component
@RequiredArgsConstructor
public class LogFunction {
private Logger logger;
public LogFunction(Logger plogger){
logger = plogger;
}
public void info(String msg) {
logger.info("Demo" + msg);
}
}
이런 경우에 @slf4j 의 log가 찍힐 것인지 아니면 부모의 log가 찍혀 Demo + msg~ 가 나올지 확인해보면 다음과 같다.
[2023-06-28 11:22:23:10055] [http-nio-8080-exec-3] INFO [com.example.demo.LogTestController.logTest:15] - Spring
이는 부모의 log값이 아닌 @slf4j의 log가 찍히는 것을 확인 할 수 있다. 돌아보면 당연한 것이긴 한데 자바에서 부모의 메소드, 필드를 참조할 수 있지만 만일 동일한 이름으로 자식 클래스에서 선언하게 되는 경우 자식의 필드값으로 변경이 된다.