MDC의 개념과 쓰레드와의 관계
업데이트:
| MDC와 쓰레드
1. MDC란?
Mapped Diagnostic Context의 약자로, 쓰레드 단위로 특정 데이털르 읽고/쓸 수 있는 저장소입니다. 로깅 프레임워크에서 사용하는 기능입니다. 로깅 프레임워크에서는 로그를 출력할 때, 로그의 내용을 출력하는 것 뿐만 아니라, 로그의 내용을 더 자세하게 출력하기 위해 추가적인 정보를 출력할 수 있습니다. 이때, MDC를 사용하면 로그의 내용을 더 자세하게 출력할 수 있습니다.
MDC 기본구조
public class MDC {
private static final ThreadLocal<Map<String, String>> contextMap
= new InheritableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
};
public static void put(String key, String value) {
contextMap.get().put(key, value);
}
public static String get(String key) {
return contextMap.get().get(key);
}
public static void remove(String key) {
contextMap.get().remove(key);
}
public static void clear() {
contextMap.get().clear();
}
}
2. MDC의 특징
- MDC는 실행 쓰레드들에 공통값을 주입하여 의미있는 정보를 추가해 로깅할 수 있도록 제공합니다.
- 로깅할때는 멀티 쓰레드 환경에서 실행되는 task는 로그가 섞여 제대로 확인하기 힘든데, 실행되는 쓰레드마다 고유한 값을 주입하여 실행 흐름을 트래킹할 수 있습니다.
- MDC는 THREAD_LOCAL을 사용하여 구현되어 있습니다.
3. MDC의 사용법
- MDC.put(“key”, “value”) : MDC에 key와 value를 저장합니다.
- MDC.get(“key”) : MDC에 저장된 key의 value를 가져옵니다.
- MDC.remove(“key”) : MDC에 저장된 key를 제거합니다.
- MDC.clear() : MDC에 저장된 모든 key와 value를 제거합니다.
ThreadLocal을 사용하여 구현되어 있기 때문에, 따로 ThreadLocal을 선언할 필요가 없습니다.
import java.io.IOException;
import java.util.UUID;
import org.jboss.logging.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class MDCLoggingFilter implements Filter {
private static final String REQUEST_ID_MDC_KEY = "request_id";
private static final String REQUEST_ID_HEADER = "X-Request-Id";
@Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
final HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String requestId = httpServletRequest.getHeader(REQUEST_ID_HEADER);
if (requestId == null || requestId.isEmpty()) {
requestId = UUID.randomUUID().toString();
}
try {
// MDC에 request_id 및 기타 정보를 추가
MDC.put(REQUEST_ID_MDC_KEY, requestId);
// 요청 속성에 request_id 추가
httpServletRequest.setAttribute(REQUEST_ID_MDC_KEY, requestId);
// 응답 헤더에 request_id 추가
httpServletResponse.setHeader(REQUEST_ID_HEADER, requestId);
filterChain.doFilter(servletRequest, servletResponse);
} finally {
// MDC에서 사용한 모든 키 제거
MDC.remove(REQUEST_ID_MDC_KEY);
}
}
}
ThreadLocal에 request_id라는 key 값으로 UUID를 넣어 각 요청(스레드) 별 로그를 구분할 수 있게 되었습니다.
사용된 Thread는 ThreadPool에 다시 돌아가기에 반드시 설정한 상태 값은 제거해주어야 합니다.
4. MDC에서 remove를 해주는 이유
- MDC에 저장된 key와 value는 쓰레드마다 고유한 값이기 때문에, 쓰레드가 종료되면 MDC에 저장된 key와 value를 제거해주어야 합니다. 그렇지 않으면, 다음에 실행되는 쓰레드에서 이전 쓰레드의 MDC에 저장된 key와 value를 사용할 수 있습니다.
- 쉽게 말해 MDC는 THREAD_LOCAL을 사용하여 구현되어 있기 때문에,각 요청이 끝날 때 MDC 값을 제거하지 않으면
Thread-1: Request A -> Request C -> Request E Thread-2: Request B -> Request D -> Request F
- 정보 유출: Request A의 정보가 Request C에 유출될 수 있습니다.
- 잘못된 로깅: Request C의 로그에 Request A의 정보가 포함될 수 있습니다.
- 예기치 않은 동작: Request C의 코드가 Request A의 MDC 값을 기반으로 동작할 수 있습니다
잘못된 예시
반납하지 않음
// Request A 처리
MDC.put("requestId", "A-123");
MDC.put("userId", "user1");
// ... Request A 처리 ...
// MDC.clear(); // 이 부분이 없다면?
// Request C 처리 (같은 스레드에서)
String requestId = MDC.get("requestId"); // "A-123"이 반환됨
String userId = MDC.get("userId"); // "user1"이 반환됨
// Request C는 잘못된 정보로 처리됨
올바른 예시
try {
MDC.put("requestId", generateRequestId());
MDC.put("userId", getCurrentUserId());
// 요청 처리
} finally {
MDC.clear(); // 모든 MDC 값을 제거
}
5. MDC와 ThreadLocal의 차이
MDC와 ThreadLocal은 밀접한 관계가 있지만, 완전히 같지는 않습니다.
MDC는 ThreadLocal을 기반으로 구축된 더 높은 수준의 추상화로, 로깅에 특화된 기능을 제공합니다. MDC를 사용하면 ThreadLocal의 이점을 누리면서도, 로깅에 더 특화된 편리한 기능을 활용할 수 있습니다.
1.용도:
ThreadLocal은 일반적인 목적으로 사용되는 반면, MDC는 주로 로깅을 위해 사용됩니다.
2.구현:
MDC는 ThreadLocal을 기반으로 구현되어 있지만, 추가적인 기능을 제공합니다.
3.API:
MDC는 로깅에 특화된 메서드(put, get, remove, clear 등)를 제공합니다.
4.데이터 구조:
ThreadLocal은 단일 값을 저장하지만, MDC는 key-value 쌍의 맵을 저장합니다.
참고사이트
https://lion-king.tistory.com/entry/MDCMapped-Diagnostic-Context
댓글남기기