본문 바로가기

Spring/Springboot

[Spring boot] MDC(Mapped Diagnostic Context) 적용

 

 

1. MDC 사용 이유

 

1) 스레드 별 로깅 컨텍스트

다중 스레드 환경에서 각 스레드는 독립적으로 실행되며 서로 다른 작업을 수행함

MDC를 사용하면 각 스레드에게 고유한 로깅 컨텍스트를 제공하여 스레드 간 로그를 구분할 수 있음

 

2) 트랜잭션 추적

웹 애플리케이션 환경에서는 하나의 요청이 여러 스레드를 통과할 수 있음

MDC를 사용하면 요청을 추적하고 해당 요청과 관련된 로그를 모아서 볼 수 있으므로 트랜잭션 춪거 및 디버깅에 유용

 

3) 디버깅 및 로깅 수준 설정

디버깅 시 특정 스레드나 작업에 대한 로그를 쉽게 필터링 할 수 있음

특정 작업에 대한 로그를 필요한 경우에만 출력하도록 로깅 수준을 동적으로 설정할 수 있음

 

4) 추가 정보 제공

로그에 정보를 쉽게 추가할 수 있음

예를 들어 요청 ID, 세션 ID, 사용자 이름 등의 정보를 MDC에 저장하면 해당 정보가 로그에 자동으로 추가되어 디버깅과 모니터링을 용이하게 함

 

5) 로그 형식 향상

로그 메시지에 동적인 정보를 추가하면 로그의 가독성을 향상시킬 수 있음

분산시스템에서 여러 서비스 간의 트랜잭션 추적이 필요한 경우 중요한 역할을 함

 

6) 내가 선택한 이유 

기존 로깅 구현은 스레드는 확인할 수 있지만 다중 스레드 환경에서 로그가 뒤섞여 요청 별 파악이 어려웠음

요청의 흐름별로 로깅 파악하는 것이 문제를 파악하는데 중요하게 작용할 것 같아 MDC 적용

 

▼ 기존로깅 구현 

 

 

 

2. MDC 적용 시점

필터(Filter), 인터셉터(Interceptor), 컨트롤러(AOP 적용) 시점 정도를 고려해 볼 수 있는데  웹 애플리케이션의 요청이 가장 처음 만나는 부분이기 때문에 필터에 가장 앞 부분에 설정하여 요청의 시작과 끝 확인

 

 

 

3. MDC 적용

 

1) 의존성 추가

로깅 구현체로 logback을 사용하는데 logback은 SLF4j  구현체이며 Spring Boot 환경이라면 spring-boot-starter-web > spring-boot-starter-logging에 logback 구현체가 포함되어 있어 별도의 dependency 추가 없이 사용할 수 있음

 

2) 필터 구현체 만들기 

package com.example.queueproject.common;

import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.UUID;

/*
    @Component: 스프링 컨텍스트에서 클래스를 빈으로 등록, Filter를 구현한 이 클래스를 스프링 빈으로 관리
    @Order: 빈의 우선순위 설정
    Ordered.HIGHEST_PRECEDENCE: 가장 높은 우선 순위
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MDCRequestLoggingFilter implements Filter { 

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 필요에 따라 초기화 코드 작성
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        try {
            // 랜덤한 uuid 값을 생성하여 MDC 저장소에 request_id를 키, uuid를 value로 저장
            UUID uuid = UUID.randomUUID();
            MDC.put("request_id", uuid.toString());
            
            // 다음 필터로 제어 전달, 실제 요청이 로직이 실행되는 지점
            chain.doFilter(request, response);
        } finally {
            // 실제 요청이 완료되면 MDC 저장소를 초기화
            MDC.clear();
        }
    }

    @Override
    public void destroy() {
        // 필요에 따라 소멸 코드 작성
    }
}

 

 

cf) MDC 메소드

put(String key, String val) 현재 스레드의 MDC에 key-value 쌍 추가
get(String key) 현재 스레드의 MDC에 주어진 key의 value 가져오기
remove(String key) 현재 스레드의 MDC에 주어진 key의 value 삭제
clear() 현재 스레드 MDC 초기화

 

 

3) logback.xml  파일 설정 수정

  • 패턴
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative]  %clr(%-5level) %clr(${PID:-}){magenta} %clr(---){faint} %clr([%15.15thread]){faint} [%clr(%X{request_id}){faint}] %clr(%-40.40logger{36}){cyan} %clr(:){faint} %msg%n"/>

 

  • 로그 확인

실행된 스레드는 같지만 요청 id가 다르게 들어오는 것을 확인할 수 있음

 

 

 

 

 

 

에러

다른 프로젝트에 적용하려고 하니까 아래와 같이 java.servelet이 인식되지 않음

 

의존성 추가

compileOnly 'javax.servlet:javax.servlet-api:4.0.1'

https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api/4.0.1

 

의존성을 추가해서 해결

 

저렇게 적용하고 로컬에서는 잘 돌아갔는데 빌드하고 vm 서버에서 실행시키니까 에러 발생

application run failed
org.springframework.beans.factory.BeanDefinitionStoreException: I/O failure while processing configuration class [cohttp://m.example.syncproject.common.log.MDCRequestLoggingFilter]

Caused by: java.io.IOException: Failed to load class [javax.servlet.Filter]

Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter

 

-> javax.servlet.Filter 클래스를 찾지 못하고 있음

 

MDC 실습했던 프로젝트는 스프링부트 2버전 대 이고, 이걸 적용한 프로젝트는 스프링부트3버전이라

버전 문제인가 생각하면서 찾은게 기존에 javaEE를 사용했는데, 부트 3부터는 JakartaEE를 지원한다는 것

javaEE에서 제공하는 Java Servlet(javax.servlet)이 Jakarta Servlet(jakarta.servlet)으로 변경 됐다는 것을 보게됨

 

외부라이브러리 확인

부트2 프로젝트 javax 

 

부트3 프로젝트 jakarta 

 

import 부분을 javax에서 jakrata로 변경했더니 로컬환경에서도 서버에 올린 환경에서도 모두 실행 잘 되었음!