-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #74 from Team-BomBomBom/feat/logging_system_setup#…
…BBB-153 Feat: #BBB-153 로깅 시스템 구축
- Loading branch information
Showing
5 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ build/ | |
!**/src/main/**/build/ | ||
!**/src/test/**/build/ | ||
*.env | ||
/logs | ||
|
||
### IntelliJ IDEA ### | ||
.idea | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
...ternal-api/src/main/java/com/bombombom/devs/external/global/logging/ApiLoggingFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
92 changes: 92 additions & 0 deletions
92
...external-api/src/main/java/com/bombombom/devs/external/global/logging/dto/ApiLogInfo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String, String> requestHeader, | ||
@JsonProperty(value = "response_header") Map<String, String> 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<String, String> getRequestHeader(HttpServletRequest request) { | ||
Map<String, String> requestHeaders = new HashMap<>(); | ||
Enumeration<String> headerNames = request.getHeaderNames(); | ||
while (headerNames.hasMoreElements()) { | ||
String headerName = headerNames.nextElement(); | ||
requestHeaders.put(headerName, request.getHeader(headerName)); | ||
} | ||
return requestHeaders; | ||
} | ||
|
||
private static Map<String, String> getResponseHeader(HttpServletResponse response) { | ||
Map<String, String> responseHeaders = new HashMap<>(); | ||
Collection<String> 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; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<configuration> | ||
<timestamp key="BY_DATE" datePattern="yyyy-MM-dd"/> | ||
<property name="LOG_PATH" value="./logs"/> | ||
<property name="FILE_NAME" value="api-server-logs"/> | ||
<property name="LOG_PATTERN" | ||
value="[%d{yyyy-MM-dd HH:mm:ss}:%-4relative] [%X{request_id:-startup}] %green([%thread]) %highlight(%-5level) %boldWhite([%C.%M:%yellow(%L)]) - %msg%n"/> | ||
|
||
<springProfile name="!prod"> | ||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | ||
<encoder> | ||
<pattern>${LOG_PATTERN}</pattern> | ||
</encoder> | ||
</appender> | ||
<root level="INFO"> | ||
<appender-ref ref="CONSOLE"/> | ||
</root> | ||
</springProfile> | ||
|
||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||
<file>${LOG_PATH}/data/${FILE_NAME}.log</file> | ||
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> | ||
<charset>UTF-8</charset> | ||
<providers> | ||
<timestamp/> | ||
<pattern> | ||
<pattern>{"service_id":"devsService"}</pattern> | ||
</pattern> | ||
<mdc/> | ||
<threadName/> | ||
<logLevel/> | ||
<message/> | ||
<loggerName/> | ||
<stackTrace/> | ||
<callerData/> | ||
</providers> | ||
</encoder> | ||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | ||
<fileNamePattern>${LOG_PATH}/prev/%d{yyyy-MM-dd}/${FILE_NAME}_%i.log</fileNamePattern> | ||
<maxHistory>90</maxHistory> | ||
<timeBasedFileNamingAndTriggeringPolicy | ||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> | ||
<maxFileSize>10MB</maxFileSize> | ||
</timeBasedFileNamingAndTriggeringPolicy> | ||
</rollingPolicy> | ||
</appender> | ||
|
||
<root level="INFO"> | ||
<appender-ref ref="FILE"/> | ||
</root> | ||
</configuration> |