기타 주제

Java의 Slf4j, Logging Framework와 Dependency

jw92 2024. 9. 2. 10:47

SLF4J (The Simple Logging Facade for Java)

Java에는 Logback, Log4j2와 같은 다양한 Logging Framework이 있다.

내가 예를 들면 Apache Project를 개발하고 있다고 해보자.

실제 사용자가 어떤 Framework을 사용할지 모르니 Logback에 대한 코드, Log4j2에 대한 코드 등을 모두 추가해야할까?

 

 

이런 때에 가장 적합한 Design Pattern이 Facade Pattern이다.

 

공통된 기능을 가지는 여러 Library를 단일 API를 이용하여 활용하도록 해주는 것이다.

이름에서도 알 수 있듯이 이 Facade Pattern 이용하여 여러 Framework에 대한 고려를 해주는 것이 SLF4J이다!

 

SLF4J를 이용한다면 코드 내부에서는 @Slf4j 만 선언해주고 log.info(), log.error()와 같이 사용해주면 된다.

물론 위와 같이 annotation 대신 직접 선언도 가능하다.

 

Logging Implementation

SLF4J는 Facade 패턴으로 이것만으로는 Concrete Class가 없기 때문에 아무 동작도 하지 않는다.
Logback, Log4j2 등 Implementation Dependency를 추가해줘야한다.

각각의 장단점은 여러 사이트에 잘 나오니 비교해서 사용하면 되지만

새로 시작하는 사용자라면 Log4j2를 추천한다.

 

Dependencies

Java

<dependencies>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.4.12</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>2.0.16</version>
    </dependency>
</dependencies>

Java에서 Logback을 사용하기 위해서는 위처럼 추가한다.

특히 프로젝트에 jar-with-dependencies를 포함하는 경우라면 반드시 명시적으로 2개 모두 선언해준다.

 

주의할 점은 logback이 slf4j보다 더 상위에 선언되어야 한다.

Maven의 버전 관리에 의해 slf4j api로 loback이 아닌 NOP(No-Operation) Class가 이용될 수도 있기 때문이다.

2024.09.02 - [기타 주제] - Maven / Gradle의 Dependencies Conflicts

 

명시적으로 slf4j 버전을 지정한다면,

JDK 8 까지는 1.7.x 이하 버전을,

JDK 11 혹은 그 이상은 가장 최신 버전을 사용하도록 한다.

Spring

Spring의 spring-boot-starter-logging 에서는 Logback을 기본값으로 사용한다.

명시적으로 Logback 버전을 지정한다면,

Spring 2 버전까지는 1.2.x 버전 및 Spring 3버전부터는 1.3.x 버전 이상을 사용하도록 한다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-web:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-logging:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-validation:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-security:3.1.5'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.5'
}

기본적으로 Spring에서 Logback을 사용하기 위해서는 위와 같이 spring-boot-starter-logging 를 선언하면 된다.

 

spring-boot-starter-log4j2에서 Log4j2 관련 Dependencies 제공한다.

configurations {
    all {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
        exclude group: 'ch.qos.logback', module: 'logback-classic'
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-web:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-log4j2:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-validation:3.1.5'
    implementation 'org.springframework.boot:spring-boot-starter-security:3.1.5'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.5'
}

Log4j2를 사용하기 위해서는 혹시 모를 Dependency를 제거하기 위해 spring-boot-starter-logging과 logback을 명시적으로 제외해주는 것이 좋다.

 

Logging Binding

Binding은 로깅 추상화 라이브러리(SLF4J)와 로깅 구현체(Logback, Log4j2 등)를 연결하는 과정

slf4j에서 제공하는 컨셉은 위와 같다.

<dependencies>
    <!-- Log4j2 Core -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.20.0</version>
    </dependency>
    <!-- Log4j2 API -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.20.0</version>
    </dependency>
    <!-- SLF4J API -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.16</version>
    </dependency>
    <!-- SLF4J to Log4j2 Binding -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.20.0</version>
    </dependency>
</dependencies>

logback은 binding을 자체적으로 제공하기 때문에 없어도 되지만,

log4j2의 경우 위와 같이 log4j-slf4j-impl을 선언해줘야 한다.

Spring의 경우 spring-boot-starter-log4j2에 포함되어 있다

 

Bridge

Bridge는 Slf4j와 비슷한 다른 logging API를 손상없이 Slf4j로 가져올 수 있도록 해주는 것이다.

예를 들어 Spring에서 기본으로 사용하는 JCL(Jakarta Commons Logging), JUL(Java Util Logging) 등이 있다.

이를 이용하면 예를 들어 JCL 기반으로 구현되어 있다고 하더라도 Slf4j를 사용할 수 있게 된다.

Slf4j에서 제공하는 Bridge의 기본 컨셉은 위와 같다.

Slf4j에서는 이러한 logging API 들을 Legacy로 생각하여 문서가 작성돼있다.

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

jcl-over-slf4 log4j-over-slf4j slf4j-reload4j slf4j-log4j jul-to-slf4j slf4j-jdk14 등이 있다.

 

다만 주의할 점은

1. log4j-over-slf4j, slf4j-reload4j.jar와 slf4j-log4j.jar 중 1가지만 사용해야 하며

 

  • log4j-over-slf4j
    • Log4j 1.x APISLF4J API로 리다이렉트
    •  기존에 Log4j 1.x를 사용하는 코드가 SLF4J를 통해 로그를 기록하도록 변환
  • slf4j-reload4j, slf4j-log4j.jar (또는 slf4j-log4j12.jar):
    • 이 라이브러리는 SLF4J와 Log4j 1.x 혹은 reload4j (Log4j 1.x의 포크된 버전) 를 연결한다.
    • SLF4J가 Log4j 1.x를 로깅 구현체로 사용하게 해준다.

log4j-over-slf4j와 slf4j-reload4j가 공존할 경우 위와 같은 Cycle이 생기기 때문이다.

 

Log4j2는 log4j-slf4j-impl 만을 유일한 binding library로 이용하기 때문에 문제가 발생하지 않는다.

 

2. jul-to-slf4j와 slf4j-jdk14 중 1가지만 사용해야 한다.

  • jul-to-slf4j
    • JUL (java.util.logging) 로그 호출을 SLF4J API로 리다이렉트
    • JUL을 사용하는 기존 코드가 SLF4J를 통해 다른 로깅 구현체(예: Logback, Log4j2 등)를 사용해 로그를 기록할 수 있게 변환해줍니다.
  • slf4j-jdk14
    • SLF4J를 JUL에 바인딩합니다.
    • SLF4J를 사용하는 코드가 JUL을 로깅 구현체로 사용하여 로그를 기록

jul-to-slf4j와 slf4j-jdk14가 공존할 경우 위와 같은 Cycle이 생기기 때문이다.

 

위를 잘 생각해서 Dependency를 선언하도록 한다.

최신 Spring 버전을 사용하는 경우 Binding이나 Bridge에 대해 고려하지 않고,

위의 Dependencies 항목에 기술된 것으로 사용하면 된다.

 

 

 

 

 

참고문서

 

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

https://en.wikipedia.org/wiki/Facade_pattern

'기타 주제' 카테고리의 다른 글

Maven / Gradle의 Dependencies Conflicts  (1) 2024.09.02
piix4smbus: Host SMbus controller not enabled  (0) 2022.10.05
Github actions 관련 정보  (0) 2022.09.15
Lex 와 Yacc  (0) 2021.04.12