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 |