Java의 로깅
Java는 다양한 로깅 라이브러리를 지원한다.그러나 로깅 라이브러리마다 내부적인 동작이 다르며 일장일단이 있기 때문에 잘 알고 사용해야한다.
단순히 I/O를 수행하는 System.out.println에 비하여 로깅 라이브러리의 장점은 크게 다음과 같다
- 상황에 따른 Log 레벨 지정으로 유연한 디버깅과 로깅 가능
- 프로그램 실행에 따른 흐름과 예외 파악 가능
- System.out.println에 비하여 자유로운 출력 위치 지정 가능
- 각각 모듈 별,파일 별,클래스 별 등 유연하게 출력 가능
- 비교적 더 뛰어난 성능
위와 같은 이유로 단순한 System.out.println 보단 로깅 라이브러리를 사용하는 것이 더 좋다.
System.out.println() 의 성능 문제
앞서 System.out.println에 비하여 로깅 라이브러리의 다양한 장점을 살폈지만 그 중에서 성능 문제에 대하여 간단히 알아보자면
1. write()와 newLine()이 동기 메서드이다 - System.out.println()이나 System.out.print() 메서드 둘다 내부적으로 write()와 newLine()을 사용합니다.내부적으로 synchronized 키워드를 이용해서 구현이 되어 있다.때문에 다른 스레드들은 접근하지 못해서 성능을 낮추게 되는 원인이 된다.
2. Blocking I/O이다 - 내부적으로 시스템 콜을 호출하는 과정에서 Blocking으로 호출된다.때문에 해당 I/O가 발생하는 작업시간동안 CPU가 놀게 되어 성능 저하의 원인이 된다.또한 System.out.println은 로그들을 파일,DB,클라우드 로그 저장소 등으로 저장하기 적합하지 않으며,로깅 라이브러리들은 이를 비동기, Non-Blocking으로 이 문제를 해결하여 System.out.println 보다는 비동기 Non-Blocking 방식으로 출력되는 로깅 라이브러리들을 사용하는 것이 좋다.
Logger
Java 내부 java.util.logging 패키지에 속하여 있는 로깅을 위한 클래스로 외부 라이브러리 같은 것 없이 파일 또는 콘솔에 로그를 출력할 수 있다.
7개의 로그 레벨을 기본적으로 제공한다.
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
import java.util.logging.Logger;
class JavaLogger {
private static final Logger logger = Logger.getLogger(JavaLogger.class.getName());
void method() {
logger.log(Level.FINEST, "{0} log", "FINEST");
logger.info("info log");
}
}
Logger의 가장 큰 장점이라면 외부 라이브러리 없이 사용 가능하다는 것이다.
단점으론 여러가지가 있는데
- 다른 라이브러리와 비교하였을 때 비교적 속도가 느리다
- 사전에 정의된 레벨이 직관적이지 않다.예를 들어 FINE FINER FINEST 3가지의 구분을 빠르고 신속하게 할 수 있을까? 즉, 익숙해지는데 상대적으로 많은 비용이 소모된다
- 메시지를 포맷팅하기에는 부적절하며 타 라이브러리에 비하여 기능이 부족하다
크게 정리하면 다음과 같다.
Log4j
Apache의 Java 로깅 프레임워크로 가장 오래된 Java 로깅 프레임워크였지만 2015년에 개발이 중단되었다.콘솔 및 파일 출력의 형태로 로깅을 도우며 XML,Propeties 등으로 설정을 구성할 수 있다.
Log4j는 다음의 구성요소를 가진다.
요소 | 설명 |
Logger | 출력할 메세지를 Appender에 전달 |
Appender | 전달된 로그를 어디에 출력할 것인지 결정 |
Layout | 로그를 어떤 형식으로 출력할 것인지 결정 |
Log4j는 고유한 로그 레벨을 가지고 있다.
레벨 | 설명 |
FATAL | 치명적인 오류가 발생한 상황 |
ERROR | 요청을 처리하던 중 예외 발생 |
WARN | 현재는 문제가 아니지만 향후 오류의 원인이 될 수 있는 경고 메세지 |
INFO | 상태 변경과 같은 정보 제공성 메세지 |
DEBUG | 개발 시 디버그 용도의 메세지 |
TRACE | DEBUG보다 더 상세한 이벤트를 포함하는 메세지 |
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
class Log4jLogger {
private static final Logger logger = LogManager.getLogger(Log4jLogger.class);
void method() {
logger.info("info log: {}", 1);
}
}
Log4j를 사용해야 할까?
이미 오래전에 개발이 중단 되었으며 후술할 Slf4j와 호환되지 않고 후신격의 Log4j2가 존재하기 때문에 굳이 사용할 필요는 없다.
Slf4j
Simple Logging Facade for Java, java.util.logging, Logback 및 Log4j 등 다양한 로그 관련 프레임워크들의 인터페이스를 제공하는 라이브러리이다.Logger의 추상체로써, 인터페이스이므로 Slf4j 인터페이스를 사용해서 로깅하게 되면 구현체만 갈아 끼우면 Logback이나 Log4j2 등으로 마이그레이션 할 수 있다.
크게 Slf4j는 3가지 구성요소로 구성되여 있는데
구성요소 | 설명 |
Slf4j API | Sfl4j를 사용하기 위한 인터페이스를 제공 |
Slf4j Binding | Slf4j 인터페이스와 로깅 구현체(Log4j2 등)를 연결해주는 역할 |
SLF4J Bridging Modules | 다른 로깅 API로 Logger 호출을 할 때, Slf4j 인터페이스로 연결하여 Sl API가 대신 Logger를 처리할 수 있도록 하는 어댑터 역할 |
Logback
Slf4j의 구현체로서 Springboot의 기본 로깅 프레임워크이며 spring-boot-starter-web 내부에 포함되어 있어 별다른 의존성 추가 없이 사용할 수 있다.
또한 Automatic Reloading 기능을 제공하여 별도에 재시작없이 설정을 변경하여 사용할 수 있습니다.
• 특정 부분에서 더 자세한 로그를 봐야할 필요가 생겨 로그 레벨을 변경해야 할 때 이를 서버의 종료 없이 컨트롤이 가능
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class LogbackLogger {
private static Logger logger = LoggerFactory.getLogger(LogbackLogger.class);
void method() {
logger.trace("Trace");
logger.debug("Debug");
logger.info("Info");
logger.warn("Warn");
logger.error("Error");
}
}
Log4j2
Log4j2는 Log4j를 보안한 라이브러리로, 그리고 Facade 패턴으로 구현되어 다른 Log 라이브러리들과 사용할 수 있다. 예를 들어 Logback의 앞에서 Facade 형태로 사용될 수도 있다.Log4j2의 가장 눈에 띄는 기능들 가운데 하나는 비동기 성능으로, Log4j2는 LMAX 디스럽터를 활용하는데, 이 라이브러리는 커널 락의 필요성을 줄이며 12배만큼의 로깅 성능을 제공한다.동일 환경에서 Log4j 2는 1초에 18,000,000개 이상의 메시지를 기록할 수 있는 반면 Logback과 Log4j 등은 초당 2,000,000개 미만의 메시지를 기록할 수 있다.파일뿐만 아니라 HTTP, DB, Kafka에 로그를 남길 수 있으며 Springboot에서 사용하려면 spring-boot-starter-logging-log4j2를 주입해야 한다.
LMAX 디스럽터?
고성능 저지연 메시징 라이브러리로 금융 거래 시스템과 같은 고성능 시스템에서 저지연 처리를 목표로 개발되었다.
무엇을 사용해야 할까...?
로깅할 양이 많고 로깅에 성능이 중요하다면 Logback보단 Log4j2를 사용하는 것이 좋을 수 있다.또한 Logback, Log4j2 둘 다Slf4j를 구현하고 있기 때문에 구현을 교체하기에 편리할 수 있다.
'Java' 카테고리의 다른 글
[Java] JavaDoc (0) | 2025.05.05 |
---|---|
[Java] StableValue! (0) | 2025.04.07 |
[Java] Ehcache (1) | 2024.12.05 |
[Java] 변수의 스코프(Scope) (0) | 2024.04.22 |
[Java] for each문 (0) | 2024.04.17 |