운영 환경에서 여러 유저의 요청이 동시에 들어올 때, 특정 요청을 추적하기 위해서는 단순한 로그만으로는 부족한 경우가 많다.
특히 문제 발생 시 어떤 사용자의 요청인지, 어떤 흐름으로 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 |