Backend/SpringBoot

Json 형식으로 로깅 하기 + MDC

Dean83 2025. 10. 29. 14:29

로깅을 json 형식으로 하여, 분석을 보다 용이하게 할 수 있다. 토스에서도 이런 방식으로 사용한다고 한다. 
기본적인 로깅 설정은 https://dean83.tistory.com/315 여기에 명시해 두었다. 

 

라이브러리를 추가 하여 이용하는것이 일반적이다. gradle 에 다음을 추가 한다.

implementation 'net.logstash.logback:logstash-logback-encoder:7.4'



일단, logback 설정 파일의 예를들면 다음과 같다. 

  • 개발환경에서는 json 형식으로 콘솔에만 출력
  • 파일은 7일 후 삭제, 분 단위로 파일로 저장
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">

    <!-- 현재 활성 프로필 불러오기 -->
    <springProperty scope="context" name="springProfile" source="spring.profiles.active" />

    <!-- 로그 경로 / 파일명 설정 -->
    <property name="LOG_PATH" value="logs" />
    <property name="LOG_FILE" value="${LOG_PATH}/app-${springProfile}.log" />

    <!-- ========================================================= -->
    <!-- 🔹 개발환경(dev): JSON 형식 콘솔 출력 -->
    <!-- ========================================================= -->
    <springProfile name="dev">

        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp timeZone="Asia/Seoul" />
                    <logLevel />
                    <threadName />
                    <loggerName />
                    <message />
                    <mdc />
                    <arguments />
                    <stackTrace />
                    <jsonProvider class="net.logstash.logback.composite.GlobalCustomFieldsJsonProvider">
                        <customFields>{"env":"dev","service":"my-service"}</customFields>
                    </jsonProvider>
                </providers>
            </encoder>
        </appender>

        <!-- 개발 환경에서는 DEBUG 로그까지 출력 -->
        <root level="DEBUG">
            <appender-ref ref="CONSOLE" />
        </root>

    </springProfile>

    <!-- ========================================================= -->
    <!-- 🔹 운영환경(prod): JSON 형식 파일 저장 -->
    <!-- ========================================================= -->
    <springProfile name="prod">

        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}</file>

            <!-- 1분 단위 로그 롤링 -->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd_HH-mm}.log</fileNamePattern>
                <maxHistory>7</maxHistory> <!-- 7일간 보존 -->
            </rollingPolicy>

            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp timeZone="Asia/Seoul" />
                    <logLevel />
                    <threadName />
                    <loggerName />
                    <message />
                    <mdc />
                    <arguments />
                    <stackTrace />
                    <jsonProvider class="net.logstash.logback.composite.GlobalCustomFieldsJsonProvider">
                        <customFields>{"env":"prod","service":"my-service"}</customFields>
                    </jsonProvider>
                </providers>
            </encoder>
        </appender>

        <!-- 운영 환경에서는 INFO 이상만 기록 -->
        <root level="INFO">
            <appender-ref ref="FILE" />
        </root>

    </springProfile>

</configuration>

 

사용할때는 그냥 동일하게 log를 통해 작성하면 된다. 

 

만일 내가 원하는 필드를 추가하고, 해당 필드에 값을 채워 넣고 싶을 경우, MDC를 이용하게 된다.

(이를 위해 위의 설정에서 <mdc/> 가 들어가 있다)

 

MDC를 이용하면, 컨트롤러 -> 서비스 -> 리파지토리 간 데이터를 공유 메모리를 통해 공유할 수 있다. 따라서 TraceID 같은것을 공유하여 로깅을 할 수 있다. 

 

사용 예를 보면, 

MDC.put("userId", "U12345");
MDC.put("traceId", UUID.randomUUID().toString());
log.info("User login success");
MDC.clear();

이런식으로 사용할 수 있다. 그러나 매번 이렇게 로깅하는것은 코드 복잡성도 증가 시키고 좋지가 않기 때문에 보통은 Filter와 인터셉터를 통해 작업을 하게 된다. 

 

Filter와 인터셉터는 Controller 에 요청이 닿기전에 들어오므로, 하나의 요청(Trace) 에 일관되게 적용을 할 수 있다.

@Component
public class LoggingMdcFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
                                    throws ServletException, IOException {
        try {
            MDC.put("traceId", UUID.randomUUID().toString());
            MDC.put("method", request.getMethod());
            MDC.put("uri", request.getRequestURI());
            // 인증된 사용자 정보가 있다면 userId 추가
            // MDC.put("userId", userService.getCurrentUserId());

            filterChain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

 

** 만일, MDC를 이용하나 json 형식이 아닌 포멧 형식으로 로깅을 한다면, MDC 항목을 로깅하기 위해 %X{이름} 포멧을 사용하게 된다. 이름은 MDC.put 에서 추가하는 키값과 일치시키는게 좋다.