본문 바로가기
Study/Logging

[Logging] SLF4J란?

by 검프 2021. 7. 10.

블로그를 작성하고, 테코톡을 진행했어요. 더 쉽게 이해하고 싶다면 아래 영상을 시청해주세요!

[10분 테코톡] ☂️ 검프의 Logging(로깅) #1

SLF4J(Simple Logging Facade for Java)는 이름에서 확인할 수 있듯이. java.util.logging, logback 및 log4j와 같은 다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 하는 라이브러리에요.
SLF4J는 추상 로깅 프레임워크이기 때문에 단독으로는 사용하지 않아요.
즉, 최종 사용자가 배포시 원하는 로깅 프레임워크를 결정하고 사용해도 SLF4J가 인터페이스화 되어있기에, SLF4J를 의존하는 클라이언트 코드에서는 실제 구현을 몰라도 돼요(의존관계 역전 법칙).


SLF4J 동작과정

SLF4J은 간략히 아래와 같은 과정을 거쳐요.

  • 개발할 때, SLF4J API를 사용하여 로깅 코드를 작성
  • 배포할 때, 바인딩된 Logging Framework가 실제 로깅 코드를 수행

https://user-images.githubusercontent.com/48986787/125076883-6b819680-e0fb-11eb-96fb-c1a344bc45d0.png

이러한 과정은 그림에서 확인할 수 있듯이 SLF4J에서 제공하는 3가지 모듈을 통해 수행될 수 있어요.
각각에 대해 알아보도록 해요.

1. SLF4J Bridging Modules

https://user-images.githubusercontent.com/48986787/125087369-64608580-e107-11eb-832a-a2d7ca4d0fa8.png

(SLF4J 이외의) 다른 로깅 API로의 Logger 호출을 SLF4J 인터페이스로 연결(redirect)하여 SLF4J API가 대신 처리할 수 있도록 하는 일종의 어댑터 역할을 하는 라이브러에요.
아직 변경되지 않은 이전의 레거시 로깅 프레임워크를 위한 라이브러리에요.
Bridge는 여러개를 사용해도 상관없지만, 사용시 주의점은 Bridge와 Binder에 같은 종류의 프레임워크를 사용하면 안된다는 것이에요.

2. SLF4J API(인터페이스)

https://user-images.githubusercontent.com/48986787/125090353-34ff4800-e10a-11eb-9150-c992afa96486.png

로깅에 대한 추상 레이어(인터페이스)를 제공해요. 즉 로깅 동작에 대한 역할을 수행할 추상 메서드를 제공해요.
앞서 말했듯이 추상 클래스이기 때문에 이 라이브러리만 단독적으로 쓰일 수 없어요.
사용시 주의점은 반드시 하나에 API에 하나의 Binding을 둬야해요.

3. SLF4J Binding(.jar)

https://user-images.githubusercontent.com/48986787/125081592-26606300-e101-11eb-8c74-0555e6bfd6ad.png

SLF4J 인터페이스를 로깅 구현체(Logging Framework)와 연결하는 어댑터 역할을 하는 라이브러리에요.
SLF4J API를 구현한 클래스에서 Binding으로 연결될 Logger의 API를 호출해요.
앞서 API의 주의점과 같이 하나에 API에 하나의 Binding을 둬야해요.


참고

스프링 부트에서는 기본적으로 SLF4J, Logback을 채택하고 있어요.

https://user-images.githubusercontent.com/48986787/124930298-2e0a0400-e03c-11eb-83f0-0d5674ada206.png


SLF4J 실습

일반 출력

package lab;

public class LabApplication {
    public static void main(String[] args) {
        for (int count = 1; count <= 10; count++) {
            System.out.println("로깅 재밌어 " + count);
        }
    }
}
로깅 재밌어 1
...
로깅 재밌어 9
로깅 재밌어 10

먼저, 일반적인 출력을 해보죠.
우리가 처음 언어를 배울 때 사용했던, System.out()또한 로깅의 한 방법이에요.
하지만 에러별, 상황별로 남기기 어렵고, 무조건 출력되기 때문에 실제 운영에서는 권장하지 않아요.

SLF4J-api 의존성 추가

먼저 SLF4J-api의존성을 추가하여, 인터페이스를 사용해보도록 해요.

dependencies {
    implementation 'org.slf4j:slf4j-api:1.7.31'
}
Logger logger = LoggerFactory.getLogger(TargetClass.class);

를 통해 Logger를 등록할 수 있어요.
아래 예시를 통해 확인해보죠.

package lab;

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

public class LabApplication {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LabApplication.class);
        for (int count = 1; count <= 10; count++) {
            logger.info("SLF4J-API를 사용하는 로깅 재밌어 {}", count);
        }
    }
}

