Skip to content

Commit

Permalink
요청 및 응답 로깅 구현(issue #448) (#459)
Browse files Browse the repository at this point in the history
* fix: 기본 설정을 가져와서 같은 로그가 중복으로 찍히는 오류 수정

* feat: 로그 포맷 변경

* feat: HTTP 요청 및 응답 로깅 구현

* fix: 로그 출력 작업 이후 MDC를 초기화하도록 수정

MDC는 스레드에 저장되는데, 스레드 풀링에 의해 스레드를 재사용할 때 이전 MDC가 남아있지 않도록 초기화하는 것

* feat: Trace-Id를 응답 헤더에 추가

* refactor: 필요 없는 static 키워드 삭제

* fix: 쿼리 스트링이 없을 경우 아예 출력하지 않도록 수정

* fix: Json 형태의 데이터는 이쁘게 출력하도록 변경

* fix: 오타 수정

* fix: 로그 포맷 수정

* chore: 로그가 여러 줄로 나뉘지 않도록 수정
  • Loading branch information
robinjoon authored Sep 20, 2024
1 parent 2106dab commit 1bfe652
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 3 deletions.
154 changes: 154 additions & 0 deletions backend/src/main/java/develup/api/logging/LoggingFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package develup.api.logging;

import java.io.IOException;
import java.util.Collections;
import java.util.UUID;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

@Component
@Order(1)
public class LoggingFilter extends OncePerRequestFilter {

private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

private final ObjectMapper objectMapper;

public LoggingFilter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
saveTraceId(request);

ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper cachedResponse = new ContentCachingResponseWrapper(response);

saveRequestBody(cachedRequest);

filterChain.doFilter(cachedRequest, cachedResponse);

saveRequestMethod(cachedRequest);
saveRequestUri(cachedRequest);
saveQueryString(cachedRequest);
saveRequestHeader(cachedRequest);
printRequestLog();

addTraceIdToResponse(cachedResponse);
saveResponseBody(cachedResponse);
cachedResponse.copyBodyToResponse();
saveResponseHeader(cachedResponse);
printResponseLog();
MDC.clear();
}

private void saveTraceId(HttpServletRequest request) {
String header = request.getHeader("X-Request-ID");
if (header == null || header.isBlank()) {
header = UUID.randomUUID().toString();
}
MDC.put("traceId", header);
}

private void saveRequestBody(ContentCachingRequestWrapper cachedRequest) {
String content = cachedRequest.getContentAsString();
String prettyJsonString = toPrettyJsonString(content);
MDC.put("requestBody", prettyJsonString);
}

private String toPrettyJsonString(String jsonString) {
try {
Object json = objectMapper.readValue(jsonString, Object.class);
ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter();
return writer.writeValueAsString(json);
} catch (JsonProcessingException e) {
return jsonString;
}
}

private void saveRequestMethod(ContentCachingRequestWrapper cachedRequest) {
MDC.put("method", cachedRequest.getMethod());
}

private void saveRequestUri(ContentCachingRequestWrapper cachedRequest) {
MDC.put("requestUri", cachedRequest.getRequestURI());
}

private void saveQueryString(ContentCachingRequestWrapper cachedRequest) {
String queryString = "?" + cachedRequest.getQueryString();
if (queryString.equals("?null")) {
queryString = "";
}
MDC.put("queryString", queryString);
}

private void saveRequestHeader(HttpServletRequest request) {
String headers = Collections.list(request.getHeaderNames())
.stream()
.map(headerName -> headerName + " : " + request.getHeader(headerName))
.collect(Collectors.joining("\n"));
MDC.put("requestHeaders", headers);
}

private void addTraceIdToResponse(HttpServletResponse response) {
response.addHeader("Trace-Id", MDC.get("traceId"));
}

private void saveResponseBody(ContentCachingResponseWrapper cachedResponse) {
String content = new String(cachedResponse.getContentAsByteArray());
MDC.put("responseBody", toPrettyJsonString(content));
}

private void saveResponseHeader(ContentCachingResponseWrapper cachedResponse) {
String responseHeader = cachedResponse.getHeaderNames()
.stream()
.map(headerName -> headerName + " : " + cachedResponse.getHeader(headerName))
.collect(Collectors.joining("\n"));
MDC.put("responseHeader", responseHeader);
}

private void printRequestLog() {
String template = """
Request
{} {}{}
Headers :
{}
Content :
{}
""";
log.info(
template,
MDC.get("method"),
MDC.get("requestUri"),
MDC.get("queryString"),
MDC.get("requestHeaders"),
MDC.get("requestBody")
);
}

private void printResponseLog() {
String responseLogTemplate = """
Response
Headers :
{}
Content :
{}
""";
log.info(responseLogTemplate, MDC.get("responseHeader"), MDC.get("responseBody"));
}
}
7 changes: 4 additions & 3 deletions backend/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<springProperty scope="context" name="appName" source="spring.application.name"/>

<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} | %t | %highlight(%-5p) | %cyan(%logger{36}) | %m%n
%d{yyyy-MM-dd HH:mm:ss.SSS} | %t | traceId=%X{traceId} | %highlight(%-5p) | %cyan(%logger{36}) | %m%n
</pattern>
</encoder>
</appender>
Expand All @@ -18,7 +17,9 @@
</http>
<format>
<label>
<pattern>app=${appName},host=${HOSTNAME},traceID=%X{traceId:-NONE},level=%level</pattern>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} | %t | traceId=%X{traceId} | %highlight(%-5p) | %cyan(%logger{36}) | app=${appName} | host=${HOSTNAME} | %m%n
</pattern>
</label>
<message>
<pattern>${FILE_LOG_PATTERN}</pattern>
Expand Down

0 comments on commit 1bfe652

Please sign in to comment.