본문으로 바로가기

시작에 앞서 로그가 무엇이며, 로깅을 하는 이유에 대해서 알아본다.

> Log(로그), Logging(로깅)

네이버 사전에 log를 검색하면 다음과 같이 검색된다.

log(logging)
1. 통나무
2. (특히 항해운항비행 등의) 일지
3. logarithm(?)

개발자들이 말하는 Log라는 것은 네이버 사전의 두 번째 뜻(=일지)과 느낌이 비슷하다.

흔히 컴퓨터 분야에서 말하는 Log(로그)는 애플리케이션(또는 운영체제)이 실행 중 발생하는 다양한 이벤트에 대한 기록을 뜻한다.
그리고 기록하는 행위를 Logging(로깅)이라고 한다.

Logging의 대상은 콘솔, 파일, 데이터베이스 등이 있다.

> Logging(로깅)을 하는 이유

- 애플리케이션의 문제 발생시, 원인 분석을 위한 정보로 활용하기 위함
- 애플리케이션의 성능 분석
- 애플리케이션 사용자들에 대한 분석 및 통계

 

> Java, Logging

자바에서는 로깅을 위해 다양한 로깅 프레임워크가 존재한다. 아래에 몇가지 프레임워크를 작성했다.

- logback : Log4j를 개발한 Ceki Gulcu(세키 굴쿠)가 기존에 사용되던 Log4j를 더 발전시킨 것 
- java.util.logging : JDK 1.4부터 포함된 로깅 API
- log4j2 : 아파치 재단에서 제공하는 로깅 API
- Apache Commons logging

 

참고로 우리가 자주 사용하는 spring boot에서는 기본으로 logback 프레임워크를 사용한다. 
그래서 이번 글에서도 logback 프레임워크에 대한 사용법을 익힐 예정이다.

그런데 무작정 logback 프레임워크 관련 코드를 애플리케이션 코드에 직접적으로 사용해도 괜찮을까?

만약에 수백개의 코드에 logback 관련 코드를 작성했다고 가정해보자.
그런데 갑자기 클라이언트에 의해서 Log4j2 로 로깅 프레임워크를 바꿔야 한다면?

방금 말한 수백개의 코드를 모두 바꿔야 한다. 설사 IDE의 도움을 받아서 한번에 바꿨다고 해도, SVN이나 Git으로 Commit을 하면 무수한 충돌이 날 것이다.

그렇다면 어떡할까? 이때 필요한 것이 바로 SLF4J(Simple Logging Facade For Java)이다.

 

> SLF4J(Simple Logging Facade For Java)

- SLF4J 는 Facade(퍼사드) 디자인 패턴에 따라 만들어진 클래스
- SLF4J 가 실제 사용하게 될 구현 클래스( 위에서 말한 로깅 프레임워크 ) 사용자가 선정
- Facade 디자인 패턴을 사용하기 덕분에, 다양한 구현체를 하나의 통일된 방식으로 사용 가능
- SLF4J는 로깅에 대한 추상 레이어를 제공하는 interface의 모음

보충 설명:

더보기
더보기

SLF4J 는 위에서 말한 로깅 프레임워크가 아니다. 단지 복잡한 로깅 프레임워크들을 쉽게 사용할 수 있도록
도와주는 퍼사드(facade)에 불과하다. 퍼사드는 GoF 디자인 패턴 중 하나로서 복잡한 서브 시스템을 쉽게 사용할
수 있도록 간단하고 통일된 인터페이스를 제공한다.

따라서 퍼사드를 이용하면 복잡한 로깅 프레임워크의 구조는 몰라도 쉽게 사용할 수 있으며,
프레임워크의 의존성이 낮아지기 때문에 쉽게 교체할 수 있다.

SLF4J 는 위에서 말한 로깅 프레임워크가 아니다. 단지 복잡한 로깅 프레임워크들을 쉽게 사용할 수 있도록
도와주는 퍼사드(facade)에 불과하다. 퍼사드는 GoF 디자인 패턴 중 하나로서 복잡한 서브 시스템을 쉽게 사용할
수 있도록 간단하고 통일된 인터페이스를 제공한다.

따라서 퍼사드를 이용하면 복잡한 로깅 프레임워크의 구조는 몰라도 쉽게 사용할 수 있으며,
프레임워크의 의존성이 낮아지기 때문에 쉽게 교체할 수 있다.

 

