819 words
4 minutes
[spring]mdc(mapped diagnostic context)
2025-08-09
2026-01-09

overview#

1. mapped diagnostic context (mdc)#


concept

  • mdc : mapped diagnostic context. 현재 실행중인 컨텍스트에 대한 진단정보를 저장하는 key-value map interface. 스레드 단위로 컨텍스트 데이터를 읽고 쓸 수 있음. 기본적으로 key-value 구조로 저장되며, thread-local 변수를 활용하여 각 스레드별로 독립적인 데이터 영역을 가짐. 기본적으로 각 요청마다 고유한 식별자(요청 id, 사용자 id, 세션 id등을 저장하여 로그 메시지에 포함시킬 수 있음. 이를 통해 로그 메시지를 필터링하거나 추적하는 데 유용.
  • 분산 트레이싱 : 마이크로서비스 아키텍처에서 하나의 요청이 여러 서비스를 거쳐 처리될 때, 전체 요청 경로를 추적하고 모니터링하는 기술
  • trace : 하나의 요청에 대한 전체 실행 경로
  • span : trace 내의 개별 작업단위 . 각 서비스나 메서드 호출마다 생성 부모-자식 관계로 구성
  • context propagation : 컨텍스트 전파. http header, message queue 등을 통해 trace id와 span id를 다음 서비스로 전달
  • thread-local : 각 스레드가 독립적인 저장 공간을 갖는 메모리 영역. mdc에서 thread-local을 사용하는 이유는 멀티스레드 환경에서 각 스레드가 독립적인 mdc context를 가질 필요가 있기 때문

02. pattern#

기본 템플릿#

public void processuserrequest(string userid, string requestid) {
try {
// mdc 설정
mdc.put("userid", userid);
mdc.put("requestid", requestid);
log.info("요청 처리 시작"); // [user123][req-456] 요청 처리 시작
// 비즈니스 로직
userservice.validateuser(userid);
orderservice.createorder(userid);
log.info("요청 처리 완료"); // [user123][req-456] 요청 처리 완료
} finally {
// 반드시 정리 (메모리 누수 방지)
mdc.clear();
}
}

filter 활용 웹 요청 처리#

filter는 서블릿 레벨에서 동작

@component
public class mdcfilter implements filter {
@override
public void dofilter(servletrequest request, servletresponse response,
filterchain chain) throws ioexception, servletexception {
httpservletrequest httprequest = (httpservletrequest) request;
try {
// 요청 id 생성 또는 헤더에서 추출
string requestid = extractorgeneraterequestid(httprequest);
string useragent = httprequest.getheader("user-agent");
string clientip = getclientipaddress(httprequest);
// mdc 설정
mdc.put("requestid", requestid);
mdc.put("useragent", useragent);
mdc.put("clientip", clientip);
// 다음 필터 체인 실행
chain.dofilter(request, response);
} finally {
// 요청 처리 완료 후 정리
mdc.clear();
}
}
private string extractorgeneraterequestid(httpservletrequest request) {
string requestid = request.getheader("x-request-id");
return requestid != null ? requestid : uuid.randomuuid().tostring();
}
}

intercepter#

intercepter는 스프링 mvc레벨에서 동작

@component
public class usercontextinterceptor implements handlerinterceptor {
@override
public boolean prehandle(httpservletrequest request,
httpservletresponse response,
object handler) throws exception {
// 인증된 사용자 정보 추출
authentication auth = securitycontextholder.getcontext().getauthentication();
if (auth != null && auth.isauthenticated()) {
string username = auth.getname();
string role = auth.getauthorities().tostring();
// mdc에 사용자 정보 추가
mdc.put("username", username);
mdc.put("userrole", role);
}
return true;
}
@override
public void aftercompletion(httpservletrequest request,
httpservletresponse response,
object handler, exception ex) throws exception {
// 사용자 정보만 제거 (requestid는 필터에서 관리)
mdc.remove("username");
mdc.remove("userrole");
}
}

비동기 환경에서 컨텍스트 전파#

  • 현재 스레드에서 mdc.getcopyofcontextmap()으로 컨텍스트를 복사
  • 새로운 스레드에서 mdc.setcontextmap(contextmap)으로 설정.
  • 작업 완료 후에는 반드시 mdc.clear()로 정리
@service
public class asyncorderservice {
@async
public completablefuture<orderresult> processorderasync(string orderid) {
// 현재 스레드의 mdc 컨텍스트 복사
map<string, string> contextmap = mdc.getcopyofcontextmap();
return completablefuture.supplyasync(() -> {
try {
// 새로운 스레드에 mdc 컨텍스트 설정
mdc.setcontextmap(contextmap);
mdc.put("orderid", orderid);
log.info("비동기 주문 처리 시작");
// 실제 처리 로직
return orderprocessor.process(orderid);
} finally {
mdc.clear();
}
});
}
}

reference#

[spring]mdc(mapped diagnostic context)
https://yjinheon.netlify.app/posts/03be/00frameworks/spring/spring-mdc/
Author
Datamind
Published at
2025-08-09
License
CC BY-NC-SA 4.0