diff --git a/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java b/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java index 81abc5b1..bd09af6f 100644 --- a/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java +++ b/src/main/java/ssu/eatssu/domain/slack/entity/SlackMessageFormat.java @@ -149,7 +149,7 @@ private static String buildGrafanaLogLink() { } String application = "eatssu-" + serverEnv; - String logQl = "{application=\"" + application + "\"} |= \"reqId=" + requestId + "\""; + String logQl = "{application=\"" + application + "\"} |= \"" + requestId + "\""; long now = System.currentTimeMillis(); Map query = new LinkedHashMap<>(); diff --git a/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java b/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java index 9e82b206..691f29c1 100644 --- a/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java +++ b/src/main/java/ssu/eatssu/domain/slack/service/SlackService.java @@ -5,6 +5,7 @@ import com.slack.api.methods.request.chat.ChatPostMessageRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import ssu.eatssu.domain.slack.entity.SlackChannel; @@ -17,6 +18,7 @@ public class SlackService { private final MethodsClient methodsClient; + @Async public void sendSlackMessage(String message, SlackChannel channel) { String channelAddress = channel.getKrName(); diff --git a/src/main/java/ssu/eatssu/global/config/AsyncConfig.java b/src/main/java/ssu/eatssu/global/config/AsyncConfig.java index ca9284c6..5f9671df 100644 --- a/src/main/java/ssu/eatssu/global/config/AsyncConfig.java +++ b/src/main/java/ssu/eatssu/global/config/AsyncConfig.java @@ -1,9 +1,31 @@ package ssu.eatssu.global.config; +import org.slf4j.MDC; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskDecorator; import org.springframework.scheduling.annotation.EnableAsync; +import java.util.Map; + @Configuration @EnableAsync public class AsyncConfig { + + @Bean + public TaskDecorator mdcTaskDecorator() { + return runnable -> { + Map context = MDC.getCopyOfContextMap(); + return () -> { + if (context != null) { + MDC.setContextMap(context); + } + try { + runnable.run(); + } finally { + MDC.clear(); + } + }; + }; + } } diff --git a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java index 16613daa..901cc7f6 100644 --- a/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/ssu/eatssu/global/handler/GlobalExceptionHandler.java @@ -1,9 +1,6 @@ package ssu.eatssu.global.handler; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.TypeMismatchException; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -25,7 +22,6 @@ import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponse; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -33,14 +29,8 @@ /** * 전역 Controller 예외처리 */ -@Slf4j @RestControllerAdvice -@RequiredArgsConstructor public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { - private final SlackErrorNotifier slackErrorNotifier; - @Value("${server.env:unknown}") - private String serverEnv; - /** * BaseException 처리 */ diff --git a/src/main/java/ssu/eatssu/global/log/MDCLoggingFilter.java b/src/main/java/ssu/eatssu/global/log/MDCLoggingFilter.java index e1d6ce9e..88fe8057 100644 --- a/src/main/java/ssu/eatssu/global/log/MDCLoggingFilter.java +++ b/src/main/java/ssu/eatssu/global/log/MDCLoggingFilter.java @@ -1,7 +1,12 @@ package ssu.eatssu.global.log; -import jakarta.servlet.*; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.MDC; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -9,18 +14,25 @@ import java.io.IOException; import java.util.UUID; +import java.util.regex.Pattern; @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class MDCLoggingFilter implements Filter { + private static final String REQUEST_ID_HEADER = "X-Request-ID"; + private static final Pattern VALID_REQUEST_ID = Pattern.compile("[A-Za-z0-9-]{1,64}"); + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - String requestId = ((HttpServletRequest) request).getHeader("X-RequestID"); - if (requestId == null) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String requestId = httpRequest.getHeader(REQUEST_ID_HEADER); + if (requestId == null || !VALID_REQUEST_ID.matcher(requestId).matches()) { requestId = UUID.randomUUID().toString().replace("-", ""); } MDC.put("requestId", requestId); + MDC.put("uri", httpRequest.getMethod() + " " + httpRequest.getRequestURI()); + ((HttpServletResponse) response).setHeader(REQUEST_ID_HEADER, requestId); try { chain.doFilter(request, response); } finally { diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 5286c067..6fcdb99e 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -44,6 +44,9 @@ spring: max-file-size: 20MB max-request-size: 20MB + mvc: + log-resolved-exception: true + ## Auth jwt: secret: diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index e3bacb43..d2b074a9 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -45,6 +45,9 @@ spring: max-file-size: 20MB max-request-size: 20MB + mvc: + log-resolved-exception: true + ## Auth jwt: secret: diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 5534a23b..d91e0132 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -7,7 +7,7 @@ + value="%d{yyyy-MM-dd HH:mm:ss.SSS, Asia/Seoul} %clr([%level]) [%thread] [reqId=%X{requestId}] %cyan(%logger{36}) - %msg%n"/> diff --git a/src/test/java/ssu/eatssu/domain/admin/controller/ManageCategoryControllerTest.java b/src/test/java/ssu/eatssu/domain/admin/controller/ManageCategoryControllerTest.java index b6507161..dc589c0f 100644 --- a/src/test/java/ssu/eatssu/domain/admin/controller/ManageCategoryControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/admin/controller/ManageCategoryControllerTest.java @@ -11,7 +11,6 @@ import ssu.eatssu.domain.admin.dto.request.RegisterCategoryRequest; import ssu.eatssu.domain.admin.service.ManageCategoryService; import ssu.eatssu.domain.restaurant.entity.Restaurant; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.GlobalExceptionHandler; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -29,15 +28,12 @@ class ManageCategoryControllerTest { @Mock private ManageCategoryService manageCategoryService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new ManageCategoryController(manageCategoryService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .build(); } diff --git a/src/test/java/ssu/eatssu/domain/admin/controller/ManageFixMenuControllerTest.java b/src/test/java/ssu/eatssu/domain/admin/controller/ManageFixMenuControllerTest.java index 139c89c3..9efa3b41 100644 --- a/src/test/java/ssu/eatssu/domain/admin/controller/ManageFixMenuControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/admin/controller/ManageFixMenuControllerTest.java @@ -13,7 +13,6 @@ import ssu.eatssu.domain.admin.dto.response.MenuBoards; import ssu.eatssu.domain.admin.service.ManageFixMenuService; import ssu.eatssu.domain.restaurant.entity.Restaurant; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.GlobalExceptionHandler; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -35,15 +34,12 @@ class ManageFixMenuControllerTest { @Mock private ManageFixMenuService manageFixMenuService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new ManageFixMenuController(manageFixMenuService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .build(); } diff --git a/src/test/java/ssu/eatssu/domain/admin/controller/ManageInquiryControllerTest.java b/src/test/java/ssu/eatssu/domain/admin/controller/ManageInquiryControllerTest.java index 97866b3c..48641cfe 100644 --- a/src/test/java/ssu/eatssu/domain/admin/controller/ManageInquiryControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/admin/controller/ManageInquiryControllerTest.java @@ -15,7 +15,6 @@ import ssu.eatssu.domain.admin.dto.response.PageWrapper; import ssu.eatssu.domain.admin.service.ManageInquiryService; import ssu.eatssu.domain.inquiry.entity.InquiryStatus; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.GlobalExceptionHandler; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -39,15 +38,12 @@ class ManageInquiryControllerTest { @Mock private ManageInquiryService manageInquiryService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new ManageInquiryController(manageInquiryService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver()) .build(); } diff --git a/src/test/java/ssu/eatssu/domain/admin/controller/ManageMealControllerTest.java b/src/test/java/ssu/eatssu/domain/admin/controller/ManageMealControllerTest.java index 020645b2..99ef0f4f 100644 --- a/src/test/java/ssu/eatssu/domain/admin/controller/ManageMealControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/admin/controller/ManageMealControllerTest.java @@ -14,7 +14,6 @@ import ssu.eatssu.domain.admin.service.ManageMealService; import ssu.eatssu.domain.menu.entity.constants.TimePart; import ssu.eatssu.domain.restaurant.entity.Restaurant; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.GlobalExceptionHandler; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -41,15 +40,12 @@ class ManageMealControllerTest { @Mock private ManageMealService manageMealService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new ManageMealController(manageMealService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .build(); } diff --git a/src/test/java/ssu/eatssu/domain/admin/controller/ManageReportControllerTest.java b/src/test/java/ssu/eatssu/domain/admin/controller/ManageReportControllerTest.java index b59e5b2d..e715cdad 100644 --- a/src/test/java/ssu/eatssu/domain/admin/controller/ManageReportControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/admin/controller/ManageReportControllerTest.java @@ -12,7 +12,6 @@ import ssu.eatssu.domain.admin.dto.response.PageWrapper; import ssu.eatssu.domain.admin.dto.response.ReportLine; import ssu.eatssu.domain.admin.service.ManageReportService; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.GlobalExceptionHandler; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -35,15 +34,12 @@ class ManageReportControllerTest { @Mock private ManageReportService manageReportService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new ManageReportController(manageReportService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver()) .build(); } diff --git a/src/test/java/ssu/eatssu/domain/admin/controller/ManageReviewControllerTest.java b/src/test/java/ssu/eatssu/domain/admin/controller/ManageReviewControllerTest.java index be5ced6e..633cb0c9 100644 --- a/src/test/java/ssu/eatssu/domain/admin/controller/ManageReviewControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/admin/controller/ManageReviewControllerTest.java @@ -8,7 +8,6 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import ssu.eatssu.domain.admin.service.ManageReviewService; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.GlobalExceptionHandler; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -26,15 +25,12 @@ class ManageReviewControllerTest { @Mock private ManageReviewService manageReviewService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new ManageReviewController(manageReviewService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .build(); } diff --git a/src/test/java/ssu/eatssu/domain/menu/presentation/rest/MealControllerTest.java b/src/test/java/ssu/eatssu/domain/menu/presentation/rest/MealControllerTest.java index 0e5c1571..5827fb87 100644 --- a/src/test/java/ssu/eatssu/domain/menu/presentation/rest/MealControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/menu/presentation/rest/MealControllerTest.java @@ -13,7 +13,6 @@ import ssu.eatssu.domain.menu.presentation.dto.response.MenusInMealResponse; import ssu.eatssu.domain.menu.service.MealService; import ssu.eatssu.domain.restaurant.entity.Restaurant; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.GlobalExceptionHandler; import java.util.List; @@ -36,15 +35,12 @@ class MealControllerTest { @Mock private MealService mealService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new MealController(mealService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .build(); } diff --git a/src/test/java/ssu/eatssu/domain/partnership/presentation/PartnershipControllerTest.java b/src/test/java/ssu/eatssu/domain/partnership/presentation/PartnershipControllerTest.java index 96dc44e1..ea46dcfc 100644 --- a/src/test/java/ssu/eatssu/domain/partnership/presentation/PartnershipControllerTest.java +++ b/src/test/java/ssu/eatssu/domain/partnership/presentation/PartnershipControllerTest.java @@ -15,7 +15,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.partnership.service.PartnershipService; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.domain.user.entity.Role; import ssu.eatssu.global.handler.GlobalExceptionHandler; import ssu.eatssu.global.handler.response.BaseException; @@ -39,9 +38,6 @@ class PartnershipControllerTest { @Mock private PartnershipService partnershipService; - @Mock - private SlackErrorNotifier slackErrorNotifier; - private MockMvc mockMvc; @BeforeEach @@ -49,7 +45,7 @@ void setUp() { ObjectMapper objectMapper = new ObjectMapper().registerModule(new ParameterNamesModule()) .registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()); mockMvc = MockMvcBuilders.standaloneSetup(new PartnershipController(partnershipService)) - .setControllerAdvice(new GlobalExceptionHandler(slackErrorNotifier)) + .setControllerAdvice(new GlobalExceptionHandler()) .setCustomArgumentResolvers(new AuthenticationPrincipalArgumentResolver()) .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper)) .build(); diff --git a/src/test/java/ssu/eatssu/domain/slack/entity/SlackMessageFormatTest.java b/src/test/java/ssu/eatssu/domain/slack/entity/SlackMessageFormatTest.java index 4a14f9fb..4d832206 100644 --- a/src/test/java/ssu/eatssu/domain/slack/entity/SlackMessageFormatTest.java +++ b/src/test/java/ssu/eatssu/domain/slack/entity/SlackMessageFormatTest.java @@ -27,6 +27,8 @@ import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponseStatus; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.Date; import static org.assertj.core.api.Assertions.assertThat; @@ -118,6 +120,9 @@ void sendServerErrorIncludesGrafanaLogLinkWhenConfiguredAndRequestIdPresent() { // then assertThat(message).contains("https://example.grafana.net/explore?schemaVersion=1&panes=", "Grafana에서 로그 보기"); + assertThat(URLDecoder.decode(message, StandardCharsets.UTF_8)) + .contains("|= \\\"test-request-id\\\"") + .doesNotContain("reqId="); } @Test diff --git a/src/test/java/ssu/eatssu/global/config/AsyncConfigTest.java b/src/test/java/ssu/eatssu/global/config/AsyncConfigTest.java index 2663eb05..b96ecf5c 100644 --- a/src/test/java/ssu/eatssu/global/config/AsyncConfigTest.java +++ b/src/test/java/ssu/eatssu/global/config/AsyncConfigTest.java @@ -1,16 +1,52 @@ package ssu.eatssu.global.config; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.slf4j.MDC; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Future; import static org.assertj.core.api.Assertions.assertThat; class AsyncConfigTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) + .withUserConfiguration(AsyncConfig.class); + + @AfterEach + void clearMdc() { + MDC.clear(); + } + @Test void 비동기_설정이_활성화되어_있다() { assertThat(AsyncConfig.class.isAnnotationPresent(Configuration.class)).isTrue(); assertThat(AsyncConfig.class.isAnnotationPresent(EnableAsync.class)).isTrue(); } + + @Test + void propagatesMdcToAsyncThreadAndClearsAfterTask() { + contextRunner.run(context -> { + // given + ThreadPoolTaskExecutor executor = context.getBean(ThreadPoolTaskExecutor.class); + executor.setCorePoolSize(1); + MDC.put("requestId", "request-1"); + + // when + Future propagated = executor.submit(() -> MDC.get("requestId")); + MDC.clear(); + Future cleared = executor.submit(() -> MDC.get("requestId")); + + // then + assertThat(propagated.get()).isEqualTo("request-1"); + assertThat(cleared.get()).isNull(); + }); + } } diff --git a/src/test/java/ssu/eatssu/global/handler/GlobalExceptionHandlerTest.java b/src/test/java/ssu/eatssu/global/handler/GlobalExceptionHandlerTest.java index 3ba4de8d..2f1f0c5a 100644 --- a/src/test/java/ssu/eatssu/global/handler/GlobalExceptionHandlerTest.java +++ b/src/test/java/ssu/eatssu/global/handler/GlobalExceptionHandlerTest.java @@ -20,7 +20,6 @@ import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.NoHandlerFoundException; -import ssu.eatssu.domain.slack.service.SlackErrorNotifier; import ssu.eatssu.global.handler.response.BaseException; import ssu.eatssu.global.handler.response.BaseResponse; import ssu.eatssu.global.handler.response.BaseResponseStatus; @@ -32,7 +31,7 @@ class GlobalExceptionHandlerTest { - private final GlobalExceptionHandler handler = new GlobalExceptionHandler(mock(SlackErrorNotifier.class)); + private final GlobalExceptionHandler handler = new GlobalExceptionHandler(); private final HttpHeaders headers = new HttpHeaders(); private final org.springframework.web.context.request.WebRequest request = mock(org.springframework.web.context.request.WebRequest.class); diff --git a/src/test/java/ssu/eatssu/global/log/MDCLoggingFilterTest.java b/src/test/java/ssu/eatssu/global/log/MDCLoggingFilterTest.java index 46dd90a4..0523cb0a 100644 --- a/src/test/java/ssu/eatssu/global/log/MDCLoggingFilterTest.java +++ b/src/test/java/ssu/eatssu/global/log/MDCLoggingFilterTest.java @@ -18,7 +18,7 @@ void clearMdc() { @Test void 요청_ID를_전달하고_필터_종료_후_제거한다() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("X-RequestID", "request-1"); + request.addHeader("X-Request-ID", "request-1"); new MDCLoggingFilter().doFilter(request, new MockHttpServletResponse(), (req, res) -> assertThat(MDC.get("requestId")).isEqualTo("request-1")); @@ -26,9 +26,51 @@ void clearMdc() { assertThat(MDC.get("requestId")).isNull(); } + @Test + void putsMethodAndUriIntoMdcAndClearsAfterFilter() throws Exception { + // given + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/v2/reviews"); + + // when + new MDCLoggingFilter().doFilter(request, new MockHttpServletResponse(), (req, res) -> + assertThat(MDC.get("uri")).isEqualTo("POST /v2/reviews")); + + // then + assertThat(MDC.get("uri")).isNull(); + } + @Test void 요청_ID가_없으면_새_ID를_생성한다() throws Exception { new MDCLoggingFilter().doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), (req, res) -> assertThat(MDC.get("requestId")).hasSize(32)); } + + @Test + void returnsRequestIdInResponseHeader() throws Exception { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("X-Request-ID", "request-1"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // when + new MDCLoggingFilter().doFilter(request, response, (req, res) -> {}); + + // then + assertThat(response.getHeader("X-Request-ID")).isEqualTo("request-1"); + } + + @Test + void replacesInvalidRequestIdWithGeneratedOne() throws Exception { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("X-Request-ID", "bad id!" + "a".repeat(64)); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // when + new MDCLoggingFilter().doFilter(request, response, (req, res) -> + assertThat(MDC.get("requestId")).matches("[0-9a-f]{32}")); + + // then + assertThat(response.getHeader("X-Request-ID")).matches("[0-9a-f]{32}"); + } }