본문 바로가기
프로젝트

Spring Boot에서 요청 로그에 userId와 requestId 추가하기 (MDC + Logback)

by jucheol 2025. 8. 7.

운영 환경에서 여러 유저의 요청이 동시에 들어올 때, 특정 요청을 추적하기 위해서는 단순한 로그만으로는 부족한 경우가 많다.  
특히 문제 발생 시 어떤 사용자의 요청인지, 어떤 흐름으로 API가 호출됐는지를 파악하려면 `userId`, `requestId` 등 식별 가능한 정보가 로그에 포함되어야 한다.

이 글에서는 Spring Boot 환경에서 MDC(Mapped Diagnostic Context)를 활용하여 모든 요청/응답 로그에 `userId`와 `requestId`를 출력하는 방법을 정리했다.

 

개발 목표

- 요청/응답 로그에 userId, requestId를 자동으로 출력
- Logback 설정을 통해 로그 포맷 지정
- Spring 인터셉터로 MDC에 값 주입
- 응답 완료 후 MDC 클리어

 

구현 방법 정리

1. 의존성 추가

// logback
implementation 'org.springframework.boot:spring-boot-starter-logging'

2. LogInterceptor 구현

 

  • 이 클래스는 스프링의 HandlerInterceptor를 구현하며, 모든 HTTP 요청 전후에 특정 작업을 실행할 수 있게 해줍니다.
  • @Component로 선언되어 있어, 별도 등록 없이 스프링 빈으로 자동 등록됩니다. (단, WebMvcConfigurer에서 명시적으로 등록 필요)

상수 정의

private static final String REQUEST_ID = "requestId";
private static final String USER_ID = "userId";

 

preHandle() – 요청 전 처리

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    String requestId = UUID.randomUUID().toString();
    MDC.put(REQUEST_ID, requestId);

    String userId = extractUserIdFromRequest(request);
    MDC.put(USER_ID, userId != null ? userId : "anonymous");

    return true;
}

동작 설명:

requestId 생성 및 저장

요청마다 고유한 UUID를 생성하여 MDC에 저장합니다.

이렇게 하면 로그백 설정에서 %X{requestId}로 이 값을 로그에 자동으로 삽입할 수 있습니다.

 

userId 추출

유저 ID는 현재는 HTTP 요청 헤더에서 X-USER-ID로 가져오도록 되어 있습니다.

추후 JWT나 Spring Security의 SecurityContextHolder에서 꺼내는 방식으로 확장 가능

 

MDC에 값 저장

MDC.put()을 통해 현재 쓰레드에 해당 값을 저장하면, 로그백 패턴을 통해 자동 출력 가능

afterCompletion() – 요청 후 처리

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    MDC.clear();
}

 

  • 요청 처리가 완료되면 MDC.clear()를 호출해줍니다.
  • MDC는 ThreadLocal 기반이기 때문에, 값을 지우지 않으면 다른 요청 쓰레드에 값이 누적되어 메모리 누수나 잘못된 로그 출력이 발생할 수 있습니다.

 

extractUserIdFromRequest()

private String extractUserIdFromRequest(HttpServletRequest request) {
    return request.getHeader("X-USER-ID");
}
  • 요청 헤더에서 유저 ID를 추출하는 방식입니다.

3. 인터셉터 등록

 

  • @Configuration 어노테이션은 이 클래스가 스프링 설정 클래스임을 나타냅니다.
  • WebMvcConfigurer는 스프링 MVC 설정을 커스터마이징할 수 있는 인터페이스입니다.

생성자 주입

private final LoggingInterceptor loggingInterceptor;

public WebMvcConfig(LoggingInterceptor loggingInterceptor) {
    this.loggingInterceptor = loggingInterceptor;
}

 

 

  • LoggingInterceptor를 생성자 주입 방식으로 주입받습니다.
  • 이 인터셉터는 요청 전/후에 userId, requestId를 MDC에 주입하고 정리하는 역할을 합니다.
  • 해당 인터셉터는 @Component로 등록되어 있기 때문에, Spring이 자동으로 주입해줄 수 있습니다.