지금 부터 SLF4J를 좀 더 심도 있게 알아보겠다.
참고로 지금부터 쓸 글은 (gmlwjd9405.github.io/2019/01/04/logging-with-slf4j.html) 의 내용을 많이 간략화 한 것이다.
그리고 해당글은 (www.slf4j.org/manual.html)을 직역 혹은 요약한 것이다.
만약 지금부터 하는 설명이 이해가 안되면 해당 블로그 혹은 slf4j 사이트에 가서 부족한 부분을 참고하길 바란다.

 

> SLF4J(Simple Logging Facade For Java), deep dive

SLF4J는 세가지 모듈을 제공한다.

>>> SLF4J API

- SLF4J 를 사용하기 위한 인터페이스를 제공
- slf4j-api-{version}.jar 를 통해 사용
- 반드시 slf4j 바인딩을 하나만 써야 함

>>> SLF4J 바인딩

SLF4J 인터페이스를 로깅 구현체와 연결하는 어댑터 역할을 하는 라이브러리

>>> SLF4J Bridging Modules

- 다른 로깅 API로 Logger 호출을 할 때, SLF4J 인터페이스로 연결(redirect)하여 SLF4J API가 대신 Logger를 처리할 수 있도록 하는 어댑터 역할의 라이브러리

- 다른 로깅 API ==> Bridge(redirect) ==> SLF4J API 

 

 

>> SLF4J Binding

SLF4J Binding은 SLF4J 인터페이스( = SLF4J API )를 로깅 구현체와 연결하는 어댑터 역할의 라이브러리다.
그리고 이러한 SLF4J Binding 라이브러리반드시 하나만 class path에 존재해야 한다. 

만약에 하나도 class path에 없다면, no operation으로 설정된다. 즉 아무것도 출력이 안된다는 의미다.

 

SLF4J binding의 종류는 대략 다음과 같다.

1. slf4j-log4j12-{version}.jar : log4j 버전 1.2에 대한 바인딩

2. slf4j-jdk14-{version}.jar : java.util.logging(JDK1.4 로깅)에 대한 바인딩

3. slf4j-nop-{version}.jar : NOP에 대한 바인딩. 모든 로깅을 자동을 삭제

4. slf4j-simple-{version}.jar :  모든 이벤트를 System.err에 출력하는 단순 구현에 바인딩

5. slf4j-jcl-{version}.jar : JCL(Jakarata Commons Logging)에 대한 바인딩. 모든 SLF4J 로깅을 JCL에 위임

6. logback-classic-{version}.jar :
   SLF4J 인터페이스를 직접 구현한 것. 참고로 ch.qos.logback.classic.Logger 클래스는 SLF4J의 org.slf4j.Logger
   인터페이스를 직접 구현한 클래스다.

 

 

>> SLF4J Bridging Modules

(SLF4J 이외의) 다른 로깅 API에서의 Logger 호출을 SLF4J 인터페이스로 연결(redirect) 하여 SLF4J API가
대신 처리할 수 있도록 하는 일종의 어댑터 역할을 하는 라이브러리다.

프로젝트에는 다양한 Component들이 있을 수 있고, 일부는 SLF4J 이외의 로깅 API에 의존할 수 있다.
이러한 상황을 처리하기 위해서 SLF4J에는 여러 Bridging Module이 제공된다.

http://www.slf4j.org/legacy.html 참고

SLF4J 가 제공하는 Bridge는 다음과 같다.

1. jcl-over-slf4j.jar : JCL API 에 의존하는 클래스들을 수정하지 않고, JCL로 들어오는 요청을 jcl-over-slf4j를 이용해서 SLF4J API가 처리하도록 한다.

2. log4j-over-slf4j.jar : log4j 호출을 slf4j api 가 처리하도록 한다.

3. jul-to-slf4j.jar : java.util.logging 호출을 slf4j api 가 처리하도록 한다.

4. (보충 설명) log4j-over-slf4j vs log4j-to-slf4j ? to 와 over의 차이

더보기
더보기

stackoverflow.com/questions/18539031/slf4j-bridge-vs-migrator-jars 

 

SLF4J: Bridge vs Migrator JARs

SLF4J (1.7.5, but really any modern version) ships with several "over" (migrator) JARs: jcl-over-slf4j-1.7.5.jar log4j-over-slf4j-1.7.5.jar ...as well as a "to" (bridge) JAR: jul-to-slf4j-1.7.5....

stackoverflow.com

 

