상세 컨텐츠

본문 제목

MDC를 이용해 로그 쉽게 추적하기 : log4j2 보안이슈 및 트랜잭션 아이디 표시

Programming/Java

by 홍잭슨 2021. 12. 15. 12:56

본문

자바의 멀티쓰레드 환경에서 다수의 요청이 들어왔을 때 로그를 쉽게 파악하기는 쉽지 않습니다. 이러한 문제를 해결하고자 Log4j에서 ThreadLocal 기술을 활용한 MDC(Mapping Diagnostic Context)을 제공하고 있습니다.

! 현재 Log4j의 보안이슈로 인해 Log4j의 버젼은 2.15.0 버젼 이상을 추천합니다.

Log4j 를 업데이트를 해야 할까?

Spring 공식 홈페이지에서 발표된 공식 문서에 의하면

Spring Boot users are only affected by this vulnerability if they have switched the default logging system to Log4J2. The log4j-to-slf4j and log4j-api jars that we include in spring-boot-starter-logging cannot be exploited on their own. Only applications using log4j-core and including user input in log messages are vulnerable.

출저: https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot

Spring Boot Starter 팩을 통해 Spring을 Initializing 할 때 제공 되는 log4j 디펜던시(log4j-to-slf4j, log4j-api)의 경우 보안이슈에 해당되지 않습니다

실제로 스프링부트를 통해 받아진 jar를 확인해보시려면

  1. 인텔리제이 IDE에서 Project → External Libraries 에서 log4j 를 검색
  2. spring-boot-starter-logging-{version}.pom 을 확인

주의할 점은 보안 이슈가 없는 2.15.0버젼 이상의 Log4j-core는 자바 8 이상의 환경에서만 동작합니다.

  • 자바 8 미만의 버젼에서 보안 이슈를 해결하기 위해서는..

MDC사용을 위한 환경 설정

MDC를 사용하기 위해선 log4j 디펜던시가 필요합니다.

Spring Boot Starter Logging 디펜던시를 추가해줍시다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

그럼 External Libraries에 이쁘게 추가가 됩니다.

Mapping 값을 위한 Key값 관리해주기

저희가 MDC Map에 저장할 Value는 하나의 요청일 들어왔을 때 어플리케이션에서 응답값를 리턴할 때까지 Thread Local의 저장되어 Log에 표시될 것입니다. 코드단에서 관리를 편하게 해주기 위해 Key값은 Enum을 통해 관리하려고 합니다.

public enum MDCKey {
    TRX_ID("trxId");

    @Getter
    private String key;

    MDCKey(String key) {
        this.key = key;
    }
}

요청과 응답 전체에 물고 있는 값이기 때문에 Transaction의 의미와 잘 맞는다고 생각하여 이름을 "trxId"로 지었습니다.

MDC Map에 트랜젝션 아이디 넣어주기 (Filter 활용)

Thread Local에 저장하는 값은 Filter를 사용해서 넣어주려고 합니다. Interceptor가 아닌 Filter에서 넣어주는 이유는 대부분의 상용 어플리케이션은 요청값의 로깅을 Filter에서 부터 어디서 요청이 들어왔는지 확인하기 때문에 일괄적인 로깅을 위해 Filter에서부터 넣어주도록 하였습니다.

@Slf4j
public class MDCFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        final String trxId = UUID.randomUUID().toString().substring(0,8);
        MDC.put(MDCKey.TRX_ID.getKey(), trxId);

        filterChain.doFilter(servletRequest, servletResponse);

        MDC.remove(MDCKey.TRX_ID.getKey());
    }
}

여기서 주의하실 점은 꼭 MDC.remove 를 통해 저장된 Value를 삭제해주셔야 합니다.
왜냐면 자바는 Thread를 재활용하기 때문에 엉뚱한 곳에 트랜잭션 아이디가 로그로 찍힐 수 있기 때문이죠!

Logback에서 출력될 양식 정해주기

자 이제 저장된 Value를 로그에 출력해주는 일만 남았습니다!

resources 디렉토리 밑에 logback.xml을 생성해줍니다.

<configuration scan="true" scanPeriod="30 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>[%d{HH:mm:ss.SSS}][%-5level][%logger{36}.%method:line%line][%X{trxId}] - %msg%n</pattern>
        </encoder>
    </appender>
    <root name="org.springframework" level="info" additivity="false">
        <appender-ref ref="STDOUT" />
    </root>
    <logger name="org.springframework.web.filter" level="debug">
    </logger>
</configuration>

Logback.xml 에서 사용되는 여러가지 Syntax들이 있으나 이 부분은 다음에 설명하고

지금은 %X{trxId} 추가를 통해 MDC의 저장된 key값을 Log 형식에 넣어줍니다.

출력해보기

이렇게 설정이 완료된 어플리케이션을 구동한 뒤 스웨거 UI나 Test tool을 이용해 요청을 보내주면!

정상적으로 출력된 8자리 trxId를 확인할 수 있습니다!

TO-BE

다음 파트에서는 MDC를 이용하여 좀 더 편하게

Log 확인을 할 수 있는 활용법에 대해 알아보겠습니다 😊 -> 다음글

'Programming > Java' 카테고리의 다른 글

🚀 NEXTSTEP - TDD, 클린 코드 with Java 회고  (1) 2022.11.24

관련글 더보기