스프링 라이브러리 logback, slf4j을 알아봅시다 !

김동헌·2024년 3월 12일
0

SpringBoot

목록 보기
14/19
post-thumbnail

이전 포스팅 스프링부트 라이브러리에서 주요 라이브러리 중 spring-boot-starter-logging에 대해서 더 자세히 알아보겠습니다.

실제 현업에서의 개발에서는 System.out.println 등의 출력문을 사용하지 않고 Log를 통해서만 시스템을 관리한다고 합니다. 이점으로는 유연성과 관리 용이성, 성능, 로그 관리, 확장성 등이 있습니다.

그렇다면 log는 무엇일까부터 시작해보겠습니다.


log & logging

로그(log)는 소프트웨어의 이벤트를 기록하는 것으로, 시스템의 상태와 동작 정보를 시간에 따라 기록합니다. 이는 소프트웨어 개발 및 운영에서 중요하며, 문제 발생 시 진단과 해결하는 데 도움이 됩니다.
또한 운영 및 관리에 필요한 정보를 제공하며 이를 로깅(logging)이라고 합니다.

Java 언어에서는 다양한 로깅 라이브러리를 지원하며 SpringBoot에서는 logback과 slf4j를 표준으로 사용하고 있습니다.


그냥 간단하게 System.out.println()을 하면 안될까 ?

성능 저하

System.out.println(); 메서드의 호출 과정을 살펴보겠습니다.

  • println Code
