From 7e39f787183e95cadc84b1afdb4c6e4fc224ee1e Mon Sep 17 00:00:00 2001 From: platinouss Date: Tue, 5 Nov 2024 16:54:47 +0900 Subject: [PATCH] =?UTF-8?q?[BBB-153]=20=E2=9C=A8Feat:=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9D=91=EB=8B=B5=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit user_id, request_body, response_body 등의 정보를 로그 파일로 저장 --- .gitignore | 1 + app/external-api/build.gradle | 3 + .../global/logging/ApiLoggingFilter.java | 70 ++++++++++++++ .../global/logging/dto/ApiLogInfo.java | 92 +++++++++++++++++++ .../logging/filter/MDCLoggingFilter.java | 27 ------ .../src/main/resources/logback.xml | 37 +++++++- 6 files changed, 202 insertions(+), 28 deletions(-) create mode 100644 app/external-api/src/main/java/com/bombombom/devs/external/global/logging/ApiLoggingFilter.java create mode 100644 app/external-api/src/main/java/com/bombombom/devs/external/global/logging/dto/ApiLogInfo.java delete mode 100644 app/external-api/src/main/java/com/bombombom/devs/external/global/logging/filter/MDCLoggingFilter.java diff --git a/.gitignore b/.gitignore index 118c7228..5eb3ca7c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ *.env +/logs ### IntelliJ IDEA ### .idea diff --git a/app/external-api/build.gradle b/app/external-api/build.gradle index ed483a5f..61ea2473 100644 --- a/app/external-api/build.gradle +++ b/app/external-api/build.gradle @@ -22,6 +22,9 @@ dependencies { // Token Bucket implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0' + // logging + implementation 'net.logstash.logback:logstash-logback-encoder:8.0' + testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/ApiLoggingFilter.java b/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/ApiLoggingFilter.java new file mode 100644 index 00000000..65691d42 --- /dev/null +++ b/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/ApiLoggingFilter.java @@ -0,0 +1,70 @@ +package com.bombombom.devs.external.global.logging; + +import com.bombombom.devs.external.global.logging.dto.ApiLogInfo; +import com.bombombom.devs.security.AppUserDetails; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +@Slf4j +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +@RequiredArgsConstructor +class ApiLoggingFilter extends OncePerRequestFilter { + + private final ObjectMapper objectMapper; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws IOException, ServletException { + String requestId = Optional.ofNullable(request.getHeader("request_id")) + .orElse(UUID.randomUUID().toString()); + MDC.put("request_id", requestId); + ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); + Long userId = getRequestUserId(); + long start = System.currentTimeMillis(); + try { + filterChain.doFilter(requestWrapper, responseWrapper); + long elapsedTime = System.currentTimeMillis() - start; + ApiLogInfo apiLogInfo = ApiLogInfo.fromResult(requestWrapper, responseWrapper, userId, + elapsedTime); + log.info(objectMapper.writeValueAsString(apiLogInfo)); + } catch (Throwable e) { + ApiLogInfo apiLogInfo = ApiLogInfo.fromResult(requestWrapper, userId); + log.info(objectMapper.writeValueAsString(apiLogInfo)); + throw e; + } finally { + responseWrapper.copyBodyToResponse(); + } + MDC.clear(); + } + + private Long getRequestUserId() { + Long userId = null; + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); + if (principal instanceof AppUserDetails) { + userId = ((AppUserDetails) principal).getId(); + } + } + return userId; + } +} diff --git a/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/dto/ApiLogInfo.java b/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/dto/ApiLogInfo.java new file mode 100644 index 00000000..32549330 --- /dev/null +++ b/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/dto/ApiLogInfo.java @@ -0,0 +1,92 @@ +package com.bombombom.devs.external.global.logging.dto; + +import com.bombombom.devs.core.exception.ErrorCode; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import lombok.Builder; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +@Builder +public record ApiLogInfo( + @JsonProperty(value = "http_method") String httpMethod, + String uri, + @JsonProperty(value = "user_id") Long userId, + @JsonProperty(value = "request_header") Map requestHeader, + @JsonProperty(value = "response_header") Map responseHeader, + @JsonProperty(value = "request_body") String requestBody, + @JsonProperty(value = "response_body") String responseBody, + @JsonProperty(value = "client_ip") String clientIp, + @JsonProperty(value = "elapsed_time") long elapsedTime +) { + + public static ApiLogInfo fromResult(ContentCachingRequestWrapper request, + ContentCachingResponseWrapper response, Long userId, long elapsedTime) throws IOException { + String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8); + String responseBody = new String(response.getContentAsByteArray(), + StandardCharsets.UTF_8); + return ApiLogInfo.builder() + .httpMethod(request.getMethod()) + .uri(request.getRequestURI()) + .userId(userId) + .requestHeader(getRequestHeader(request)) + .responseHeader(getResponseHeader(response)) + .requestBody(requestBody) + .responseBody(responseBody) + .clientIp(getClientIp(request)) + .elapsedTime(elapsedTime) + .build(); + } + + public static ApiLogInfo fromResult(ContentCachingRequestWrapper request, Long userId) + throws IOException { + String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8); + return ApiLogInfo.builder() + .httpMethod(request.getMethod()) + .uri(request.getRequestURI()) + .userId(userId) + .requestHeader(getRequestHeader(request)) + .requestBody(requestBody) + .responseBody(String.valueOf(ErrorCode.UNEXPECTED_EXCEPTION)) + .clientIp(getClientIp(request)) + .build(); + } + + private static Map getRequestHeader(HttpServletRequest request) { + Map requestHeaders = new HashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + requestHeaders.put(headerName, request.getHeader(headerName)); + } + return requestHeaders; + } + + private static Map getResponseHeader(HttpServletResponse response) { + Map responseHeaders = new HashMap<>(); + Collection headerNames = response.getHeaderNames(); + for (String headerName : headerNames) { + responseHeaders.put(headerName, response.getHeader(headerName)); + } + return responseHeaders; + } + + private static String getClientIp(HttpServletRequest request) { + String clientIp; + String xForwardedForHeader = request.getHeader("X-Forwarded-For"); + if (xForwardedForHeader != null && !xForwardedForHeader.isEmpty()) { + clientIp = xForwardedForHeader.split(",")[0]; + } else { + clientIp = request.getRemoteAddr(); + } + return clientIp; + } + +} diff --git a/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/filter/MDCLoggingFilter.java b/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/filter/MDCLoggingFilter.java deleted file mode 100644 index 4e9c678e..00000000 --- a/app/external-api/src/main/java/com/bombombom/devs/external/global/logging/filter/MDCLoggingFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.bombombom.devs.external.global.logging.filter; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import java.io.IOException; -import java.util.UUID; -import org.slf4j.MDC; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; - -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -class MDCLoggingFilter implements Filter { - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, - FilterChain filterChain) throws IOException, ServletException { - final UUID uuid = UUID.randomUUID(); - MDC.put("requestId", uuid.toString()); - filterChain.doFilter(servletRequest, servletResponse); - MDC.clear(); - } -} diff --git a/app/external-api/src/main/resources/logback.xml b/app/external-api/src/main/resources/logback.xml index 6376fea6..0d2730aa 100644 --- a/app/external-api/src/main/resources/logback.xml +++ b/app/external-api/src/main/resources/logback.xml @@ -1,8 +1,11 @@ + + + value="[%d{yyyy-MM-dd HH:mm:ss}:%-4relative] [%X{request_id:-startup}] %green([%thread]) %highlight(%-5level) %boldWhite([%C.%M:%yellow(%L)]) - %msg%n"/> + @@ -13,4 +16,36 @@ + + + ${LOG_PATH}/data/${FILE_NAME}.log + + UTF-8 + + + + {"service_id":"devsService"} + + + + + + + + + + + + ${LOG_PATH}/prev/%d{yyyy-MM-dd}/${FILE_NAME}_%i.log + 90 + + 10MB + + + + + + + \ No newline at end of file