api를 통해 info로그 레벨을 출력해보는 예제에요. 이상태에서, 빌드를 해보면,

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

위와 같은 에러를 볼 수 있어요.

에러가 왜 나는걸까요?

앞서, SLF4J API(인터페이스)는 로깅에 대한 추상 레이어(인터페이스)를 제공한다 얘기했어요. 즉 로깅 동작에 대한 역할을 수행할 추상 메서드만 제공 됐기 때문에, 실제 구현체가 필요한 것이에요. 에러 메세지에서도 구현체가 없다는 예외임을 확인할 수 있어요.

구현체 추가

logback-classic는 자신에게 의존하는 logback-core-1.2.3.jar뿐만 아니라 slf4j-api-1.7.31.jar를 자동으로 가져와요.
해당 artifact의 올바른 버전을 사용하는데 필요하기 때문에 모두 명시적으로 선언하는 것이 좋기에 exclude에 추가 했어요.

dependencies {
    implementation 'org.slf4j:slf4j-api:1.7.31'
    implementation 'ch.qos.logback:logback-core:1.2.3'

    implementation ('ch.qos.logback:logback-classic:1.2.3'){
        exclude group: 'org.slf4j', module: 'slf4j-api'
        exclude group: 'ch.qos.logback', module: 'logback-core'
    }
}
package lab;

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

public class LabApplication {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LabApplication.class);
        for (int count = 1; count <= 10; count++) {
            logger.info("SLF4J-API와 Logback을 사용하는 로깅 재밌어 {}", count);
        }
    }
}

추가를 하고 실행해보면 동작이 잘 되는 것을 확인할 수 있어요.

14:55:54.555 [main] INFO lab.LabApplication - SLF4J-API와 Logback 로깅 재밌어 1
...
14:55:54.558 [main] INFO lab.LabApplication - SLF4J-API와 Logback 로깅 재밌어 9
14:55:54.558 [main] INFO lab.LabApplication - SLF4J-API와 Logback 로깅 재밌어 10

에러 레벨 출력확인

레벨별 출력확인을 위해선, 설정파일을 생성해야해요. 우선 Logback이 설정을 참조하는 순서를 알아보도록 해요.

Log 설정 참조 순서

Logback은 프로그래밍으로 또는 XML이나 Groovy 포맷의 설정 스크립트 파일을 통해서 설정할 수 있어요. Logback이 스스로 설정을 찾는 스텝은 다음과 같아요.

  1. classpath에서 logback-test.xml 파일을 찾음.
  2. (1) 과정에서 파일을 찾지 못했다면, classpath에서 logback.groovy 파일을 찾음
  3. (2) 과정에서 파일을 찾지 못했다면, classpath에서 logback.xml 파일을 찾음.
  4. (3) 과정에서 파일을 찾지 못했다면, JDK 1.6의 service-provider loading facility (Service Loader)에 의해 com.qos.logback.classic.spi.Configurator 인터페이스의 구현체를 찾음. 탐색 위치는 classpath에서 META-INF\services\ch.qos.logback.classic.spi.Configurator.
  5. 위 과정에서 성공한 경우가 없다면, logback은 콘솔에 출력하는 BasicConfigurator으로 설정(기본은 에러 출력 레벨은 info).

우선 SLF4J에서 제공하는 레벨별 에러를 작성해요.

package lab;

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

public class LabApplication {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LabApplication.class);

        for (int count = 1; count <= 10; count++) {
            logger.trace("trace 로깅이야!!! {}", count);
            logger.debug("debug 로깅이야!!! {}", count);
            logger.info("info 로깅이야!!! {}", count);
            logger.warn("warn 로깅이야!!! {}", count);
            logger.error("error 로깅이야!!! {}", count);
        }
    }
}

설정파일을 작성해요( 각각에 설정 부분은 [Logging] Logback이란?)을 확인해주세요.

resources/logback.xml

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

    <root level="trace">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

설정파일을 작성해고 실행하면, 아래와 같은 출력을 확인할 수 있어요.

15:08:55.846 [main] TRACE lab.LabApplication - trace 로깅이야!!! 1
15:08:55.850 [main] DEBUG lab.LabApplication - debug 로깅이야!!! 1
15:08:55.851 [main] INFO  lab.LabApplication - info 로깅이야!!! 1
15:08:55.851 [main] WARN  lab.LabApplication - warn 로깅이야!!! 1
15:08:55.851 [main] ERROR lab.LabApplication - error 로깅이야!!! 1
...

only error

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

    <root level="error">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
15:09:41.254 [main] ERROR lab.LabApplication - error 로깅이야!!! 1
15:09:41.258 [main] ERROR lab.LabApplication - error 로깅이야!!! 2
...
15:09:41.258 [main] ERROR lab.LabApplication - error 로깅이야!!! 10

Refer

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

댓글