public void println() {
        newLine();
    }

  • newLine(); Code

    private void newLine() {
        try {
            if (lock != null) {
                lock.lock();
                try {
                    implNewLine();
                } finally {
                    lock.unlock();
                }
            } else {
                synchronized (this) {
                    implNewLine();
                }
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }
    

System.out.println() 메서드의 호출 과정에서 Java는 newLine() 메서드를 사용해 새 줄을 추가합니다. 이 과정에서 출력 스트림이라는 공유 자원의 안전한 사용을 위해 동기화 메커니즘이 적용됩니다.

이는 존재하는 lock객체를 통해 다른 스레드의 접근을 제어하며
lock객체가 null이 아닐 경우 명시적으로 자원을 락(lock)하고 언락(Unlock)합니다.
만약 lock객체가 null이면, synchronized (this)를 사용해 현재 객체에 대한 동기화를 진행합니다. 이 동기화는 한 스레드가 출력 스트림을 사용 중일 때 다른 스레드가 대기하도록 하여, 이때 동기화로 인한 성능 저하의 원인이 될 수 있습니다.

이러한 동기화는 데이터의 일관성과 스레드의 안정성을 보장하지만 블로킹을 통해 처리 속도 저하의 가능성을 내포하고 있습니다.

휘발성

System.out.print()로 출력된 로그는 콘솔에만 나타나며, 프로그램이 종료되면 이 정보는 사라집니다. 따라서 로그가 발생한 날짜, 시간, 로그 발생 위치(클래스명, 메소드명, 라인 넘버등..)와 같은 추적 정보를 기록하지 않기 때문에 문제의 원인을 파악하고 해결할 수 없습니다.

로그 레벨

로그 레벨(DEBUG, INFO, WARN, ERROR 등)을 사용해 로그 중요도에 따라 메시지를 구분할 수 있어, 에러나 장애 발생 시 필요한 정보를 파악하기 용이합니다.

하지만 System.out.print()와 같은 출력된 로그는 휘발성으로 로그 레벨에 따른 로깅이 불가능합니다.

만약 로그 레벨을 관리 없이 System.out.print를 통해 출력되는 모든 로그 정보가 파일에 저장될 경우 중요도가 낮은 정보까지 모두 파일에 저장되어 서버의 저장 공간을 불필요하게 사용할 수 있습니다. 특히 대규모 시스템이나 장기간 운영되는 서비스에서는 이러한 불필요한 로그 데이터가 상당한 저장 공간을 차지할 수 있으며, 이로 인해 시스템 성능에도 영향을 줄 수 있습니다.

로그 레벨을 적절히 설정하여 중요한 정보만을 기록해야 효율적으로 데이터를 관리할 수 있고, 서버 저장 공간을 절약할 수 있습니다.


LogBack

logbacklog4j의 후속작으로, log4j에 비해 향상된 기능을 제공하는 Java 로깅 라이브러리입니다. SLF4J(Simple Logging Facade for Java)의 개발자인 Ceki Gülcü에 의해 개발되었으며, SLF4J를 기본 로깅 파사드로 사용합니다. 이는 logbackSLF4J API를 직접 구현한다는 것을 의미하며, 추가적인 브리지나 어댑터 없이 SLF4J와 함께 자연스럽게 작동합니다.

기본적으로 spring-boot-starter-web안에 spring-boot-starter-logginglogback이 기본적으로 포함되어 있으므로 별다른 dependency 추가 없이 사용할 수 있습니다.

logback은 설정 변경에 따른 자동 리로딩 기능을 제공하여, 서버 재시작 없이 로그 레벨 조정과 같은 수정 사항을 즉각 반영할 수 있어, 운영 중인 애플리케이션의 로깅 관리를 효율적으로 수행할 수 있습니다.

Sample Code : 다양한 로그 레벨에서 메시지를 로깅

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

public class LogbackLogger {

    // Logger 인스턴스 생성
    private static final Logger logger = LoggerFactory.getLogger(LogbackLogger.class);

    public void method() {
        // 다양한 로그 레벨에서 메시지 로깅
        logger.trace("This is a TRACE message.");
        logger.debug("This is a DEBUG message.");
        logger.info("This is an INFO message.");
        logger.warn("This is a WARN message.");
        logger.error("This is an ERROR message.");
    }

    public static void main(String[] args) {
        LogbackLogger example = new LogbackLogger();
        example.method();
    }
}

SLF4J

Java 버전의 업그레이드로 인해 log4j 사용 시 간혹 발생할 수 있는 호환성 문제가 간혹 있다고 합니다. log4j에서 logback으로 전환을 하려면 어떤 과정을 거칠까요 ?

먼저, 프로젝트의 빌드 도구 maven 또는 gradle을 사용하여 의존성 관리를 진행합니다.
log4j 의존성을 제거(exclude)하고, 대신 logback 의존성을 프로젝트에 추가합니다.

다음으로 코드 내에서 log4j의 직접적인 사용을 logback으로 변경하는 작업이 필요할 수 있으며, 이는 모두 개발자가 수작업으로 진행하게 됩니다.


그렇다면 SLF4J를 사용하면 어떨까요 ?
SLF4Jlog4j, logback, java.util.logging 등과 같은 다양한 로깅 프레임워크에 대한 추상 계층을 제공합니다. 이를 통해, 개발자는 프로젝트에서 사용하는 로깅 구현체를 손쉽게 교체할 수 있게 됩니다.

특히, SLF4J를 사용하면 로깅 구현체의 교체 시 코드의 변경을 최소화할 수 있습니다. 이는 SLF4J 인터페이스를 사용하여 로깅을 수행하기 때문이며, 구현체 변경은 의존성 관리 및 설정 조정을 통해 이루어지므로, 코드 자체의 변경 없이 로깅 시스템을 업그레이드하거나 변경할 수 있습니다.

이렇게 SLF4J를 사용하면 로깅 시스템의 유연성이 크게 향상되며, Java 버전 업그레이드와 같은 상황에서 발생할 수 있는 로깅 관련 이슈에 대응하기 쉬워집니다.

SLF4J는 로깅 구현체 사이의 결합도를 낮추고, 프로젝트의 유지보수성을 향상시키는 중요한 역할을 합니다.


코드로 이해하기

log4j에 직접 의존하는 코드 예시와 SLF4J 추상체에 의존하여 로깅 프레임워크를 쉽게 변경할 수 있는 코드 예시로 살펴보겠습니다.

log4j에 직접 의존하는 Sample Code

import org.apache.log4j.Logger;

public class Log4jExample {
    private static final Logger log = Logger.getLogger(Log4jExample.class);

    public static void main(String[] args) {
        log.warn("=====log4j=====");
    }
}

위 코드는 log4jLoggerLoggerFactory를 직접 사용합니다.
log4j 라이브러리에 직접 의존하여 결합도가 높아지고, 따라서 다른 로깅 프레임워크로 전환하기 위해서는 코드를 직접 수정해야합니다.


SLF4J 추상체에 의존하는 Sample Code

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

public class Slf4jExample {
    private static final Logger logger = LoggerFactory.getLogger(Slf4jExample.class);

    public static void main(String[] args) {
        logger.info("=====slf4j=====");
    }
}

이 코드는 SLF4JLoggerLoggerFactory를 사용합니다. SLF4J는 로깅 프레임워크의 추상 계층으로 작동하므로, logback, log4j2 등 다른 로깅 프레임워크로 손쉽게 전환할 수 있습니다. 프레임워크 전환 시, pom.xml이나 build.gradle 등의 의존성 설정만 변경하면 되며, 코드 자체는 변경할 필요가 없습니다.

SLF4J를 사용하는 것은 로깅 프레임워크 간의 결합도를 낮추고, 유연성이 향상되며, 이는 장기적인 유지보수와 로깅 시스템의 확장성 측면에서 매우 유리합니다.


SLF4J 동작 과정

SLF4J API(인터페이스)

정의 : 로깅에 대한 추상 레이어를 제공합니다. 즉, 개발자가 애플리케이션 내에서 로깅을 수행할 수 있게 하는 추상 메서드들로 구성됩니다.

구성 : slf4j-api 모듈이 이 부분을 담당합니다. slf4j-api는 로깅 기능을 정의하는 인터페이스만 포함하며, 실제 로깅 기능의 구현은 이 인터페이스를 구현한 Binding을 통해 제공됩니다.

SLF4J Binding

동작 : SLF4J 인터페이스를 실제 로깅 프레임워크와 연결하는 어댑터로, SLF4J API를 구현한 클래스에서 Binding을 통해 실제 로깅 프레임워크의 API를 호출합니다.

  • 주의할점으로 로깅 프레임워크 간의 충돌을 방지하고, 일관된 로깅 동작을 보장하기 위해 프로젝트에서는 하나의 SLF4J Binding만 존재해야 합니다.

SLF4J Bridgin Modules

목적 : 다른 로깅 프레임워크로의 로거 호출을 SLF4J 인터페이스로 리다이렉트하여, SLF4J API가 로깅을 처리할 수 있도록 합니다. 이는 기존에 다른 로깅 프레임워크를 사용하는 코드를 SLF4J로 전환하고자 할 때 유용합니다.

  • 예시 : log4j-over-slf4j, jcl-over-slf4j, jul-to-slf4j 등의 모듈이 있습니다. 이들 모듈은 log4j, JCL, JUL 등의 로깅 호출을 SLF4J로 리다이렉트하는 역할을 합니다.
profile
백엔드 기록 공간😁

0개의 댓글