Log4j 의 한계

Brunch Kim·2024년 12월 27일

logging

목록 보기
1/2
post-thumbnail

Java 진영에서 로깅 라이브러리를 선택할 때, Log4j 보다는 Log4j, Logback 을 사용해야합니다.
왜 그럴까요?
Log4j는 오래도록 Java 진영에서 널리 사용된 로깅 라이브러리였지만, 성능 면에서 몇 가지 아쉬운 점이 있었습니다. 그 구체적인 문제점들을 하나씩 보도록 하겠습니다.


동기적 로깅 (Synchronous Logging) 문제

Log4j의 동기적 로깅은 로그를 기록할 때마다 기다려야 하므로 성능에 영향을 미칩니다.
이 부분을 코드 예시로 보면 다음과 같습니다.

import org.apache.log4j.Logger;

public class Log4jExample {
    // Log4j Logger 객체 생성
    static final Logger logger = Logger.getLogger(Log4jExample.class);

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            // 매번 로그를 기록할 때마다 기다리므로 성능 저하
            logger.info("This is an info message: " + i);
        }
    }
}

문제점

  • 매번 로그를 기록할 때 디스크 I/O가 발생하며, 로그 기록이 완료될 때까지 기다려야 하므로, 전체 애플리케이션 성능이 떨어집니다.
  • 대량의 로그를 기록할 때, 이 동기적 방식은 매우 비효율적입니다.

파일 I/O 성능 문제

Log4j의 기본 설정은 파일 I/O를 동기적으로 처리합니다. 로그를 디스크에 기록하는 과정에서 I/O가 블로킹되기 때문에 성능 저하를 일으킵니다.

<!-- log4j.xml 설정 예시 -->
<configuration>
    <appender name="file" class="org.apache.log4j.FileAppender">
        <param name="File" value="app.log"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n"/>
        </layout>
    </appender>

    <root>
        <level value="INFO"/>
        <appender-ref ref="file"/>
    </root>
</configuration>

문제점

  • 위 코드에서 FileAppender를 사용해 로그를 파일에 기록할 때, 파일 I/O 작업이 동기적으로 처리됩니다.
  • 디스크에 로그를 기록하는 동안 애플리케이션이 대기하게 되어 성능 저하를 초래합니다. 이 문제는 특히 대량의 로그를 다룰 때 더욱 문제가 됩니다.

불필요한 로그 포맷팅

Log4j에서는 로그 메시지를 기록할 때마다 포맷을 처리합니다. 이 포맷팅 작업은 특히 DEBUGTRACE 레벨에서 자주 발생하는 경우 성능을 저하시킬 수 있습니다.

import org.apache.log4j.Logger;

public class Log4jFormatExample {
    static final Logger logger = Logger.getLogger(Log4jFormatExample.class);

    public static void main(String[] args) {
        // DEBUG 레벨 로그는 포맷팅 작업을 매번 수행
        for (int i = 0; i < 100000; i++) {
            logger.debug("User " + i + " has logged in.");
        }
    }
}

문제점

  • logger.debug()는 로그를 기록할 때마다 메시지 포맷팅을 수행합니다.
  • 대량의 로그가 발생하는 환경에서, 매번 포맷팅 작업을 수행하면 불필요한 계산이 많아지고, 시스템의 성능이 저하될 수 있습니다.
  • 예를 들어, 위 코드에서 100,000번의 DEBUG 로그를 기록할 때, 포맷팅 작업이 매번 발생하므로 성능 저하가 일어날 수 있습니다.

멀티스레딩 환경에서의 성능 저하

멀티스레딩 환경에서 여러 스레드가 동시에 로그를 기록할 때, 동기화가 필요하기 때문에 성능이 떨어질 수 있습니다. Log4j는 기본적으로 동기화된 로깅을 제공하는데, 이로 인해 스레드 간 경쟁이 발생할 수 있습니다.
다음 멀티스레드 환경에서의 성능 저하를 일으키는 코드를 보겠습니다.

import org.apache.log4j.Logger;

public class Log4jMultithreadingExample {
    static final Logger logger = Logger.getLogger(Log4jMultithreadingExample.class);

    public static void main(String[] args) {
        // 멀티스레드 환경에서 동시에 로그를 기록하는 예시
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                logger.info("Thread " + Thread.currentThread().getName() + " - Log " + i);
            }
        };

        // 5개의 스레드에서 동시에 로그를 기록
        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}

문제점

  • 위 코드에서는 5개의 스레드가 동시에 logger.info()를 호출하고 있습니다.
    동기화된 로깅에서는 여러 스레드가 동시에 로그를 기록할 때 락(lock)을 사용하여 경쟁 상태를 피하려고 합니다. 하지만 이로 인해 락 경합이 발생하고, 스레드들이 대기하게 되므로 성능이 저하됩니다.
  • 특히, 멀티스레드 환경에서 로그 기록을 동기화하면 경쟁 조건이 발생할 수 있어, 성능이 떨어지게 됩니다.

정리

  1. Log4j의 동기적 로깅은 디스크 I/O와 동기화 작업이 애플리케이션 성능에 부정적인 영향을 미친다는 점에서 문제가 될 수 있습니다.
  2. 불필요한 포맷팅은 자주 발생하는 로그에서 성능을 저하시킬 수 있습니다. 이를 해결하려면 필요한 로그 레벨만 포맷팅하도록 조정하거나, 비동기 로깅을 사용하여 성능을 개선할 수 있습니다.
  3. 멀티스레드 환경에서의 성능 저하는 로그 기록을 동기화할 때 발생하는 락 경합 때문에 성능 저하를 일으킵니다. 이를 해결하기 위해 비동기 로깅을 사용하여 각 스레드가 대기하지 않고 로그를 기록할 수 있게 할 수 있습니다.

대안

우리는 Log4j 를 대신하여 로깅처리를 할 수 있도록 다음과 같은 대안을 생각해볼 수 있습니다.

  1. Log4j2
  2. Logback

다음 포스팅을 통해서 Log4j 보다 더 나아진 방식들에 대해 알아보도록 하겠습니다.
Logback 이 Log4j 보다 나아진 점
Logback 을 활용하여 log 남기기

profile
브런치 즐기는 여유있는 날

0개의 댓글