[Spring] 로그(Log)와 로깅 프레임워크(Logging Framework)에 대해 알아보자 - Log4j, Logback, Log4j2
System.out.print()
로그가 아닌 System.out.print()로도 콘솔에 기록을 남길 수 있으나 이를 사용하면 문제가 있다. print() 메서드를 살펴보면 synchronzied로 동기화가 되어있는 것을 볼 수 있다. 그러면 기록을 남길 때마다 쓰레드 lock이 걸리기 때문에 엄청난 성능 저하를 불러일으키게 된다. 혹여나 개발 단계에서 이를 사용했다 하더라도 운영시에는 이를 모두 삭제해줘야 한다. 방치하면 I/O 요청이 발생할 때마다 쓸데없는 리소스를 잡아먹게 된다.
로그(Log)
로그(Log)는 기록을 남기는 것이다. 로깅을 하면 앱 개발시 운영 중 발생하는 문제점을 모니터링하거나 추적하는 데 용이하다. 또한 해당 데이터를 분석해 통계를 낼 수도 있다. 하지만 로그를 잘못 사용하면 쓰기 작업이 방대하게 몰리는 문제를 겪거나 무의미한 로그만 쌓이게 되는 불상사가 생길 수도 있다.
기존 로그 쓰기 작업으로 인한 성능 저하에 비해 로그 기록으로 얻는 이점이 더 많기 때문에 사용한 것인데, 이렇게 잘못 사용하게 되면 로그를 써야할 이유가 없어지게 되는 것이다. 그러니 효율적으로 로깅하는 방법을 이해하는 것이 중요하다.
스프링 부트에서 로그(Log) 사용하기
스프링부트는 JCL(Jakarta(Apache) Commons Logging)을 사용해서 로깅(Logging)을 구현했다. JCL은 로깅 추상화 라이브러리이기 때문에 로깅 라이브러리 선택권은 개발자의 몫이다. 현재 대표적인 로그 구현체는 위에서 다룬 Log4(2), Logback 두 가지가 있다. JCL을 사용하면 얼마든지 로깅 구현체를 교체하거나 직접 구현할 수 있다.
그러나 JCL에 문제점이 있었는데 JCL 구현체를 선택하는 시점이 런타임이라 클래스 로더에 의존적이라는 것이다. 여러 문제가 있는데 짧게 말을 빌려오면 런타임 시점에 JCL이 지정된 클래스 로더에 대해 참조하고 있으면 해당 클래스 로더에 의해 로드된 리소스가 가비지 컬렉션 수집을 방해하여 메모리 누수가 발생한다. 즉, 가비지 컬렉션이 제대로 동작하지 않는 것이다.
이러한 문제를 해결하기 위해 클래스 로더 대신 컴파일 시점에 구현체를 선택하도록 변경되었다. 그게 현재 스프링부트에서 제공하고 있는 SLF4J이다. SLF4J에서는 Bridging, API, Binding 모듈을 제공하여 컴파일 시점에 로깅 구현체를 결정한다.
- SLF4J API는 로깅 인터페이스이다.
- SLF4J Binding은 말 그대로 어댑터 역할을 하여 SLF4J API 인터페이스와 로깅 구현체를 연결해준다. (예. slf4j-log4j12-{version}.jar)
- SLF4J Bridge는 로거 호출을 SLF4J 인터페이스로 연결해준다. (예. Log4J → SLF4J API)
로깅 프레임워크 종류 (Log4j, Logback, Log4j2)
이러한 Slf4j 인터페이스를 통해 다른 여러 로그 프레임워크 구현체와 연동을 간단하게 이룰 수 있다. 스프링 부트에선 기본적으로 Logback이 설정되어 있다. 그래서 아무런 설정을 하지 않을 경우 @Slf4j로 Logback 기능을 지원받아 로깅을 사용할 수 있다. 이외에도 log4j2라는 로그 프레임워크가 있다. deprecated된 log4j의 다음 버전으로 Logback이 제공하는 기능 외에 여러 기능을 추가적으로 제공해주고 있다.
1. Log4j (Deprecated)
Log4j는 가장 오래된 프레임워크이며 Apache기반 Logging 프레임워크이다. xml, properties로 환경을 구성하고 콘솔 및 파일 I/O 형태로 로깅을 할 수 있게 도와준다. 그런데 이는 서비스가 중단되었기 때문에 더 이상 사용되지 않는다. 구성은 다음과 같이 3가지로 되어있다.
- logger: 출력할 메세지를 appender에 전달
- appender: 전달된 로그를 어디에 출력할 것인지 설정 (Console, File, Slack 등)
- layout: 로그를 어떤 형식으로 출력할 것인지 설정
2. Logback
Logback은 log4j 이후에 출시된 Java 기반 Logging 프레임워크이다. 해당 프레임워크는 logback-core, logback-classic, logback-access 세 부분으로 구성되어있다.
- logback-core는 로깅 프레임워크의 핵심 기능을 제공한다.
- logback-access는 이를 서블릿 컨테이너와 통합하여 HTTP 액세스 로그를 작성하는 데 사용할 수 있다.
- logback-classic의 Logger클래스는 기본적으로 slf4j API를 구현한다. 즉, Slf4j의 구현체이므로 스프링 부트 환경이라면 별도의 의존성 추가없이 기본적으로 포함되어 있다.
log4j와 동일한 개념을 따르지만 보다 더 다양한 기능을 제공하고 성능을 개선하였다.
- 필터링: log4j보다 더 많은 필터링 기능을 제공한다.
- 자동 리로드: logback- classic은 수정시 구성 파일을 자동으로 리로드할 수 있다.
- 등등 문서 참고
3. Log4j2
Log4j2는 Apache의 log4j의 다음 버전이며 가장 최근에 등장한 프레임워크이다. 이는 Logback와 동일하게 자동 리로드와 필터링 기능을 제공하고 이 외에도 Java8부터 도입된 람다식 지원과 Lazy Evalutaion, 가비지 프리 기능들을 제공하고 있다. Logback과 가장 큰 차이는 Log4j2는 멀티스레드 환경에서 비동기 로거를 사용할 경우 10배 가까운 처리량을 처리할 수 있다는 것이다.
1) Log4j2 비동기 vs 동기 최대 처리량
동기 로거(Sync Logger), 비동기 로거(Async Logger), 비동기 어펜더(Async Appender) 모든 스레드 총 처리량을 비교한 결과이다. 64개 스레드를 사용한 결과 비동기 로거가 비동기 어펜더보다 12배, 동기 로거보다 68배 빨랐다.
2) Log4j, Logback과 최대 처리량 비교
비동기 로거(Async Logger)의 최대 처리량을 log4j, logback과 비교한 결과이다. Log4j와 Logback의 비동기 어펜더와 비교했으며 위에서 비교한 결과와 유사했다. 비동기 어펜더(Async Appender)의 경우 많은 스레드를 추가할 때 총 로깅 처리량이 거의 일정하게 유지된다. 그러나 비동기 로거는 쓰레드가 늘어날수록 머신에서 사용할 수 있는 멀티코어 환경보다 더 효율적으로 처리한다.
- 만약 Log4j2로 비동기 어펜더만 사용한다면 Logback과 큰 성능 차이는 없다.
참고
https://www.slideshare.net/whiteship/ss-47273947
https://logging.apache.org/log4j/2.x/manual/index.html
https://nikoskatsanos.com/blog/2016/08/log4j2-vs-log4j/
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.logging