이전 포스팅 스프링부트 라이브러리에서 주요 라이브러리 중
spring-boot-starter-logging
에 대해서 더 자세히 알아보겠습니다.
실제 현업에서의 개발에서는
System.out.println
등의 출력문을 사용하지 않고Log
를 통해서만 시스템을 관리한다고 합니다. 이점으로는유연성과 관리 용이성
,성능
,로그 관리
,확장성
등이 있습니다.그렇다면
log
는 무엇일까부터 시작해보겠습니다.
로그(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
은log4j의 후속작
으로,log4j에 비해 향상된 기능을 제공
하는 Java 로깅 라이브러리입니다. SLF4J(Simple Logging Facade for Java)의 개발자인 Ceki Gülcü에 의해 개발되었으며,SLF4J를 기본 로깅 파사드로 사용
합니다. 이는logback
이SLF4J API를 직접 구현
한다는 것을 의미하며,추가적인 브리지나 어댑터 없이 SLF4J와 함께 자연스럽게 작동
합니다.
기본적으로
spring-boot-starter-web
안에spring-boot-starter-logging
의logback
이 기본적으로 포함되어 있으므로 별다른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();
}
}
Java 버전의 업그레이드로 인해
log4j
사용 시 간혹 발생할 수 있는 호환성 문제가 간혹 있다고 합니다.log4j
에서logback
으로 전환을 하려면 어떤 과정을 거칠까요 ?
먼저, 프로젝트의 빌드 도구 maven
또는 gradle
을 사용하여 의존성 관리를 진행합니다.
log4j
의존성을 제거(exclude)하고, 대신 logback
의존성을 프로젝트에 추가합니다.
다음으로 코드 내에서 log4j
의 직접적인 사용을 logback
으로 변경하는 작업이 필요할 수 있으며, 이는 모두 개발자가 수작업으로 진행하게 됩니다.
그렇다면 SLF4J
를 사용하면 어떨까요 ?
SLF4J
는 log4j
, 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=====");
}
}
위 코드는 log4j
의 Logger
와 LoggerFactory
를 직접 사용합니다.
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=====");
}
}
이 코드는 SLF4J
의 Logger
와 LoggerFactory
를 사용합니다. SLF4J
는 로깅 프레임워크의 추상 계층으로 작동하므로, logback
, log4j2
등 다른 로깅 프레임워크로 손쉽게 전환할 수 있습니다. 프레임워크 전환 시, pom.xml
이나 build.gradle
등의 의존성 설정만 변경하면 되며, 코드 자체는 변경할 필요가 없습니다.
SLF4J
를 사용하는 것은 로깅 프레임워크 간의 결합도를 낮추고, 유연성이 향상되며, 이는 장기적인 유지보수와 로깅 시스템의 확장성 측면에서 매우 유리합니다.
정의
: 로깅에 대한 추상 레이어를 제공합니다. 즉, 개발자가 애플리케이션 내에서 로깅을 수행할 수 있게 하는 추상 메서드들로 구성됩니다.
구성
: slf4j-api
모듈이 이 부분을 담당합니다. slf4j-api
는 로깅 기능을 정의하는 인터페이스만 포함하며, 실제 로깅 기능의 구현은 이 인터페이스를 구현한 Binding을 통해 제공됩니다.
동작
: SLF4J
인터페이스를 실제 로깅 프레임워크와 연결하는 어댑터로, SLF4J API
를 구현한 클래스에서 Binding
을 통해 실제 로깅 프레임워크의 API를 호출합니다.
SLF4J Binding
만 존재해야 합니다.목적
: 다른 로깅 프레임워크로의 로거 호출을 SLF4J
인터페이스로 리다이렉트하여, SLF4J API
가 로깅을 처리할 수 있도록 합니다. 이는 기존에 다른 로깅 프레임워크를 사용하는 코드를 SLF4J
로 전환하고자 할 때 유용합니다.
예시
: log4j-over-slf4j
, jcl-over-slf4j
, jul-to-slf4j
등의 모듈이 있습니다. 이들 모듈은 log4j
, JCL
, JUL
등의 로깅 호출을 SLF4J
로 리다이렉트하는 역할을 합니다.