[LogBack] Architecture

노유성·2024년 4월 11일
0

Logback

목록 보기
1/1
post-thumbnail

들어가며

스프링이 포함된 웹서비스를 배포하기 위해서는 모니터링 및 에러 로깅이 되어야 한다. 스프링은 SLF4J 인터페이스를 따르고 그에 대한 구현체로 Logback을 이용한다. Logback의 공식문서를 읽으면서 앞으로 오랫동안 사용될 로깅 라이브러리에 대해서 알아보자.

Logback's architecture

로그백은 이전 구현체인 log4j를 업그레이드 한 버전이라고 한다. log4j를 만든 사람이 기획하고 만든 새로운 라이브러리이다.

Logger, Appenders and Layouts

로그백은 Logger, Appender, Layout 총 3개의 메인 클래스로 이루어여있다. Logger 클래스는 logback-classic 모듈의 일부이고, 다른 모듈은은 logback-core의 일부이다.

로거는 로거마다 자신이 다룰 로깅의 레벨, 로그의 출력 방식 등을 정의한다.

Logger Context

Logback의 가장 큰 장점은 로깅의 레벨이다. System.out.println과의 차이점은 로깅에 레벨을 둘 수 있다는 점이다. 그리고 Logger를 여러 개를 정의해서, 서로 다른 클래스에 서로 다른 로거를 적용할 수 있다. 또한 Logger의 이름은 계층적으로 정의되기 때문에 상속관게를 가질 수 있다.

에를 들어서 com.foo 로거는 com.foo.Bar의 부모이다. 그래서 com.foo.Bar로거가 없을 경우에는 그의 부모인 com.foo 로거를 이용할 수 있다. 이런 식으로 설계를 한 이유는 패키지별로 자신의 패키지 이름에 맞는 로거를 생성해서 사용하면 편리하기 때문이다.

Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.​ROOT_LOGGER_NAME);

로거는 위처럼 생성해서 사용하는데 파라미터에 com.foo.Bar 처럼 자신의 패키지 이름을 넣어서 생성했을 떄, 해당 로거가 없으면 부모의 로거를 이용하고 부모도 없으면은 root 로거를 이용하게 설계하면 편리하기 때문이다.


로거의 기본적은 클래스는 다음과 같이 구성된다.

위 그림은 그림은 로거를 상속해서 사용한 예시이다.
root 로거는 debug로 로깅 레벨이 설정되어있다. 그래서 결과적으로 debug 레벨의 로거가 된다. 다른 로거의 경우에는 할당된 레벨이 없기 때문에 root로거의 effective level을 상속받아서 사용하게 된다.

다른 것은 동일하나 x, x.y.z로거의 경우에는 자신만의 레벨이 있기 때문에 부모의 레벨에 영향을 받지 않는다.

Printing methods and the basic selection rule


로깅의 레벨은 위 그림과 같은 계층 구조를 갖고 있다. 개발자가 작성한 로그의 레벨보다 로거가 사용하는 로깅의 레벨이 같거나 낮아야 로그가 출력된다.

import ch.qos.logback.classic.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
....

// get a logger instance named "com.foo". Let us further assume that the
// logger is of type  ch.qos.logback.classic.Logger so that we can
// set its level
ch.qos.logback.​classic.Logger logger = 
        (ch.qos.logback.​classic.Logger) LoggerFactory.getLogger("com.foo");
//set its Level to INFO. The setLevel() method requires a logback logger
logger.setLevel(Level. INFO);

Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");

// This request is enabled, because WARN >= INFO
logger.warn("Low fuel level.");

// This request is disabled, because DEBUG < INFO. 
logger.debug("Starting search for nearest gas station.");

// The logger instance barlogger, named "com.foo.Bar", 
// will inherit its level from the logger named 
// "com.foo" Thus, the following request is enabled 
// because INFO >= INFO. 
barlogger.info("Located nearest gas station.");

// This request is disabled, because DEBUG < INFO. 
barlogger.debug("Exiting gas station search");

