Java Log 역사 요약
Java Log 역사 요약

Java Log 역사 요약

Tags
Java
Core
Published
September 14, 2025
Author
lkdcode
개발을 하다보면 원인 파악, 성능 진단, 버그/보안/감사 추적 등 로그를 통해 기록을 해야한다. 로그의 종류도 많고 어디에 기록할지도 다양한데, Java 에서 과거부터 지금까지 로그 이야기를 간단하게 요약한다.

🔥 로그

 
notion image
 
개발자는 로그를 남겨야한다. console 에 남길 수도 있고 file 로 남길 수도 있다. 이외에도 다양한 방법이 존재하는데 뭔가를 로깅하고 싶을 때마다 어떤 객체에 넘겨서 로깅을 수행하게 하는 요구 사항은 그리 어렵지 않다. 이미 JDK 에 내장되어 있는 로깅 라이브러리가 있는데 살펴보자.
 

🔥 java.util.logging

 
https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html
 
아주 오래전에 등장한 클래스인데 로깅을 수행한다. 로그 레벨이 살짝 다른데
  • SEVERE ↔ ERROR
  • WARNING ↔ WARN
  • INFO ↔ INFO
  • CONFIG ↔ DEBUG
  • FINE ↔ DEBUG
  • FINER ↔ TRACE
  • FINEST ↔ TRACE
각각의 로그 레벨은 우리가 잘 알고 있는 logback 레벨과 다르지만 사용하는데 문제는 없다.
또 Console/File 로 로그를 기록할 수 있게 Handler 객체를 제공하는데 이게 문제가 된다.
 

🚀 java.util.logging.Handler

 
notion image
ConsoleHandlerFileHandler 가 로그를 기록할 때 StreamHandlerflush(), publish() 메서드를 사용하여 로그를 기록하게 되는데, lock 을 획득하고 synchronized
 
notion image
.onsoleHandlerFileHandlerHandler 추상 클래스를 상속하는 메서드를 보면 항상 락 안에서 실행되므로 동일 인스턴스 기준으로 한 번에 한 쓰레드씩 처리됨을 알 수 있다. 이외에도 내부에서 포맷팅, 호출자 추론 비용, 비동기 미지원 등으로 인해 성능 이슈가 발생한다.
 
사람들은 로깅 라이브러리에 대한 필요성을 느꼈고 입맛에 맞지 않게 동작하는 JDK 의 기본 기능 때문에 라이브러리를 만들기 시작했고 수 많은 라이브러리들 중 선택받은 소수의 라이브러리만 널리 사용되었다.
 

🔥 log4j

대표적으로 log4j 가 가장 오래되고 인기 있었던 로깅 라이브러리 중 하나인데 자체 Logger 클래스를 가지고 있으며 logger.info, logger.debug 와 같은 메서드를 사용한다. JDK 로깅의 핸들러 대신 APPENDER 라는 개념을 사용했는데 마찬가지로 console, file, stmp, jdbc 등을 지원한다.
 

🚀 하지만, log4j 는 실제 구현체라서 문제다.

하지만 실체 구현체이기 때문에 버전 호환성 등 불가피하게 라이브러리를 전환할 때마다 코드의 로깅 부분을 모두 변경해야 되는 불상사가 발생한다. 이러한 문제를 해결하기 위해 JCL(Apache Commons Logging) 과 SLF4J 와 같은 퍼사드 라이브러리가 등장했으며 SLF4J 가 엄청난 인기를 얻게 된다.
 

🔥 SLF4J

notion image
실제 구현체가 아닌 인터페이스만 제공하며 개발자는 해당 표준 API 를 사용하여 로깅을 수행하고 실제 로깅은 다른 라이브러리가 구현하도록 만든다. Logback, Log4j 2와 같은 새로운 로깅 라이브러리들은 SLF4J 구현체를 기본적으로 제공하기 시작했다. 실제 로깅 라이브러리는 Logback or Log4j 2 중에서 사용하는 것이 권장된다.
 
이 둘은 더 이상 개발되지 않는 오래된 Log4j 의 후속작이다. Logback 을 일반적으로 추천되는데 Log4j 와 SLF4J 를 처음 만든 개발자가 개발했기 때문이다. Log4j 2 는 지연 평가를 위한 람다 표현식 지원, 비동기 로깅과 같은 특정 상황에서 Logback 보다 약간의 개선된 성능을 제공한다. 상황에 맞게 어떤 구현체를 사용할지 선택하면 된다.
 

🚀 실전으로!

  • 메인 메서드를 하나만 두고 로그를 출력해보자.
package lkdcode.server.log import org.slf4j.LoggerFactory fun main() { LoggerFactory.getLogger("lkdcode").info("HELLO") } class LkdCode
 

🎯 의존성 없이 SLF4J 만

  • 다른 의존성 없이 SLF4J 만 추가
notion image
 
  • 컴파일을 해보자. 성공!
notion image
 
  • 메인 메서드 실행 결과
notion image
SLF4J 는 인터페이스이므로 구현체인 바인딩이 필요한데 없다는 것이다.
 

🎯 slf4j-simple 의존성 추가하기

  • 이번엔 SLF4J 의 구현체 중 하나인 slf4j-simple 을 추가해보자.
notion image
 
  • 종속성을 추가해준후 컴파일을 해보자. 성공!
notion image
 
  • 메인 메서드 실행 결과
notion image
 
SLF4J 의 구현체인 slf4j-simple 가 잘 바인딩되어 콘솔에 로그가 찍혔다.
 

🎯 logback 의존성 추가하기

  • 이번엔 SLF4J 의 구현체 중 하나인 logback 을 추가해보자.
notion image
 
  • 컴파일 OK
notion image
 
  • 메인 메서드 실행 결과
notion image
 
SLF4J 의 구현체인 logback 가 잘 바인딩되어 콘솔에 로그가 찍혔다.
 

🎯 slf4j-simple + logback 의존성 추가하기

  • 다수의 바인딩을 추가한다.
notion image
 
  • 컴파일 성공
notion image
 
  • 메인 메서드 실행 결과
notion image
 
로그를 자세히 보면 여러 SLF4J 바인딩이 발견되었고 2개의 바인딩 중 SimpleServiceProvider(slf4j-simple) 이 선택되었다고 나와있다. 왜 일까?
  • SLF4J Error Codes
한 개 이상의 바인딩이 있는 경우 SLF4J 는 하나를 선택해 바인딩하는데 어떤 바인딩이 선택되는지는 JVM에 의해 결정되며 실질적으로는 랜덤으로 간주해야 한다. ServiceLoader 로 provider 들을 찾는데 명세상 순서는 보장하지 않는다.(클래스 로더가 반환한 순서. 랜덤이라곤 하지만 클래스 패스 순서가 안정적으로 같다면 매번 같은 바인딩이 선택된다. 아무리 시도해도 logback 은 안 나옴)