인터셉터 등록

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loggingInterceptor);
}

 

  • 이 메서드는 Spring MVC의 요청 흐름에 인터셉터를 추가하는 역할을 합니다.
  • addInterceptor(loggingInterceptor)를 호출함으로써 모든 HTTP 요청에 대해 LoggingInterceptor를 적용하게 됩니다.

패턴 제한이 필요한 경우

registry.addInterceptor(loggingInterceptor)
        .addPathPatterns("/api/**")
        .excludePathPatterns("/swagger/**", "/health");

 

  • 위와 같은 방식으로 경로에 다라 인셉터 적용을 제한할 수 있다.

 

4. logback-spring.xml

공통 설정

<property name="LOG_PATH" value="logs" />
    <property name="LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg [userId:%X{userId:-anonymous}] [requestId:%X{requestId}]%n"/>

 

  • LOG_PATH: 로그 파일이 저장될 디렉터리 (logs/).
  • LOG_PATTERN: 로그 출력 형식.
    • %d: 날짜
    • %thread: 쓰레드 이름
    • %level: 로그 레벨 (INFO, ERROR 등)
    • %logger{36}: 로거 이름
    • %msg: 로그 메시지
    • %X{userId}, %X{requestId}: MDC에서 설정한 값
    • %replace(...): MDC 값이 있을 때만 출력되도록 정규식으로 감싸서 표시

 

콘솔 로그 출력

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>${LOG_PATTERN}</pattern>
    </encoder>
</appender>
  • 콘솔(터미널)로 로그를 출력하는 설정입니다.

파일 로그 설정

INFO 로그 전용 파일

<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_PATH}/app-info.log</file>
    ...
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>INFO</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
</appender>

 

 

  • INFO 레벨 로그만 기록합니다.
  • 매일 로그 파일을 새로 생성하며(app-info.2025-08-07.log 형식), 30일간 보관합니다(maxHistory 설정)
  • LevelFilter: INFO만 허용하고 다른 레벨은 차단합니다.

ERROR 로그 전용 파일.

<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_PATH}/app-error.log</file>
    ...
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>ERROR</level>
    </filter>
</appender>

 

  • ERROR 레벨 이상의 로그만 기록합니다.
  • 예외나 시스템 문제 발생 시 로그로 분리해서 확인할 수 있어 운영에서 매우 중요합니다.

 

4. 루트 로거 설정

<root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="INFO_FILE" />
    <appender-ref ref="ERROR_FILE" />
</root>

 

  • 루트 로거의 최소 로그 레벨을 INFO로 설정합니다.
  • 모든 로그는 다음 세 곳에 동시에 기록됩니다:
    • 콘솔
    • INFO_FILE (INFO만)
    • ERROR_FILE (ERROR만)

실행 결과

Spring Starter 로그

 

Get 요청 로그

주의할 점

- MDC는 ThreadLocal 기반이라 비동기 처리나 쓰레드 풀에서는 별도 설정이 필요함 (예: @Async, CompletableFuture)
- `userId` 추출 로직은 서비스 환경에 맞게 구현 필요 (JWT, 세션, 헤더 등)
- `MDC.clear()`는 꼭 해줘야 메모리 누수 방지

 

https://github.com/TeamQuestday/questDayBackend

 

GitHub - TeamQuestday/questDayBackend

Contribute to TeamQuestday/questDayBackend development by creating an account on GitHub.

github.com

'프로젝트' 카테고리의 다른 글

JaCoCo + Codecov로 CI 테스트 커버리지 관리하기  (4) 2025.08.25
로깅(Logging)과 Logback 정리  (3) 2025.08.09
EC2에서 RDS 접속하기  (0) 2025.05.15
aws 배포 - EC2, RDS, ElastiCache  (0) 2025.03.14
aws-EC2 public ip 설정  (0) 2025.03.12