요청 ID와 구조화 로그 구성
로그를 검색 가능한 단위로 만든다
Spring Boot 서비스의 장애를 추적할 때 단순 문자열 로그만 있으면 같은 요청에서 나온 줄을 모으기 어렵다. 먼저 요청마다 하나의 requestId를 붙이고, 로그 수집기가 필드로 읽을 수 있는 JSON 구조를 만든다. 이렇게 해 두면 운영자는 시간 범위와 요청 ID로 로그를 좁히고, 개발자는 컨트롤러부터 하위 서비스까지 같은 흐름을 따라갈 수 있다.
Spring Boot 3.4 이상에서는 구조화 로그 형식을 속성으로 켤 수 있다. 아래 예시는 콘솔 로그를 ECS JSON 형식으로 내보내고 서비스 이름을 함께 기록한다.
spring.application.name=order-api
logging.structured.format.console=ecs
logging.structured.ecs.service.name=order-api
logging.structured.ecs.service.environment=local
구조화 형식은 MDC에 들어간 키 값을 JSON 필드에 함께 실어 보낸다. 그래서 요청 필터에서 requestId를 넣고 반드시 제거하는 흐름이 중요하다.
요청 경계에서 MDC를 정리한다
@Component
class RequestIdFilter extends OncePerRequestFilter {
private static final String HEADER = "X-Request-Id";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String requestId = Optional.ofNullable(request.getHeader(HEADER))
.filter(id -> !id.isBlank())
.orElseGet(() -> UUID.randomUUID().toString());
MDC.put("requestId", requestId);
response.setHeader(HEADER, requestId);
try {
chain.doFilter(request, response);
} finally {
MDC.remove("requestId");
}
}
}
필터는 외부에서 전달된 ID를 우선 사용하고 없으면 새로 만든다. 응답 헤더에도 같은 값을 돌려주면 클라이언트, API 게이트웨이, 애플리케이션 로그가 같은 식별자를 공유한다. finally에서 제거하지 않으면 서블릿 스레드 재사용 때문에 다음 요청 로그에 이전 ID가 섞일 수 있다.
남길 필드와 버릴 필드를 구분한다
모든 값을 MDC에 넣으면 검색은 쉬워 보이지만 비용과 개인정보 위험이 커진다. 요청 ID, 사용자 내부 식별자의 해시, HTTP 메서드처럼 운영 판단에 필요한 낮은 민감도 필드만 남긴다. 본문, 토큰, 주민번호 같은 값은 로그 설계 단계에서 제외한다.
검증은 애플리케이션을 한 번 호출한 뒤 JSON 로그에 requestId, service.name, message가 같이 있는지 확인하면 된다. 이 첫 단계가 안정적이어야 다음 파트의 traceId와 spanId도 로그 검색 기준으로 자연스럽게 이어진다.
댓글
댓글 쓰기