Logback은 인기있는 프로젝트인 log4j의 후속 프로젝트로써 log4j가 떠나는 위치를 이어받기 위해 탄성하였다.
Logback 아키텍처는 다른 환경/상황에서도 적용 가능할 정도로 일반적이다. 현재 logback은 logback-core
, logback-classic
, logback-access
의 세 가지 모듈로 나뉘어져있다.
logback-core
모듈은 다른 두 모듈의 기반이된다. logback-classic
모듈은 코어를 확장하는데, 이는 log4j의 매우 발전한 형태라고 생각하면 된다. 또한, logback-classic
은 기본적으로 SLF4J API를 구현하므로 log4j
나 java.util.logging (JUL)
와 같은 로깅 프레임 워크 사이에서 쉽게 전환 할 수 있다.
logback-access
모듈은 Tomcat이나 Jetty 같은 서블릿 컨테이너와 통합돼 HTTP-access 로그 기능을 제공한다. logback-core
위에는 자체 모듈을 쉽게 구축 할 수 있다.
ogback의 기본 아키텍처는 여러 상황에서 적용 가능할 정도로 일반화 되어있다. 현재 logback은 logback-core
, logback-classic
, logback-access
의 세 가지 모듈로 나뉘어져있다.
코어 모듈은 다른 두 개의 모듈을 위한 기반이다. logback-classic
모듈은 코어를 확장하는데, 이는 log4j의 매우 발전한 형태라고 생각하면 된다. 또한, logback-classic
은 기본적으로 SLF4J API를 구현하므로 JDK1.4에 도입된 log4j
나 java.util.logging (JUL)
와 같은 로깅 프레임 워크 사이에서 쉽게 전환 할 수 있다. 엑세스라는 세 번째 모듈은 서블릿 컨테이너와 통합돼 HTTP-access 로그 기능을 제공한다. 엑세스 모듈 설명서는 여기에 포함되어있다.
logback은 세 가지 주요 클래스(Logger, Appender, Layout)을 기반으로 구현되어있다. 이 세 가지 컴포넌트가 함께 작동함으로써 개발자는 메세지 타입이나 레벨에 따라 이를 기록할 수 있으며 메세지의 포맷과 리포팅 위치를 제어할 수 있다.
Logger
클래스는 logback-classic
모듈의 일부인 반면에 Appender
와 Layout
인터페이스는 logback-core
의 일부이다. 범용 모듈인 logback-core
는 logger에 대한 개념이 없다.
일반적인 System.out.println
에 비해 logging API의 첫 번째이자 가장 중요한 장점은 다른 로그 문들을 방해하지 않고 특정 로그 문을 비활성화 할 수 있다는 것이다. 이 기능은 로깅 공간, 즉 모든 가능한 로깅 문서 공간이 개발자가 선택한 일부 기준에 따라 분류된다고 가정한다. logback-classic에서 이러한 분류는 로거의 고유한 부분이다. 모든 싱글 로거는 로거를 만들고 계층 구조와 같은 트리에 이를 배치하는 역할을 가진 LoggerContext
에 연결된다.
로거는 명명된 엔티티이며, 이 때 이름은 대소문자를 구분하며 계층적 명명 규칙을 따른다.
Named Hierarchy
로거의 이름이.(dot)
에 의해 하위 로거 이름의 접두어가 될 경우 이는 다른 로거의 조상이다. 사진과 하위 로거 사이에 조상이 없는 경우에는 아는 하위 로거의 부모가 된다.
예를 들어, com.foo
인 로거는 com.foo.Bar
인 로거의 부모이다. 마찬가지로 java
는 java.util
의 부모이자 java.util.Vector
의 조상이다.
루트 로거는 로거 계층의 맨 위에 있다. 시작점부터 모든 계층의 일부라는 점에서 예외적이다. 모든 로거와 마찬가지로 다음과 같이 이름으로 가져올 수 있다. Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
다른 모든 로거 또한 org.slf4j.LoggerFactory
클래스에서 찾을 수 있는 static getLogger
메소드로 가져올 수 있다. 이 메소드는 가져올 로거의 이름을 매개 변수로 사용한다. Logger
interface의 기본 메소드 중 일부는 다음과 같다. :
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);
}
로거에는 level
이 지정 될 수 있다. 가능한 레벨들 (TRACE, DEBUG, INFO, WARN, ERROR)은 ch.qos.logback.classic.Level
클래스에 정의되어 있다. Logback에서 Level
은 final
클래스이며 유연한 접근 방식인 Marker
오브젝트의 형식으로 존재하기 때문에 하위 클래스가 될 수 없다.
만약 주어진 로거가 레벨이 지정되지 않았다면 지정된 레벨을 가진 가장 가까운 조상에서 상속받는다. 즉, 주어진 로거 L
의 유효 레벨은 L
로부터 root
로거를 향해 계층 구조를 거슬러 올라가면서 만나는 첫 번째 널이 아닌 레벨과 같다.
모든 로거가 결국 레벨을 상속 받을 수 있도록 root 로거에는 항상 지정된 레벨이 있다.( default : DEBUG )
다음은 다양한 계층에 할당된 레벨 값과 상속 규칙에 따른 결과적인 레벨에 대한 예시이다.
🔎 Example 1. 루트 로거만 레벨이 명시되어 있다. DEBUG
인 이 레벨은 다른 로거인 X
, X.Y
, X.Y.Z
에 상속된다.
Logger name | Assined level | Effective level |
---|---|---|
root | DEBUG | DEBUG |
X | none | DEBUG |
X.Y | none | DEBUG |
X.Y.Z | none | DEBUG |
🔎 Example 2. 모든 로거가 레벨이 명시되어 있다. 레벨 상속은 이뤄지지 않는다.
Logger name | Assined level | Effective level |
---|---|---|
root | ERROR | ERROR |
X | INFO | INFO |
X.Y | DEBUG | DEBUG |
X.Y.Z | WARN | WARN |
🔎 Example 3. root
, X
, X.Y.Z
가 각각 DEBUG
, INFO
, ERROR
레벨을 명시하고 있다. 로거 X.Y
계층의 레벨은 부모인 X
로 부터 상속 받는다.
Logger name | Assined level | Effective level |
---|---|---|
root | DEBUG | DEBUG |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | ERROR | ERROR |
🔎 Example 4. root
, X
가 DEBUG
, INFO
레벨을 명시하고 있다. 로거 X.Y
, X.Y.Z
는 가장 가까운 부모인 X
로부터 레벨을 상속받는다.
Logger name | Assined level | Effective level |
---|---|---|
root | DEBUG | DEBUG |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | none | INFO |
정의에 따라, 출력 방식이 로깅 요청 레벨을 결정한다. 예를 들어 L
이라는 로거 인스턴스가 있을 때 L.info("..")
명령문은 INFO 레벨의 로깅 명령문이다.
로깅 요청 레벨이 해당 로거의 유효 레벨 이상인 경우에만 사용가능하며 그렇지 않으면 요청은 비활성화된다. 위에서 말했듯, 할당된 레벨이 없는 로거는 가장 가까운 조상에서 로거를 상속 받는다.
Basic Selection Rule
유효 레벨q
를 갖는 로거에 발행된 레벨p
의 로그 요청은p>=q
인 경우에만 사용 가능하다.
이 규칙은 logback의 핵심이며 레벨은 다음과 같이 정렬된다. (아래로 갈수록 더 많은 정보를 포함함. 로그 요청 레벨(p)은 로거의 레벨(q)과 같거나 우측에 위치해야한다.)
⭐️ TRACE < DEBUG < INFO < WARN < ERROR ⭐️
다음 표에서 세로 헤더는 로깅 요청의 레벨(p)이고 가로 헤더는 로거의 유효 레벨(q)이다. 행(요청 레벨)과 열 (유효 레벨)의 교차는 기본 선택 규칙에 의한 boolean 결과이다.
여기 기본 선택 규칙에 대한 예시가 있다. :
import ch.qos.logback.classic.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
....
// "com.foo" 라는 이름의 로거를 얻는다.
// 추가적으로 로거가 "ch.qos.logback.classic.Logger" 타입이라 가정했을 때,
// 우리는 이것의 레벨을 설정할 수 있다.
ch.qos.logback.classic.Logger logger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
// 이 로거의 레벨을 INFO로 지정하였다.
// setLevel() 메소드는 logback logger가 필수적이다.
logger.setLevel(Level.INFO);
// WARN >= INFO이므로 이 요청은 수행된다.
logger.warn("Low fuel level.");
// DEBUG < INFO이므로 이 요청은 수행되지 않는다.
logger.debug("Starting search for nearest gas station.");
Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");
// "com.foo.Bar" 라는 이름의 로거 인스턴스는 "com.foo"라는 이름의 로거로부터 레벨을 상속 받는다.
// 따라서 INFO >= INFO("com.foo"의 명시된 레벨 = com.foo.Bar"가 상속받은 레벨) 다음의 요청은 수행된다.
barlogger.info("Located nearest gas station.");
// DEBUG < INFO이므로 다음 요청은 수행되지 않는다.
barlogger.debug("Exiting gas station search");
이름이 같은 LoggerFactory.getLogger 메소드를 호출하면 항상 동일한 로거 오브젝트에 대한 참조(reference)가 리턴된다.
Logger x = LoggerFactory.getLogger("wombat");
Logger y = LoggerFactory.getLogger("wombat");
즉, x와 y는 정확히 동일한 로거 객체를 가리킨다.
따라서 로거를 구성한 다음 reference를 전달하지 않고 코드의 다른 곳에서 동일한 인스턴스를 가져 올 수 있다. 또한, logback logger는 순서(부모객체-자식객체..)에 상관없이 만들어지고 구성 될 수 있다. 특히 "부모" 로거는 이후에 인스턴스화 된 경우에도 자신의 하위 항목을 찾아서 링킹 할 수 있다.
logback 환경 구성은 일반적으로 어플리케이션의 초기화시에 수행된다. 선호되는 방식은 configuration 파일을 읽는 것이다. 이 접근법은 곧 논의 될 것이다.
logback을 사용하면 간단하게 소프트웨어 컴포넌트별로 로거 이름을 지정 할 수 있다. 클래스의 유효한 이름과 동일한 로거 이름을 이용해 각 클래스의 로거를 인스턴스화 시킴으로써 이를 가능하게 한다. 이것은 로거를 정의하는 유용하고 간단한 방법이다. 로그 출력에 이를 생성한 로거의 이름이 있으므로 이름 지정 전략을 사용하면 로그 메세지의 출처를 쉽게 식별 할 수 있다. 하지만 이는 로거의 이름을 지정하기위한 하나의 전략일 뿐이며 logback은 가능한 로거의 설정을 제한하지 않는다. 개발자는 원하는대로 자유롭게 로거의 이름을 지정 할 수 있다.
그럼에도 불구하고, 클래스 생성 후에 로거의 이름을 지정하는 것이 가장 잘 알려진 일반적인 전략이긴하다.
그들의 로거를 기본으로 로깅 요청을 선택적으로 활성화 혹은 비활성화하는 기능은 picture의 일부일 뿐이다. logback을 통해 로깅 요청을 다양한 대상으로 print 할 수 있다. logback에서 출력 대상은 appender라고 불린다. 현재 콘솔, 파일, 원격 소켓 서버, MySQL/PostgreSQL/Oracle 등과 같은 데이터베이스, JMS, 원격 UNIX Syslog daemon에 대한 appender가 있다. 로거에 하나 이상의 appender을 추가 할 수 있다.
addAppender
메소드는 주어진 로거에 appender를 추가한다. 지정된 로거에 대해 사용 가능한 각 로깅 요청은 해당 로거 뿐만아니라 계층상 더 높은 로거의 appender에게도 전달된다. 즉, 어펜더는 로거 계층에서 추가적으로 상속된다. 예를 들어, 콘솔 appender가 root 로거에 추가되면 사용 가능한 모든 로깅 요청이 최소한 콘솔에서 출력된다. 또한 파일 appender가 로거 L
에 추가되면 L
과 L
의 하위 로거에 대해 사용 가능한 로깅 요청이 파일(L
에서 추가) 및 콘솔(root
에서 추가)에 출력된다. 로거의 additivity 플래그를 false
로 설정하여 appender가 누적되는 것을 막을 수 있다.
appender의 추가에 대한 규칙은 아래와 같다.
🔎 Appender Additivity
로거L
의 로그 명령문의 출력은L
과L
의 모든 조상의 appender로 이동한다. 이것이 "appender additivity"라는 용어의 의미이다.하지만
L
의 조상 로거인P
가additivity
플래그를false
로 했다면,L
의 출력은L
를 비롯한P
까지의 모든 조상의 모든 appender에 이뤄지고P
이상의 조상의 appender는 사용되지 않는다.
로거의additivity
플래그의 기본값은true
이다.
Logger Name | Attached Appenders | Additivity Flag | Output Targets | Comment |
---|---|---|---|---|
root | A1 | not applicable | A1 | 루트 로거는 로거 계층의 최상위이므로 Additivity Flag가 적용되지 않는다. |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | x 와 root 의 Appenders |
x.y | none | true | A1, A-x1, A-x2 | x 와 root 의 Appenders |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | x , x.y.z , root 의 Appenders |
security | A-sec | false | A-sec | Additivity Flag가 false이므로 appender가 누적되지않는다. 따라서 security 의 appender인 A-sec 만 사용된다. |
security.access | none | true | A-sec | security 의 Additivity Flag가 false이므로 security 의 Appender만 사용된다. |
종종 사용자는 출력 대상 뿐만 아니라 출력 형식 또한 커스텀 마이징 하길 원한다. 이는 appender와 함께 레이아웃을 연관시킴으로써 수행된다. 레이아웃은 사용자의 요구에 따라 로깅 요청의 포맷을 정의하는 반면, appender는 포맷이 갖춰진 출력을 목적지로 전송한다. logback이 일반적으로 제공하는 PatternLayout
을 통해 C 언어의 printf
함수와 유사한 변환 패턴을 따라 출력 형식을 지정할 수 있다.
예를 들어, 변환 패턴이 %-4relative [%thread] %-5level %logger{32} - %msg%n
인 PatternLayout은 다음과 같은 결과를 출력한다. :
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
첫 번째 필드 %-4relative
는 프로그램 시작 이후 경과 된 시간(밀리 초)이다. 두 번째 필드 %thread
는 로그 요청을 하는 스레드이다. 세 번째 필드 %-5level
는 로그 요청의 레벨이다. 네 번째 필드 %logger{32}
는 로그 요청과 연관된 로거의 이름이다. '-'뒤의 텍스트 %msg%n
는 요청 메시지이다.
매우 감사드립니다..........