Skip to content

Commit

Permalink
[BBB-153] ✨Feat: 요청 및 응답 로그 데이터를 파일로 저장
Browse files Browse the repository at this point in the history
user_id, request_body, response_body 등의 정보를 로그 파일로 저장
  • Loading branch information
platinouss committed Nov 19, 2024
1 parent b2cd255 commit 7e39f78
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
*.env
/logs

### IntelliJ IDEA ###
.idea
Expand Down
3 changes: 3 additions & 0 deletions app/external-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
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;
}
}
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 was deleted.

37 changes: 36 additions & 1 deletion app/external-api/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<?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{requestId:-startup}] %green([%thread]) %highlight(%-5level) %boldWhite([%C.%M:%yellow(%L)]) - %msg%n"/>
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>
Expand All @@ -13,4 +16,36 @@
<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>

0 comments on commit 7e39f78

Please sign in to comment.