Spring MDC Log Filter

INSANEZINDOL·2024년 2월 14일

seminar

목록 보기
2/9

MDC(Mapped Diagnostic Context) 란?

  • MDC(Mapped Diagnostic Context)는 현재 실행중인 쓰레드에 메타 정보를 넣고 관리하는 공간이다. MDC는 내부적으로 Map을 관리하고 있어 (Key, Value) 형태로 값을 저장할 수 있다. 메티 정보를 쓰레드 별로 관리하기 위해 내부적으로는 쓰레드 로컬을 사용하고 있다.
  • 스프링 애플리케이션에서는 요청(Request)이 오면 MDC에 uuid를 넣고 로그에 이를 포함시키면 된다. 그리고 uuid로 로그를 찾으면 1개의 요청에 대한 로그들을 살펴볼 수 있다.

MDC 필터 구현

  • X-API-ID 이름의 Key로 Random한 UUID를 만들어서 MDC와 Header에 각각 넣어준다.
package com.example.micrometerboot.config;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

@Component
@Slf4j
public class MDCLoggingFilter implements Filter {

    final static String _HEADER_NAME = "x-api-id";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // get header value in request
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        Map<String, String> headers = Collections.list(httpRequest.getHeaderNames())
                .stream()
                .collect(Collectors.toMap(h -> h, httpRequest::getHeader));
        String apiId = headers.get(_HEADER_NAME);
        if (apiId == null || apiId.isEmpty()) {
            apiId = UUID.randomUUID().toString();
        }

        // add mdc
        MDC.put(_HEADER_NAME, apiId);
        log.trace("Request IP address is {}", servletRequest.getRemoteAddr());
        log.trace("Request content type is {}", servletRequest.getContentType());

        // set header value in response
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpServletResponse);
        filterChain.doFilter(servletRequest, responseWrapper);
        responseWrapper.setHeader(_HEADER_NAME, apiId);
        responseWrapper.copyBodyToResponse();
        log.trace("Response header is set with uuid {}", responseWrapper.getHeader(_HEADER_NAME));
    }

}

Logging Pattern 수정

  • logback-spring.xml 파일 수정한다.
  • pattern 항목에 [%X{X-API-ID}] 형식으로 넣어준다.
    <!-- FILE Appender -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH_NAME}</file>
        <!-- 일자별로 로그파일 적용하기 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH_NAME}.%d{yyyyMMdd}</fileNamePattern>
            <maxHistory>60</maxHistory> <!-- 일자별 백업파일의 보관기간 -->
        </rollingPolicy>
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5p] [cellookadmin] [%X{x-api-id}] [%F:%line] [%M] : %m%n</pattern>
        </encoder>
    </appender>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">      
       <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="ch.qos.logback.classic.PatternLayout">
        <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5p] [cellookadmin] [%X{X-API-ID}] [%F:%line] [%M] : %m%n</pattern>
      </layout>
    </encoder>

API 호출

  • API 호출 후 Response Header에 X-API-ID 라는 항목의 값이 있다.

로그 확인

  • Response Header에 값으로 로그를 grep 한다.
  • 해당 Request에 대한 로그만 추출해서 확인 할 수 있다.

왜 써야하나?

  • FE 개발자와 소통

    • AS-IS
      • 어떤 API로 호출하셨나요?
      • Parameter는 뭔가요?
      • Request랑 Response 보내주세요.
      • 지금 로그띄웠으니까 다시 한번 호출해주세요.
    • TO-BE
      • Response Header에 X-API-ID 값 보내주세요.
  • 수많은 로그 중 내가 호출한 Request의 로그만 찾아서 확인하기 쉽다.

    • 기존에는 호출한 시점에 로그를 찾아서 Thread 번호를 확인하고 grep 해야만 했다.
    • curl이나 Postman으로 호출한 결과로 X-API-ID을 알 수 있고, grep 할 수 있다.
profile
Backend Engineer

0개의 댓글