819 words
4 minutes
[spring]mdc(mapped diagnostic context)
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는 서블릿 레벨에서 동작
@componentpublic 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레벨에서 동작
@componentpublic 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()로 정리
@servicepublic 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/