본문 바로가기

학습 기록 (Learning Logs)/Today I Learned

MDC

import org.slf4j.MDC;

쓰레드 로그를 남기는데 있어서 해결방법이라고 들었다.

 

좀 조사를 해보려고한다.

 

 

MDC

MDC는 애플리케이션에서 요청별 또는 트랜잭션별로 로그를 관리하고 디버깅을 용이하게 하는 데 매우 유용합니다.

 특히, 로그 추적성을 강화하고 로깅 출력에 추가 정보를 포함해야 하는 경우 활용하면 효과적입니다. 

그러나 멀티스레드나 분산 환경에서는 MDC의 한계를 보완할 추가적인 설계가 필요합니다.

 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

@Override
	public HttpAAResponse doService(HttpAARequest request)
	{
		String sessionId = UUID.randomUUID().toString();

		MDC.put("session_id", sessionId);
		MDC.put("client_ip", getClientIp(request));

		long time = System.currentTimeMillis();

		logger.info("req : {}", request.getRequest().getUri());

		HttpAAResponse response = new HttpAAResponse();

		process(request, response);

		long elapsed = System.currentTimeMillis() - time;

		logger.info("res : responseCode={}, uri={}, elapsed={}", response.getResponseStatus().code(), request.getRequest().getUri(), elapsed);

		MDC.remove("client_ip");
		MDC.remove("session_id");

		return response;
	}

 

이렇게 MDC을 적용해도, 멀티쓰레드 환경, 분산 환경은 따로 구현을 해야한다.

 

 

멀티 쓰레드

taskdecorator 같은거 추가 구현

쓰레드가 바뀔때 이전 쓰레드의 context 값을 전달

 

 

 

분산환경

다른서비스에서는 해당 정보들을 모르니 

webclient 나 restemplate 같은거 쓸때 

필요정보들을 전달할 수 있는 방법을 생각

 

WebClient 사용 시 MDC Context 전파

WebClient 요청 시 MDC 값을 HTTP 헤더로 전파하려면 ExchangeFilterFunction을 사용

 

 

  • 보내는 측:
    • WebClient의 ExchangeFilterFunction을 사용해 요청 헤더에 X-Thread-ID를 추가.
    • 헤더를 통해 threadId를 다른 서비스로 전송.
  • 받는 측:
    • 컨트롤러에서 @RequestHeader로 헤더 값을 읽어 MDC에 저장.
    • 필요하면 해당 threadId를 로깅이나 추가 요청에 활용.

 

 

설정:  헤더에 필터 추가

import org.slf4j.MDC;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

public class WebClientConfig {

// 추가
 	public static WebClient createWebClient() {
        return WebClient.builder()
            .filter(addThreadIdHeaderFilter()) // 헤더에 threadId 추가
            .build();
    }
    
	private static ExchangeFilterFunction addThreadIdHeaderFilter() {
        return (request, next) -> {
            // MDC에서 threadId 가져오기
            String threadId = MDC.get("threadId");
            // 요청 헤더에 threadId 추가
            return next.exchange(
                ClientRequest.from(request)
                    .header("X-Thread-ID", threadId != null ? threadId : "default-thread-id")
                    .build()
            );
        };
    }
}

 

전송

WebClient webClient = WebClientConfig.createWebClient();

public void sendDataToAnotherService() {
    String dataToSend = "{ \"name\": \"example\", \"value\": 123 }";

    webClient.post()
        .uri("http://another-service/api/resource")
        .header("Custom-Header", "CustomValue") // 추가적으로 다른 헤더도 설정 가능
        .bodyValue(dataToSend)
        .retrieve()
        .bodyToMono(String.class)
        .doOnSuccess(response -> {
            System.out.println("Response received: " + response);
        })
        .doOnError(error -> {
            System.err.println("Error occurred: " + error.getMessage());
        })
        .subscribe();
}

public void fetchDataFromAnotherService() {
    webClient.get()
        .uri("http://another-service/api/resource")
        .retrieve()
        .bodyToMono(String.class)
        .doOnSuccess(response -> {
            System.out.println("Response: " + response);
        })
        .doOnError(error -> {
            System.err.println("Error: " + error.getMessage());
        })
        .subscribe();
}

 

 

수신 사용

WebClient webClient = WebClientConfig.createWebClient();

webClient.get()
    .uri("http://other-service/api/resource")
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(response -> System.out.println("Response: " + response));
    
    
    
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ExampleController {

    @GetMapping("/api/resource")
    public String handleRequest(@RequestHeader(value = "X-Thread-ID", required = false) String threadId) {
        if (threadId != null) {
            MDC.put("threadId", threadId); // MDC에 threadId 저장
        }

        // 이후 처리
        return "Received Thread ID: " + threadId;
    }
}

 

RestTemplate 사용 시 MDC Context 전파

Custom Interceptor

import org.slf4j.MDC;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

public class MdcRestTemplateInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        String threadId = MDC.get("threadId"); // MDC에서 threadId 가져오기
        if (threadId != null) {
            request.getHeaders().add("X-Thread-ID", threadId);
        }
        return execution.execute(request, body);
    }
}

RestTemplate에 Interceptor 등록

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.singletonList(new MdcRestTemplateInterceptor())); // Interceptor 등록
        return restTemplate;
    }
}

사용

RestTemplate restTemplate = new RestTemplate();

String response = restTemplate.getForObject("http://other-service/api/resource", String.class);
System.out.println("Response: " + response);

 

다른 서비스에서 Context 값 활용

다른 서비스는 HTTP 요청에서 X-Thread-ID 값을 읽어 해당 컨텍스트를 복원합니다.

 

Controller에서 헤더 값 읽기

import org.slf4j.MDC;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ExampleController {

    @GetMapping("/api/resource")
    public String handleRequest(@RequestHeader("X-Thread-ID") String threadId) {
        // MDC에 Thread ID 설정
        MDC.put("threadId", threadId);
        // 이후 로직에서 MDC 값을 활용
        return "Received Thread ID: " + threadId;
    }
}

 

End-to-End Trace (추적 ID 관리)

분산 환경에서 Spring Cloud Sleuth를 활용하면, 추적 ID(Trace ID)와 스팬 ID(Span ID)를 자동으로 서비스 간 전파할 수 있습니다.

Spring Cloud Sleuth 설정

application.yml

spring:
  sleuth:
    sampler:
      probability: 1.0
    log:
      pattern:
        level: "%clr{%5p} [%X{traceId},%X{spanId}]"

 

자동 헤더 전파

Spring Cloud Sleuth를 설정하면 X-B3-TraceId와 같은 추적 헤더가 자동으로 전파되므로, 별도의 MDC 설정 없이 분산 추적이 가능합니다.

 

쓰레드 추적

MDC 에서의 ThreadLocal

https://velog.io/@bonjugi/MDC-%EC%9D%98-%EB%8F%99%EC%9E%91%EB%B0%A9%EC%8B%9D-ThreadLocal

'학습 기록 (Learning Logs) > Today I Learned' 카테고리의 다른 글

난생 처음 인공지능 입문  (0) 2025.02.22
nestJs  (0) 2025.01.26
RabbitMQ, Kafka  (0) 2024.12.02
Map 함수  (0) 2024.08.14
Redis, Spring  (0) 2024.08.07