728x90
반응형
Java MDC (Mapped Diagnostic Context) 란?
MDC (Mapped Diagnostic Context) 는 Java 의 로깅 프레임워크 (Slft4j, Logback, Log4j) 에서 제공하는 기능으로, Thread-local 컨텍스트에 데이터를 저장하고 로깅할 때 이를 로그 메시지에 포함할 수 있도록 하는 역할이다.
주로 로그의 컨텍스트 정보를 추가하고, 이를 통해 로그 메시지를 더 유용하고 분서하기 쉽게 만들기 위해 사용된다.
MDC 의 특징
- Thread-local Storage
- MDC 는 내부 구현적으로 ThreadLocal 을 사용한다.
- 즉, 데이터를 현재 쓰레드에만 저장하고, 다른 쓰레드에 영향을 미치지 않는다.
- 따라서, 멀티 쓰레드 환경에서 안전하게 사용할 수 있다.
- 자동화된 로그 컨텍스트
- 로그에 일관된 정보를 자동으로 포함할 수 있어, 디버깅과 분석이 쉬워진다.
- 의존성
- Slf4j, Logback , log4j 와 같은 로깅 프레임쿼와 같이 사용하며 , 내부에 구현되어있다.
MDC 내부 구현 파헤치기
public class MDC {
static MDCAdapter mdcAdapter;
/**
* An adapter to remove the key when done.
*/
public static class MDCCloseable implements Closeable {
private final String key;
private MDCCloseable(String key) {
this.key = key;
}
public void close() {
MDC.remove(this.key);
}
}
private MDC() {
}
static {
try {
mdcAdapter = bwCompatibleGetMDCAdapterFromBinder();
} catch (NoClassDefFoundError ncde) {
mdcAdapter = new NOPMDCAdapter();
String msg = ncde.getMessage();
if (msg != null && msg.contains("StaticMDCBinder")) {
Util.report("Failed to load class \\"org.slf4j.impl.StaticMDCBinder\\".");
Util.report("Defaulting to no-operation MDCAdapter implementation.");
Util.report("See " + NO_STATIC_MDC_BINDER_URL + " for further details.");
} else {
throw ncde;
}
} catch (Exception e) {
// we should never get here
Util.report("MDC binding unsuccessful.", e);
}
}
/**
* As of SLF4J version 1.7.14, StaticMDCBinder classes shipping in various bindings
* come with a getSingleton() method. Previously only a public field called SINGLETON
* was available.
*
* @return MDCAdapter
* @throws NoClassDefFoundError in case no binding is available
* @since 1.7.14
*/
private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError {
try {
return StaticMDCBinder.getSingleton().getMDCA();
} catch (NoSuchMethodError nsme) {
// binding is probably a version of SLF4J older than 1.7.14
return StaticMDCBinder.SINGLETON.getMDCA();
}
}
...
}
- MDC 클래스의 경우 static 블럭을 통해 MDCAdapter 를 초기화 시킨다.
public class StaticMDCBinder {
/**
* The unique instance of this class.
*/
public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();
private StaticMDCBinder() {
}
/**
* Currently this method always returns an instance of
* {@link StaticMDCBinder}.
*/
public MDCAdapter getMDCA() {
return new LogbackMDCAdapter();
}
public String getMDCAdapterClassStr() {
return LogbackMDCAdapter.class.getName();
}
}
- StaticMDCBinder 클래스의 경우 singletone 으로 관리도고 있으며, MDCAdapter 인터페이스의 구현체인LogbackMDCAdapter 클래스를 반환한다.
public class LogbackMDCAdapter implements MDCAdapter {
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
final ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>();
public void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
Map<String, String> oldMap = copyOnThreadLocal.get();
Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);
} else {
oldMap.put(key, val);
}
}
- LogbackMDCAdapter 의 경우 final 인자로 내부에 ThreadLocal 을 두개 가지고 있다.
- copyOnThreadLocal 의 경우 현재 스레드의 MDC 데이터를 저장하기 위해 사용된다.
- 필요할 경우에는 기존 맵의 복사본을 만들어서 사용하는 모습을 볼 수 있다.
- lastOperation 의 경우 변경사항에 대한 기록을 하는 역할로 확인할 수 있다.
- 현재 작업 상태를 설정하고, 이전 작업 상태를 반환하는 역할로 데이터 일관성을 보장하고, 불필요한 맵 객체 복사를 방지하는 역할로 보인다.
MDC 적용
MDCFilter
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MDCFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String traceId = UUID.randomUUID().toString().replaceAll("-", "");
MDC.put("traceId", traceId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
logback.xml
<property name="LOG_PATTERN"
value="%-5level %d{yyyy-MM-dd HH:mm:ss} [%X{traceId}] [%logger{0}:%line] - %msg%n"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>1GB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>3</maxHistory>
</rollingPolicy>
</appender>
<root level="${LOG_LEVEL}">
<appender-ref ref="FILE"/>
</root>
INFO 2025-01-21 17:09:03 [6a064952adaf47629f4fb57e3e9b8771] [LoggingFilter:71]
...
- 동시에 여러 요청이 들어올 경우 로그를 파악하는데 힘이 든다.
- 최근 관심있는 OpneSource 에서도 MDC 를 사용하는 모습을 보고 현재 프로젝트에 찾아보고 적용하게 되었다.
728x90
반응형
'1.프로그래밍 > Java' 카테고리의 다른 글
[Spring Security] Spring Security OAuth2.0 Flow (0) | 2023.03.23 |
---|---|
[Spring Security] SessionManagement (0) | 2023.01.20 |
[Spring] Spring 비밀번호 암호화 SHA-256 ~ BCryptPasswordEncoder(MessageDigest, SHA-256, BCryptPasswordEncoder) (0) | 2022.12.07 |
[Spring] JPA 사용시 Entity Class Setter 메서드에 대한 고찰 (1) | 2022.12.01 |
[Spring] Spring MockMvc 정리 (REST API 테스트, Multipart/form-data 테스트) (0) | 2022.11.21 |