[Java] Log(로그)를 위한 SLF4J & Logback

식빵·2022년 1월 10일
1

Java Lab

목록 보기
3/29
post-thumbnail
post-custom-banner

이 글은 내가 tistory에서 활동할 때 적었던 이다.
블로그 이전하면서 버리기 아까운 내용이여서 가져왔다.

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


🍀 Log

- 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

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

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

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


지금부터 SLF4J를 좀 더 심도 있게 알아보겠다.
참고로 이후 내용 중 일부를 이 블로그에서 참조했다. 그리고 해당 블로그의 글은 slf4j 공식 사이트 설명을 직역한 보인다. 만약 지금부터 하는 설명이 이해가 안되면 해당 블로그 혹은 slf4j 사이트에 가서 부족한 부분을 참고하길 바란다.




- SLF4J 구성요소

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과 그 구현체는 반드시 classpath에 동시에 있어야 하며,
SLF4J Binding 라이브러리는 반드시 하나만 classpath에 존재해야 한다.

로깅을 하는 구현체를 하나로 통일하려고 쓰는 Slf4j인데, 구현체를 연결해주는 Binding을 여러개 둔다는 건 말이 안된다.

만약에 하나도 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이 제공된다.


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, log4j-to-slf4j ? 아래 스택 오버플로우 글을 참조한다.


이런 많은 브릿지 라이브러리가 정확히 어떻게 기존 로깅 라이브러리의 호출을 redirect 시키는지, 그리고 중간중간 보이는 overto 가 붙는데, 이 둘간의 차이가 뭔지를 한방에 설명하는 이 스택오버플로우 글을 한번 읽어 보고 오자.

스택 오버플로우의 글에 따르면 toover 모두 SLF4J API로 Logging 작업을 redirect 시키는 기능은 같다. redirect을 구현하기 위한 방식이 다를 뿐이다.

over의 경우에는 치환하려는 로깅 프레임워크의 핵심 클래스들을 똑같이 만들어서 대신 쓰이도록 하는 것이며, 내부적으로 slf4j-api이 호출되도록 한다.

to의 경우는 Handler 클래스를 사용해서 redirect를 수행한다.




- SLF4J 사용시 주의사항

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


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




- SLF4J 정리

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

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

  1. Logging Framework
    • 실제 로깅 코드를 실행하는 주체이다.
    • logback-core, log4j-core

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

중요! Logging Framework를 변경하고 싶으면 2,3번을 동시에 교체한다.
사실상 SLF4J BindingLogging Framework 은 한 몸이다.




🍀 SLF4J 실습

이론적인 것을 어느정도 알았으니, 실전이다.
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()
}

참고
앞으로 로그를 찍을 때 log.trace, log.debug, log.info, log.warn, log.error 를 쓴다.
이렇게 나뉘는 이유는 로그의 중요도를 다르게 하기 위함이다.

trace ==> debug ==> info ==> warn ==> error 순으로 중요도가 높아진다.

정말 치명적인 에러의 경우에는 log.error 를 하고
단순히 애플리케이션 사용 통계를 위한 거면 log.info 를 사용하면 된다.




- 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();
    }
}

실행 결과

에러의 내용을 보면 알겠지만, 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 의존성이 자동 추가된다. 하지만 빌드툴의 의존성 전이 메커니즘에 의해 버전이 잘못 들어가는 것을 방지하기 위해서 이렇게 했다.


의존성 현황


테스트 코드 재실행 결과

테스트 코드를 실행하면 정상적으로 log가 찍히는 것을 확인할 수 있다.
그런데 log.trace가 출력이 안된다. trace가 나오게 하는 것은 뒤에서 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 을 사용하게 된다.


실행 결과

성공 😊




🍀 logback.xml config

logback의 동작 및 로그 패턴을 설정하고 싶다면 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 loggerlevelwarn 이므로 위 그림처럼 warn, error 로그가 출력된다.

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




🍀 logback logger Hierarchy


- SLF4J Name Hierarchy

logback.xml<logger> 태그는 이름 규칙에 의한 부모, 자식 상속 구조를 갖는다.

아래 그림을 보자.

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



- SLF4J Level Inheritance

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


- Example 1

Logger nameAssigned levelEffective level
rootDEBUGDEBUG
XnoneDEBUG
X.YnoneDEBUG
X.Y.ZnoneDEBUG

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 nameAssigned levelEffective level
rootERRORERROR
XINFOINFO
X.YDEBUGDEBUG
X.Y.ZWARNWARN

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



- Example 3

Logger nameAssigned levelEffective level
rootDEBUGDEBUG
XINFOINFO
X.YnoneINFO
X.Y.ZERRORERROR

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 nameAssigned levelEffective level
rootDEBUGDEBUG
XINFOINFO
X.YnoneINFO
X.Y.ZnoneINFO

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 & Inheritance


위에서 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 공식 사이트에서 제공하는 예제를 보자(아래 사진).

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

  • root logger에는 additivity 속성을 부여할 수 없다.

  • additivity="true"(기본값) 이면 모든 부모 logger로부터 Appender를 상속 받는다. 

  • 상속을 받은 logger는 자신이 갖고 있던 ( Appender + 상속받은 Appender )를 로깅 대상으로 간주한다.

  • additivity="false" 를 지정한 logger는 자신이 갖고 있는 Appender만을 사용해서 로깅을 한다.

  • additivity="false" 를 지정한 logger의 자식 logger는 additivity="false"인 부모까지만 Appender를 상속받는다.

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



- SLF4J Level & Appender Inheritance 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>

이렇게 작성하고 <logger name="me.sickbbang.logging_test"> 의 출력 결과를 예측해보자. 어떻게 될까?

어렵다면 위에서 설명한 level 상속과 Appender 상속을 차례대로 생각하면 된다.

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

  2. 다음으로 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와는 다른 방식으로 로깅을 해야하면 그것을 지정해주면 된다.




🍀 참고

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

  2. https://www.slf4j.org/legacy.html

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

  4. https://www.slf4j.org/manual.html

  5. https://logback.qos.ch/manual/configuration.html

  6. https://logback.qos.ch/manual/architecture.html#effectiveLevel

  7. https://kouzie.github.io/spring/logback/#appender-%EC%A2%85%EB%A5%98

profile
백엔드를 계속 배우고 있는 개발자입니다 😊
post-custom-banner

0개의 댓글