나는 항상 로그를 찍을때 이렇게 Logger를 클래스 최상단에 생성한 후 이용해 왔는데
Logger logger = Logger.getLogger(현재클래스.getName());
팀원이 내가 로그를 찍는 방식을 보더니 @Slf4j를 추천해 주었다.
그 후 직접 생성할 필요 없이 @Slf4j어노테이션만 달아주고 log.info , log.debug 등으로 바로 사용할 수 있어서 코드가 더 깔끔해지고 간편해졌다.
사용하다가 문득 저 해괴한 알파벳의 조합이 무슨 의미일까 궁금해서 찾아보았다
Slf4j : Simple Logging Facade for Java
Facade 패턴이란 ?
복잡한 서브시스템들의 통합 인터페이스를 제공하는 구조적 디자인 패턴
건물의 정면(facade)처럼, 내부의 복잡한 구조를 감추고 깔끔한 외관을 제공한다
Slf4j는 자바 로깅을 위한 파사드이다
다음과 같이 단순하게 사용할 수 있다
@Slf4j
public class MyService {
public void doSomething() {
log.info("Hello, SLF4J!");
}
}
Slf4j의 등장 이유
로그를 남기는데는 여러가지 방법이 존재한디
1. Log4j 직접 사용
import org.apache.log4j.Logger;
private static final Logger logger = Logger.getLogger(MyClass.class);
2. java.util.logging 직접 사용
import java.util.logging.Logger;
private static final Logger logger = Logger.getLogger(MyClass.class.getName());
3. Logback 사용
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = (Logger) LoggerFactory.getLogger(MyClass.class);
그러나 로깅 방식을 변경하려고 하면 dependency 및 import문, 코드까지 모든 것을 다 바꿔야 한다
4. SLF4J 사용
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
로깅 방식을 변경할 때 dependency만 변경하면 되고 코드는 그대로 사용할 수 있어서 유지보수가 더 편리해진다.
애플리케이션 - SLF4J - 로깅구현체 이렇게 연결되어 애플리케이션 코드와 로깅 구현체 간의 결합도가 낮아진다.
구체적인 로깅 구현체를 몰라도 사용할 수 있어 간편하다.
SLF4J의 동작 과정
구현체 바인딩 매커니즘
SLF4J는 시작 시 클래스패스를 스캔하여 org.slf4j.impl.StaticLoggerBinder 클래스를 찾는다.
발견된 바인더를 통해 실제 로깅 구현체와 연결되게 된다.
이후 모든 로깅 호출은 바인딩된 구현체로 전달된다.
1. StaticLoggerBinder 클래스 탐색
// LoggerFactory.java
private static void bind() {
try {
// 클래스패스에서 org.slf4j.impl.StaticLoggerBinder 검색
Class.forName("org.slf4j.impl.StaticLoggerBinder");
} catch (ClassNotFoundException e) {
throw new NoClassDefFoundError("로깅 구현체를 찾을 수 없습니다.");
}
}
2. 바인더를 통한 구현체 연결
// StaticLoggerBinder.java (Logback 구현체의 경우)
public class StaticLoggerBinder implements LoggerFactoryBinder {
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private LoggerFactory loggerFactory;
private StaticLoggerBinder() {
loggerFactory = new LogbackLoggerFactory();// 실제 구현체 초기화
}
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
}
3. 로깅 호출 처리
// LoggerFactory.java
public static Logger getLogger(Class<?> clazz) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(clazz.getName());
}
// LogbackLoggerFactory.java
public Logger getLogger(String name) {
// Logback 구현체의 Logger 인스턴스 생성 및 반환
return new LogbackLogger(LogbackFactory.getLogger(name));
}
이 구조를 통해 SLF4J는 컴파일 타임 의존성 없이 런타임에 유연하게 로깅 구현체를 연결할 수 있게 된다.
서비스 로케이터 패턴(Service Locator Pattern)
저걸 보다보니 이전에 IoC 공부할 때 DI 이외의 방법으로 IoC를 달성할 수 있을지 찾아보다가 공부했던 서비스 로케이터 패턴이 기억이 났다
Service Locator Pattern
IoC를 구현하는 한 가지 방법
중앙 레지스트리가 필요한 구현체를 찾아 제공한다
// 서비스 로케이터 정의
public class ServiceLocator {
private static Map<String, Service> services = new HashMap<>();
public static Service getService(String name) {
return services.get(name);
}
}
// 사용
Service userService = ServiceLocator.getService("userService");
원래대로라면 코드에서 구현체를 직접 다 변경해줘야 하는데
서비스 로케이터 패턴을 사용하면 유연하고 느슨한 결합으로 사용하는 측의 코드는 변경하지 않아도 된다
// IoC 적용 전: 직접 의존성 생성
class Client {
private Service service = new ServiceImpl();
}
// IoC 적용 후: 서비스 로케이터를 통해 의존성 획득
class Client {
private Service service = ServiceLocator.getService("service");
}
서비스 로케이터 패턴과 SLF4J의 동작 방식의 유사성
// 서비스 로케이터 패턴
public class ServiceLocator {
private static Map<String, Service> services = new HashMap<>();
public static Service getService(String name) {
return services.get(name);
}
}
// SLF4J
public class LoggerFactory {
public static Logger getLogger(Class<?> clazz) {
// 내부적으로 구현체를 찾아 반환
return findPlatformLoggerBinder().getLogger(clazz);
}
}
런타임에 유연하게 구현체를 찾아 바인딩하는 구조가 굉장히 비슷하다
내 생각에는 SLF4J가 두 패턴을 모두 활용하는 것 같다
Facade 패턴
- Logger 인터페이스를 통해 로깅 시스템 추상화
- 복잡한 로깅 구현체들의 단순화된 인터페이스 제공
서비스 로케이터 패턴
- LoggerFactory가 서비스 로케이터 역할 수행
- 클래스패스에서 StaticLoggerBinder 검색
- 적절한 로깅 구현체를 찾아 연결