logback 홈페이지의 매뉴얼을 읽으며 내용들을 정리한 글입니다.
Logback의 구조
Logback 아키텍처는 Generic 하기 때문에 다양한 상황에서 이용될 수 있습니다. Logback은 아래의 세 가지의 모듈로 나뉩니다.
- logback-core
- logback-classic
- logback-access
logback-core는 다른 두 모듈을 위한 기반 역할을 하는 모듈입니다.
logback-classic은 logback-core에서 확장된 모듈로, log4j에서 매우 향상된 버전에 해당합니다. logback-classic은 기본적으로 SLF4J API를 구현하여 log4j 또는 Java.util.loggin(JUL)과 같은 다른 로깅 시스템을 쉽게 전환할 수 있도록 합니다.
logback-access는 Servlet Container와 통합되어 HTTP 액세스에 대한 로깅 기능을 제공합니다.
logback-classic
Logback은 Logger, Appender, Layout 이 세 가지의 기본 클래스로 구성되어 있습니다. 이 세 가지 Component는 개발자가 타입이나 레벨에 따라 메시지를 기록할 수 있도록 하고 이런 메시지들이 런타입에 어떤 형식으로, 어디에 출력될지 제어할 수 있도록 합니다.
Logger 클래스는 logback-classic 모듈에 속하고, Appender와 Layout 인터페이스는 logback-core 모듈에 속하기 때문에, logback-core에는 logger의 개념이 없습니다.
Logger context
System.out.println을 통한 출력에 비해 Logger API가 갖는 가장 큰 장점은 환경이나 사용에 따라 특정 메시지를 비활성화시킬 수 있다는 점입니다. 개발자는 이러한 기능을 통해 모든 로그에 대해서 분류하거나 컨트롤할 수 있습니다. Logger는 상위 계층인 LoggerContext에 부착되며, 각각의 Logger는 로그를 만들어 낼 수 있습니다.
Logger는 하나의 엔티티이며 계층을 갖습니다. 이러한 계층은 이름 규칙에 따라 파악할 수 있습니다. 로거의 이름 뒤에 dot(.)이 있다면 dot 이전의 prefix 이름을 가진 로거가 상위 로거가 됩니다. 예를 들면 "com.foo"라는 로거는 "com.foo.Bar"의 상위 로거입니다. Root Logger는 로거 계층의 맨 위에 존재합니다. 이 로거는 다음과 같은 코드를 통해 가져올 수 있습니다.
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
모든 로거는 org.slf4j.LoggerFactory의 getLogger 메서드를 통해 가져올 수 있습니다. 메서드의 파라미터로는 원하는 로거의 이름이 전달됩니다. getLogger 메서드를 통해 가져오는 Logger 인터페이스의 형태는 다음과 같습니다.
package org.slf4j;
public interface Logger {
// Printing methods:
public void trace(String message);
public void debug(String message);
public void info(String message);
public void warn(String message);
public void error(String message);
}
logback-classic모듈의 ch.qos.logback.classic.Level 클래스에는 로거가 사용 가능한 다섯 가지 레벨인 TRACE, DEBUG, INFO, WARN, ERROR가 정의되어 있습니다. 로거에 레벨이 정해지지 않았더라면 상위 로거의 레벨을 상속받으며 기본 레벨은 DEBUG입니다.
로그의 레벨은 아래와 같으며, 지정된 레벨 이하의 메서드 호출은 기록되지 않습니다.
TRACE < DEBUG < INFO < WARN < ERROR
예를 들어 INFO 레벨로 지정한 로거는 INFO, WARN, ERROR 로그만 기록하게 된다.
LoggerFactory.getLogger 반환 Logger
앞서 LoggerFactory.getLogger 메서드를 통해 원하는 이름에 로거를 가져올 수 있다고 하였습니다. 그렇기에, 파라미터로 전달된 값이 같다면 반환하는 로거도 정확히 같은 오브젝트입니다.
주로 Logger의 이름은 software component를 따라 지정하게 됩니다. 예로, 한 클래스에서 사용하는 로거는 그 클래스의 정규화된 이름과 동일하게 인스턴스화 하여 사용합니다. 로그 출력에는 생성 로거의 이름이 표시되므로 이러한 로거 명명 규칙을 통해 로그 메시지의 출처를 쉽게 식별할 수 있습니다.
Appenders와 Layouts
로깅을 선택적으로 허용/비허용 하는 것은 하나의 기능을 뿐입니다. Logback은 로깅을 다양한 destinations에 대하여 출력하도록 지정할 수 있습니다. Logback에서는 이러한 destination을 Appender라고 명명합니다. 현재 Appender의 종류는 콜솔(console), 파일(files), 원격 소켓 서버(remote socket servers), DB(MySQL, PostgreSQL,...)등이 있습니다.
하나 이상의 Appender는 하나의 Logger에 부착될 수 있습니다 (반대로, 하나의 Logger는 여러 개의 하나 이상의 Appender를 가진다고 생각하는 게 더 이해에 좋을 것 같습니다).
addAppender 메서드를 통해 Logger에 appender를 추가할 수 있습니다. 특정 로거로 들어오는 로깅 요청은 자신을 포함한 상위 계층의 로거의 모든 Appender에게 전달되게 됩니다 (Appender Additivity). 예를 들어 ConsoleAppender가 RootLogger에 지정되었다면 모든 로깅 요청은 적어도 console에는 출력됩니다.
사용자들은 출력 destinations를 정의하는 것 이외에 출력 포맷을 수정하고 싶을 수 있습니다. 이를 만족시켜줄 component가 바로 Layout입니다. Layout은 사용자의 요청에 따라 로그 메시지의 포맷을 지정해줍니다. PatternLayout은 C언어의 pringf 함수처럼 로그를 출력하도록 도와줍니다.
파라미터를 이용한 로깅
logback-classic의 Logger는 SLF4J API를 implements 합니다. 그렇게 print 방식에 여러 파라미터를 허용하도록 하였습니다. 방식은 다음과 같습니다.
Object entry = new SomeObject();
// logback {} 과 parameter를 방식을 이용한 로깅 - (1)
logger.debug("The entry is {}.", entry);
// '+'로 문자열 생성을 통한 로깅 - (2)
logger.debug("The entry is " + entry + ".");
문자열에서 중괄호로 ('{ }') 묶은 부분이 등장하면 해당 부분은 뒤이어 나오는 파라미터로 대체됩니다 (1). Logback이 선택한 파라미터를 이용한 로깅은 기존의 '+' 연산자를 이용한 문자열 생성 후 전달하는 방식(2)에 비해 속도에 유리함을 갖습니다.
차이점은 logger.debug의 호출 순서입니다. (1) 방법은 logger.debug 메서드 내부에서 파라미터를 검증하고 출력합니다. 반면 (2) 방법은 '+' 연산자를 이용한 문자열 생성 이후 logger.debug 메서드가 실행됩니다.
이 순서는 로깅 레벨에 따라 성능 상 차이를 보입니다. 지정한 로깅 레벨이 INFO라면 logger.debug 메서드는 실행 목록에서 제외됩니다. 하지만 (2) 방법을 사용했다면 호출되지도 않을 메서드의 파라미터 생성에 비용이 들게 됩니다.