람다를 사용한 Sl4j 보일러 플레이트 개선

 

Slf4j의 문제점

아래는 로그 레벨을 Debug로 설정해뒀을 때만 출력되는 로그 예제이다.

if (logger.isDebugEnabled()) {
    logger.debug("{}", foo.veryExpensiveMethod());
}

이렇게 작성하는 이유는 다음과 같다:

  • foo.veryExpensiveMethod()는 실행 비용이 크다.
  • isDebugEnabled() 조건문을 사용해서 로그 레벨이 DEBUG가 아니라면 호출을 막아 비용을 절감한다.

하지만 매번 조건문을 작성해야 해서 보일러플레이트 코드가 발생한다.

 

 

람다(Supplier)를 이용한 개선 방식

Java 8부터 도입된 람다를 사용하여 개선할 수 있다. 람다는 함수의 마지막 파라미터를 함수 본문으로 옮길 수 있기 때문인데...


🔹 Supplier<Object>는 인터페이스다

@FunctionalInterface
public interface Supplier<T> { 
	T get(); // T 타입의 값을 반환한다.
}

즉, Supplier<T>는 아무런 입력도 받지 않고, T 타입의 결과만 반환하는 함수다.

즉, Supplier<Object>는 아무런 입력도 받지 않고, Object 타입의 결과만 반환하는 함수다.

Supplier<Object> supplier = () -> "Hello";


여기서 "Hello"는 String이지만, String은 Object의 하위 타입이기 때문에 OK.
supplier.get()을 호출하면 "Hello"라는 Object 타입 결과가 반환된다.

🔹 람다는 Supplier의 get() 메서드를 구현하는 방식

() -> "some string"

이건 자바에서 익명으로 Supplier의 get() 메서드를 구현한 것이다. (익명이므로, get() 메서드의 이름이 없는 것)

위의 람다는 다음과 동일하다:

new Supplier<Object>() { 
	@Override
	public Object get() { 
		return "some string"; 
	} 
}

 

 

즉, 이 두 가지는 동일한 의미이다:

logger.debug(() -> "some string");


logger.debug(new Supplier<Object>() {
    @Override
    public Object get() {
        return "some string";
    }
});

자바에서는 Supplier<Object>처럼 함수형 인터페이스를 사용할 때,

  • new Supplier<>() { ... } 대신,
  • 간단하게 () -> ... 식으로 람다를 쓸 수 있다.

 

 

logger.debug(() -> foo.veryExpensiveMethod().toString());
logger.debug(() -> "Hello " + person + " " + person);

이 방식이 가능한 이유는 로그 라이브러리(SLF4J, Log4j 등)가 다음과 같은 메서드 오버로드(overload)를 제공하기 때문이다:

public void debug(Supplier<Object> messageSupplier);

🔹 오버로드란?

오버로드란 같은 이름의 메서드를 서로 다른 매개변수 형태로 여러 개 정의하는 것이다.
logger.debug(...)는 문자열, 포맷+파라미터, 그리고 Supplier<Object>를 받는 여러 버전이 존재할 수 있다.


✅ 왜 이 방식이 성능에 좋을까?

람다는 지연 실행(lazy execution)이다.

() -> foo.veryExpensiveMethod().toString()

이 표현식은 Supplier<Object>의 get() 메서드를 구현한 것이다.
이 람다는 단지 함수 자체를 전달하는 것이며, 실제로 실행되지 않는다.

logger.debug(Supplier<Object>)의 내부 구현은 보통 이렇게 되어 있다:

public void debug(Supplier<Object> msgSupplier) {
    if (isDebugEnabled()) {
        Object msg = msgSupplier.get(); // 이때서야 람다 실행
        logToConsoleOrFile(msg.toString());
    }
}

즉,

  • 로그 레벨이 DEBUG일 때만 get()이 호출되어 람다가 실행된다.
  • 로그 레벨이 INFO나 WARN이면 람다는 아예 실행되지 않는다.
  • 결과적으로 foo.veryExpensiveMethod()도 호출되지 않으므로 불필요한 비용을 아낄 수 있다.

 

'【 개발 이야기 】' 카테고리의 다른 글

enum 클래스  (1) 2025.06.24
MDCContext  (2) 2025.06.20
[Spring Boot] WebFilter  (1) 2025.06.19
[coroutine] runBlocking  (0) 2025.06.18
e.stackTrace와 e.stackTraceToString  (1) 2025.06.18