Retrieving Loggers

Logger x = LoggerFactory.getLogger("wombat"); 
Logger y = LoggerFactory.getLogger("wombat");

위 상황에서 x, y 로거는 같은 참조를 갖는다. 왜냐하면 각 로거는 기본적으로 싱글톤으로 관리되기 때문이다. 그래서 로깅을 위해서 로거의 참조를 다른 곳으로 전달하지 않아도 된다. 그리고 생물학적인 부모, 자식과는 다르게(생성에 순서가 있음), 로거는 생성 순서의 제한이 없다. 부모가 자식보다 나중에 생성되어도 부모와 자식은 서로를 식별할 수 있다.

Logger의 이름은 개발자가 마음대로 정의할 수 있지만 계층적 구조의 이점을 위해 계층적으로 이름을 작명하는 것을 추천한다.

Appenders and Layouts

Logger 만을 이용하면 단순히 로깅의 레벨에 따라서 로그를 출력할 것인지 말것인지에 대해서 결정할 수 있다. Appender를 이용하면 로그를 어디로 출력할 것인지에 대해서 설정할 수 있다.
files, remote socket servers, to MySQL, PostgreSQL, Oracle and other databases, JMS, and remote UNIX 등 다양한 곳으로 출력할 수 있으며 출력 형식은 ch.qos.logback.core. 하위에 정의되어 있다.


로거에 정의된 appender는 자식에게 상속된다. 그림을 보면은 root 로거는 A1 로거를 가지고 있고 x 로거는 A-x1, A-x2 appender를 갖고 있지만 부모에게 상속받은 A1 appender도 갖게 된다.

출력 형식또한 사용자가 지정할 수 있으며 기본적으로는 PatternLayout을 기본 출력 형식으로 사용한다.

Parameterized logging

로깅은

logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);

1번의 방식보다는 2번의 방식이 최소 수십배는 빠르다. 그러므로 연산자를 이용한 로깅이 아닌, 파라미터를 활용한 로깅을 하도록 하자ㅑ.

로깅의 절차

로거를 생성해서 info() 메소드를 호출할 때까지의 절차를 알아보자.

1. Get the filter chain decision

예를 들어 TurboFilter 체인이 존재한다면(해당 필터는 로그 이벤트를 필터링 함) 특정 이벤트를 필터링이 가능하다.


public enum FilterReply {
    DENY,
    NEUTRAL,
    ACCEPT;

    private FilterReply() {
    }
}

필터링 후에는 위 3가지의 상태중 하나를 갖게 된다.

2. Apply the basic selection rule

로깅은 각자 info부터 error까지 레벨을 갖게 되는데, 이 레벨에 따라서 해당 레벨의 로그를 출력할지 말지 결정한다.

3. Create a LoggingEvent object

2번까지의 과정에서 살아남았다면

  • 예외
  • 요청 수준
  • 메시지
  • 요청

등의 데이터를 LoggingEvent 객체로 만든다.

4. Invoking appenders

LoggingEvent가 생성된 이후에는 등록된 AppenderdoAppend() 메소드를 이용해서 호출한다.

doAppend()는 동기화된 블록에서 thread-safety 하게 실행된다. ApopenderBase 인터페이스의 구현체에서 doAppend()Appender에 연결된 사용자 지정 필터도 호출한다. 사용자 지정 필터는 다른 챕터에서 다룬다.

5. Formatting the output

로깅 이벤트를 포맷하는 것은 Appender의 역할이다. 하지만 일부는 포맷의 작업을 Layout에 위임한다. 레이아웃은 LoggingEvent 인스턴스를 포맷하고 결과를 String으로 반환한다. SocketAppender의 경우에는 이벤트를 문자열로 변환하지 않고 직렬화한다. 그러므로 이런 경우에는 Layout이 필요하지 않다.

6. Sending out the LoggingEvent

profile
풀스택개발자가되고싶습니다:)

0개의 댓글