운 좋게 발견한 글이다. 일단 to 나 over 모두 SLF4J API로 Logging 작업을 redirect 시키는 기능은 같다.
하지만 이 기능을 구현하기 위한 방식이 다르다.

over의 경우에는 치환하려는 프레임워크의 클래스와 똑같은 모습의 클래스를 만들고 대신 쓰이도록 하는 것이다.
그리고 쓰이게 되면 모든 요청을 SLF4J API로 redirect한다.

to의 경우는 Handler 클래스를 사용해서 redirect를 수행한다. 자세한 내용은 개인적으로 검색해보기 바란다.

(참고: 현재 spring-boot 2.3.8 버전에서는 log4j-over-slf4j.jar 가 아닌 log4j-to-slf4j.jar 를 사용한다)

 

 

>> SLF4J 모듈 사용 시 주의할 점

브릿지와 바인딩 모듈은 같은 로깅 프레임워크와 관련된 jar를 사용하지 않도록 한다.
(log4j 관련 bridge : log4j-over-slf4j / log4j 관련 binding : slf4j-log4j)

 

아래처럼 하는 방식을 생각해두자.

 

 

> SLF4J 정리

1. SLF4J API (인터페이스):
 - 로깅에 대한 추상 레이어를 제공
 - 사용자가 이 interface를 통해 로깅 코드를 작성
 - slf4j-api

2. SLF4J Binding(.jar)
 - SLF4J 인터페이스를 로깅 구현체로 연결하는 어댑터 역할을 하는 라이브러리
 - SLF4J에 구현체(Logging Framework)를 바인디앟기 위해 사용
 - 여러 바인딩 중 하나만 사용
 - [Logback] logback-classic / [Log4J] slf4j-log4j12

3. Logging Framework
 - 실제 로깅 코드를 실행할 Logging Framework 를 정한다.
 - [Logback] logback-core / [Log4J] log4j-core

4. SLF4J Bridging Module
 - 다른 로깅 API => Bridge(redirect) => SLF4J API
 - 다른 로깅 API에서의 Logger 호출을 SLF4J 인터페이스로 연결(redirect)하여 SLF4J API가 대신 처리할 수 있도록 하는
   일종의 어댑터 역할을 하는 라이브러리
 - log4j-over-slf4j, jcl-over-slf4j, jul-to-slf4j

5. Logging Framework를 변경하고 싶으면 2,3번을 교체한다.

 

> SLF4J Practice

이론적인 것을 어느정도 알았으니, 실전이다.
gradle 빌드툴을 사용해서 의존성을 추가하고, 간단하게 테스트 코드를 작성하겠다.

일단 아래와 같이 gradle.build 파일을 작성하겠다.

plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

test {
    useJUnitPlatform()
}

 

 

① slf4j-api 만 있을 때

테스트 코드

package me.sickbbang.logging_test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogTest {

    private final static Logger log = LoggerFactory.getLogger(LogTest.class);

    public void run() {
        log.trace("trace!!!!!");
        log.debug("debug!!!!");
        log.info("info!!!!!");
        log.warn("warn!!!!!");
        log.error("error!!!!");
    }

    public static void main(String[] args) {
        new LogTest().run();
    }
}

 

참고 설명:  로그 레벨

더보기
더보기

trace, debug, info, warn, error 가 보일 것이다.
이것은 로그의 중요도와 관련된 것이다.
trace ==> debug ==>  info  ==>  warn  ==>  error  순으로 중요도가 높아진다.
정말 치명적인 에러의 경우에는 log.error 를 하거나,
단순히 애플리케이션 사용 통계를 위한 거면 log.info 를 사용하면 된다.

 

실행 결과

에러의 내용을 보면 알겠지만, LoggerBinder 가 없어서 나는 에러다.
즉 slf4j-api에 사용될 실제 구현체(로깅 프레임워크)와 바인딩을 하는 Binder가 없다는 뜻이다.

 

② slf4j-api + logback-classic (binding)


build.gradle 에서 dependencies{ ~ } 을 다음과 같이 수정한다.

dependencies {
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'
    implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

사실은 logback-classic 의존성만 추가해도 slf4j-api, logback-core 의존성이 자동 추가된다.
하지만 빌드툴의 의존성 추가 메커니즘에 의해 버전이 잘못 들어가는 것을 방지하기 위해서 이렇게 했다.
(위 문장은 굳이 이해 안해도 된다. 그래도 궁금하다면 아래 "더보기"를 가볍게 읽어보자)

더보기
더보기

위 그림은
(maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.htmlmaven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#transitive-dependencies) 에서 참조했다.
현재 gradle을 사용하는데, maven으로 이해해도 되는지 걱정할 수도 있겠지만, 걱정 안해도 된다.
위 그림의 메커니즘은 gradle이나 maven이나 똑같다. 

위의 그림에 있는 문구를 가볍게 읽고 이해하자. 만약 이해가 안된다면 위 주소로 들어가서 더 자세히 읽자.

 

의존성 현황

 

이러고 나서 다시 이전 테스트 코드를 실행하면 정상적으로 log가 찍히는 것을 확인할 수 있다.

그런데 log.trace가 출력이 안되는 것을 알 수 있다. trace가 나오게 하려면 어떻게 해야되는지는 
> SLF4J Practice With logback.xml 에서 알 수 있다.

 

slf4j-api + logback-classic(binding) + log4j2-to-slf4j(bridge)


build.gradle 에서 dependencies{ ~ }을 다음과 같이 수정한다.

dependencies {
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'
    implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
    implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: '2.13.3'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

 

테스트 코드

package me.sickbbang.logging_adapter_test;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class LogAdapterTest {

    private final static Logger log = LogManager.getLogger(LogAdapterTest.class);

    public static void main(String[] args) {
        log.info("안녕");
    }

}

테스트 코드에서는 Log4j 프레임워크의 api를 사용했다. 하지만 브릿지를 사용하고 있으므로 실제 Logger 동작은
SLF4J API ==> logback-classic 을 사용하게 된다.

실행 결과

성공

 

> SLF4J Practice With logback.xml

개인이 원하는 방식으로 logback의 동작 혹은 로그 패턴을 변경하고 싶다면 logback.xml 파일을 작성해야한다.
일단 logback.xml 파일을 생성해준다. 생성 경로는 src/main/resources/logback.xml 이다.

 

logback.xml을 다음과 같이 작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{yyyy/MM/dd HH:mm:ss.SSS} %-5level --- [%thread] %logger[%method:%line] - %msg %n</pattern>
        </encoder>
    </appender>


    <!-- me.sickbbang 패키지와 서브 패키지 모두 이 로거가 Logging 처리를 할 것이다. -->
    <!-- additivity 는 아래 보이는 <root> (Root Logger)의 Appender에 대한 상속 유무를 뜻한다. -->
    <!-- 자세한 내용은 이후에 설명하겠다. -->
    <logger name="me.sickbbang.logging_test" level="DEBUG" additivity="false">
        <appender-ref ref="consoleAppender" />
    </logger>

    <!-- 기본 로거 ( Root Logger ) -->
    <!-- 앞서 logger는 특정 패키지 (혹은 클래스) 를 위한 거였다면, root 는 모든 패키지를 의미한다. -->
    <!-- level 미지정시 debug 기본 값사용 -->
    <root level="warn">
        <appender-ref ref="consoleAppender" />
    </root>

</configuration>

 

 

작성 후, 테스트를 위해서 다음과 같이 패키지 및 Class를 구성해준다.

 

3개의 클래스에 대해서 작성한다.

package me.sickbbang.logging_test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogTest {

    private final static Logger log = LoggerFactory.getLogger(LogTest.class);

    public void run() {
        log.trace("trace!!!!!");
        log.debug("debug!!!!");
        log.info("info!!!!!");
        log.warn("warn!!!!!");
        log.error("error!!!!");
    }
}


// ----------------------------------------------------------------------------------

package me.sickbbang.bravo_log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BravoLogTest {
    private static final Logger log = LoggerFactory.getLogger(BravoLogTest.class);
    public void run() {
        log.trace("trace!!!!!");
        log.debug("debug!!!!");
        log.info("info!!!!!");
        log.warn("warn!!!!!");
        log.error("error!!!!");
    }
}

// ----------------------------------------------------------------------------------

package me.sickbbang;

import me.sickbbang.bravo_log.BravoLogTest;
import me.sickbbang.logging_test.LogTest;

public class MainRunner {
    public static void main(String[] args) {
        LogTest logTest = new LogTest();
        BravoLogTest bravoLogTest = new BravoLogTest();

        System.out.println("logTest.....");
        logTest.run();

        System.out.println("bravoLogTest.....");
        bravoLogTest.run();
    }
}

다 작성하고 나서 MainRunner의 main 메소드를 실행하면 다음과 같은 결과가 나온다.

 

실행 결과를 보면 <logger> 의 name 속성으로 준 me.sickbbang.logging_test 패키지는 logger level에서 지정한
"DEBUG" 이상의 레벨만 출력하는 것을 볼 수 있다.

반면에 logger로 지정하지 않은 me.sickbbang.bravo_log 패키지는 <root> (= root logger)에서 지정한
기본값이 적용된다. 현재는 root logger의 level 이 "warn" 이므로 위 그림처럼 warn, error 로그가 출력된다.

다음으로는 logback.xml에서 작성했던 <root><logger> 태그 간의 상속 관계를 알아보겠다.

 

> SLF4J Name Hierarchy

logback.xml 에 존재하는 <logger> 태그는 상속을 상속이라는 개념이 적용된다.

logback.xml에서 부모, 자식 logger 태그의 구분은 간단하다.
아래 그림을 보자.

그림을 보면 root logger가 항상 최상단의 부모 logger이고, 
그 이후로는 로거의 이름에 "." 으로 부모 자식이 나뉘는 것을 확인할 수 있다.

 

> SLF4J Level Hierarchy

이름에 의해서 부모 자식이 구별되고 나면 자식 logger는 부모 logger의 level를 기본으로 상속 받는다.
하지만 logger 스스로가 level 을 명시했다면 부모의 level을 무시한다. 
위 말이 이해가 안된다면 다음 예제를 보자. (예제 참고: logback.qos.ch/manual/architecture.html#effectiveLevel)

- Example 1

Logger name Assigned level Effective level
root DEBUG DEBUG
X none DEBUG
X.Y none DEBUG
X.Y.Z none DEBUG

In example 1 above, only the root logger is assigned a level. This level value, DEBUG, is inherited by the other loggers X, X.Y and X.Y.Z

 

- Example 2

Logger name Assigned level Effective level
root ERROR ERROR
X INFO INFO
X.Y DEBUG DEBUG
X.Y.Z WARN WARN

In example 2 above, all loggers have an assigned level value. Level inheritance does not come into play.

 

- Example 3

Logger name Assigned level Effective level
root DEBUG DEBUG
X INFO INFO
X.Y none INFO
X.Y.Z ERROR ERROR

In example 3 above, the loggers root, X and X.Y.Z are assigned the levels DEBUG, INFO and ERROR respectively. Logger X.Y inherits its level value from its parent X.

 

- Example 4

Logger name Assigned level Effective level
root DEBUG DEBUG
X INFO INFO
X.Y none INFO
X.Y.Z none INFO

In example 4 above, the loggers root and X and are assigned the levels DEBUG and INFO respectively. The loggers X.Y and X.Y.Z inherit their level value from their nearest parent X, which has an assigned level.

표만 봐도 뭔 내용인지 감이 올 것이다. 
다음은 <logger>태그와 별개로 만들었던 <appender> 태그에 대해서 알아보겠다.

 

> SLF4J Appender & Hierarchy

위에서 Logger를 열심히 설명했지만, 정작 <logger> 스스로는 아무것도 못한다.
<logger> 는 단순히 로그를 출력하고자 하는 패키지 위치와 level을 지정해주는 게 전부이다.
<logger> 가 실제 로그를 기록하는 대상(파일, 콘솔, DB 등등)를 지정하는 것은 <appender> 이다.

하나의 <logger>에는 자식 태그인 <appender-ref> 태그를 통해서 appender를 포함시킨다,
이렇게 함으로써 다양한 위치(콘솔, 파일, DB 등)에 로그를 동시에 기록할 수 있다.

그런데 위에서 작성한 logback.xml 에서 아래와 같은 스크립트를 봤을 것이다.

<logger name="me.sickbbang.logging_test" level="info" additivity="false">
   <appender-ref ref="consoleAppender" />
</logger>

여기서 봐야될 것은 additivity이다. 이 additivity의 true/false 값에 따라서
부모 logger의 Appender들을 상속 받을지 안 받을지를 결정하게 된다.
참고로 additivity는 설정을 안하면 기본으로 true 가 지정된다.

위의 말이 이해가 안된다면 logback 공식 사이트에서 제공하는 예제를 보자(아래 사진).

(참고:  logback.qos.ch/manual/architecture.html#additivity )

예제를 보면 대충 알겠지만, 번역하면 이런 내용이다.

- root logger에는 additivity 속성을 부여할 수 없다.
- additivity="true"(기본값) 이면 모든 부모 logger로부터 Appender를 상속 받는다. 
- 상속을 받은 logger는 자신이 갖고 있던 ( Appender + 상속받은 Appender )를 로깅 대상으로 간주한다.
- additivity="false" 를 지정한 logger는 자신이 갖고 있는 Appender만을 사용해서 로깅을 한다.
- additivity="false" 를 지정한 logger의 자식 logger는 additivity="false"인 부모까지만 Appender를 상속받는다.

당장 이해가 안되도, 천천히 다시 보고 이해하려고 노력하자.

 

> SLF4J Level & Appender Hierarchy Test

우리가 기존에 작성했던 테스트 logback.xml을 살짝 수정하면 위의 내용이 사실임을 알 수 있다.
logback.xml 일부를 다음과 같이 수정한다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy/MM/dd HH:mm:ss.SSS} %-5level --- [%thread] %logger[%method:%line] - %msg %n</pattern>
        </encoder>
    </appender>

    <!-- level="info" , additivity="true" 로 변경 -->
    <logger name="me.sickbbang.logging_test" level="info" additivity="true"> 
        <appender-ref ref="consoleAppender" />
    </logger>

    <!-- level="error" 로 변경 -->
    <root level="error">
        <appender-ref ref="consoleAppender" />
    </root>

</configuration>

이렇게 작성하고 결과를 예측해보자. 어떻게 될까?
어렵다면 위에서 설명한 level 상속과 Appender 상속을 차례대로 생각하면 된다.

일단 level 상속부터 생각하자.
logger 자신이 level을 지정하면 부모 logger가 어떤 level을 하든 무시한다.
그러므로 logger의 level은 info 가 된다.

다음으로 Appender 상속을 생각하자.
현재 logger는 additivity=true 인 상태다. 그러므로 부모 logger의 Appender를 상속 받는다.
그런데 같은 appender인데도 상속을 받을까? 그렇다. 중복이 되더라도 additivity=true 이면 상속을 받는다.

테스트를 해서 위의 예측이 맞는지 알아보자.

테스트 코드:

package me.sickbbang.logging_test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogTest {

    private final static Logger log = LoggerFactory.getLogger(LogTest.class);

    public void run() {
        log.trace("trace!!!!!");
        log.debug("debug!!!!");
        log.info("info!!!!!");
        log.warn("warn!!!!!");
        log.error("error!!!!");
    }

    public static void main(String[] args) {
        new LogTest().run();
    }
}

 

실행 결과

1. level = "info"
2. consoleAppender 2개 사용, 하나는 자신의 것, 다른 하나는 부모의 것

1,2 번을 생각하면 위의 결과가 맞다.

 

그렇다면 위처럼 중복되는 결과를 원하지 않는다면 어떡할까?
간단하다. logger의 additivity="false" 를 해주면 끝이다.

logback.xml 일부 수정 

<logger name="me.sickbbang.logging_test" level="info" additivity="false">
    <appender-ref ref="consoleAppender" />
</logger>

 

실행결과:

 

 

> root , logger 나누는 이유(추측)

root와 logger를 나누면 전체와 부분을 나누어서 로깅을 할 수 있다.
root logger 에는 전체적으로 해야되는 logging을 맡기고
logger에는 특정 패키지에서 root와는 다른 방식으로 로깅을 해야하면 그것을 지정해주면 된다.

 

 

 

> 다음에 배울 것 

이상으로 Java의 Logging 을 위한 SLF4J 와 그 구현체인 logback 프레임워크에 대해서 공부했다.

다음 글에서는 logback.xml로 파일에 이력을 남기는 것과 같은 "활용"에 대해서 알아보겠다.

 

 

 

 

Reference

gmlwjd9405.github.io/2019/01/04/logging-with-slf4j.html

www.slf4j.org/legacy.html

wiki.base22.com/btg/how-to-setup-slf4j-and-logback-in-a-web-app-fast-35488048.html

stackoverflow.com/questions/18539031/slf4j-bridge-vs-migrator-jars

www.slf4j.org/manual.html

logback.qos.ch/manual/configuration.html

logback.qos.ch/manual/architecture.html#effectiveLevel

 

 

'Java > Java 낙서장' 카테고리의 다른 글

파일들 zip으로 압축하기  (0) 2021.12.19
[apache POI] 간단 사용법  (1) 2021.01.26