로그 요청 및 응답을 위한 Spring MVC Logger 작성
15586 단어 springloggingarchitecturejava
getReader 메서드 또는 getInputStream 메서드를 호출하여 데이터를 한 번만 읽을 수 있음을 알려줍니다. 이 제한을 해결하기 위해 두 가지 특수 클래스ContentCachingRequetWrapper.java와 ContentCachingResponseWrapper.java가 발명되었습니다. 이 클래스는 요청 데이터를 기록하는 데 필요합니다. 요청 및 응답에 대한 데이터를 다듬기 위해 OncePerRequestFilter.java 클래스를 확장하는 필터를 구현할 것입니다. 이를 영화 LoggingFilter라고 부르고 작성을 시작하겠습니다. 먼저 로거의 인스턴스를 선언하겠습니다.
private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
다음으로 기록하려는 모든 MIME 유형을 선언합니다.
private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
MediaType.valueOf("text/*"),
MediaType.APPLICATION_FORM_URLENCODED,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML,
MediaType.valueOf("application/*+json"),
MediaType.valueOf("application/*+xml"),
MediaType.MULTIPART_FORM_DATA
);
그런 다음 부모 클래스의 메서드를 재정의합니다.
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain)
throws ServletException, IOException {
if (isAsyncDispatch(httpServletRequest)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
doFilterWrapped(wrapRequest(httpServletRequest),
wrapResponse(httpServletResponse,
filterChain);
}
이 경우 호출이 비동기인지 확인하고, 이 경우에는 이를 연마하려고 하지 않고, 호출이 동기이면 로깅을 랩핑합니다. doFilterWrapped 메소드 구현
protected void doFilterWrapped(ContentCachingRequestWrapper contentCachingRequestWrapper, ContentCachingResponseWrapper contentCachingResponseWrapper, FilterChain filterChain) throws IOException, ServletException {
try {
beforeRequest(contentCachingRequestWrapper);
filterChain.doFilter(contentCachingRequestWrapper, contentCachingResponseWrapper);
} finally {
afterRequest(contentCachingRequestWrapper, contentCachingResponseWrapper);
contentCachingResponseWrapper.copyBodyToResponse();
}
}
이 방법에서는 이미 캐시된 요청 및 응답을 수신하고 요청에 대한 처리 및 후처리를 수행하고 응답 데이터도 복사합니다. 요청 처리 코드를 작성해 보겠습니다.
protected void beforeRequest(ContentCachingRequestWrapper request) {
if (logger.isInfoEnabled()) {
logRequestHeader(request, request.getRemoteAddr() + "|>");
}
}
로깅 수준을 확인하고 INFO 수준과 같으면 요청 헤더도 기록합니다.
사후 처리 방법을 작성해 봅시다.
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (logger.isInfoEnabled()) {
logRequestBody(request, request.getRemoteAddr() + "|>");
logResponse(response, request.getRemoteAddr() + "|>");
}
}
이 경우 요청 및 응답을 다듬기 전에 로깅 수준이 INFO로 설정되어 있는지도 확인합니다. 마지막으로 가장 흥미로운 점은 요청 및 응답뿐만 아니라 헤더의 로깅을 작성한다는 것입니다.
private static void logRequestHeader(ContentCachingRequestWrapper request, String prefix) {
String queryString = request.getQueryString();
if (queryString == null) {
logger.info("{} {} {}", prefix, request.getMethod(), request.getRequestURI());
} else {
logger.info("{} {} {}?{}", prefix, request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue -> logger.info("{} {} {}", prefix, headerName, headerValue)));
logger.info("{}", prefix);
}
logRequestHeader 메서드에서 다음과 같은 일이 발생합니다. 요청에서 문자열을 얻고 null인지 확인하고 null이면 HTTP 메서드와 해당 요청이 온 URL을 코딩합니다. 그렇지 않으면 HTTP 메서드도 기록됩니다. 요청이 도착한 URL 및 모든 요청 헤더 다음으로 요청 본문을 기록하는 코드를 작성해야 합니다.
private static void logRequestBody(ContentCachingRequestWrapper request, String prefix) {
byte[] content = request.getContentAsByteArray();
if (content.length > 0) {
logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix);
}
}
우리는 요청으로부터 데이터를 바이트 배열로 받고 배열의 크기가 0보다 큰 경우 데이터를 조금 나중에 작성할 logContent 메서드에 전달합니다.
이제 애플리케이션의 응답을 로깅하기 위한 코드를 작성할 때입니다.
private static void logResponse(ContentCachingResponseWrapper response, String prefix) {
int status = response.getStatus();
logger.info("{} {} {}", prefix, status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(header -> response.getHeaders(header).forEach(headerValue -> logger.info("{} {} {}", prefix, header, headerValue)));
logger.info("{}", prefix);
byte[] content = response.getContentAsByteArray();
if (content.length > 0) {
logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix);
}
}
여기서 정확히 무슨 일이 일어나고 있는지 알아봅시다. 첫 번째 줄에서 애플리케이션의 HTTP 응답 코드를 얻은 다음 이에 대한 데이터를 기록합니다. 다음으로 응답 헤더를 살펴보고 동일한 방식으로 기록합니다. 또한 논리는 이전에 본 것과 매우 유사합니다. 응답에서 데이터를 바이트 배열로 받은 다음 logContent 메서드에 전달합니다. 이는 다음과 같습니다.
private static void logContent(byte[] content, String contentType, String contentEncoding, String prefix) {
MediaType mediaType = MediaType.valueOf(contentType);
boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
if (visible) {
try {
String contentString = new String(content, contentEncoding);
Stream.of(contentString.split("\r\n|\r\n")).forEach(line -> logger.info("{} {}", prefix, line));
} catch (UnsupportedEncodingException e) {
logger.info("{}, [{} bytes content]", prefix, content.length);
}
} else {
logger.info("{}, [{} bytes content]", prefix, content.length);
}
}
무슨 일이야? 먼저 전송된 데이터의 유형이 도핑을 지원하는지 확인하고 지원하지 않는 경우 데이터 배열의 크기를 표시합니다. 그렇다면 요청에 지정된 인코딩을 사용하여 데이터에서 한 줄을 만들고 캐리지 리턴 및 줄 바꿈 문자를 사용하여 각 줄을 구분합니다. 인코딩이 지원되지 않으면 지원되지 않는 데이터 유형의 경우와 마찬가지로 요청 또는 응답에서 받은 데이터의 크기를 간단히 기록합니다.
그리고 코드의 마지막 터치는 ContentCachingRequestWrapper 및 ContentCachingResponseWrapper에서 HttpServletRequest 및 HttpServletResponse를 래핑하는 데 실제로 필요한 두 가지 메서드입니다.
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest httpServletRequest) {
if (httpServletRequest instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) httpServletRequest;
} else {
return new ContentCachingRequestWrapper(httpServletRequest);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse httpServletResponse) {
if (httpServletResponse instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) httpServletResponse;
} else {
return new ContentCachingResponseWrapper(httpServletResponse);
}
}
다음으로 배포 설명자에 필터를 등록해야 합니다.
<filter>
<filter-name>Filter</filter-name>
<filter-class>ru.skillfactory.filter.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
마지막으로 필터가 제대로 작동하는지 확인합니다.
2021-01-18 12:25:24 DEBUG RequestResponseBodyMethodProcessor:91 - Read "application/json;charset=ISO-8859-1" to [StudentData(firstName=John, lastName=Doe, grade=5)]
2021-01-18 12:25:24 DEBUG RequestResponseBodyMethodProcessor:265 - Using 'text/plain', given [*/*] and supported [text/plain, */*, application/json, application/*+json]
2021-01-18 12:25:24 DEBUG RequestResponseBodyMethodProcessor:91 - Writing ["You are great with your grade 5"]
2021-01-18 12:25:24 DEBUG DispatcherServlet:1131 - Completed 200 OK
2021-01-18 12:25:24 INFO LoggingFilter:104 - 0:0:0:0:0:0:0:1|> {
"firstName": "John",
"lastName": "Doe",
"grade": 5
}
2021-01-18 12:25:24 INFO LoggingFilter:89 - 0:0:0:0:0:0:0:1|> 200 OK
2021-01-18 12:25:24 INFO LoggingFilter:91 - 0:0:0:0:0:0:0:1|>
2021-01-18 12:25:24 INFO LoggingFilter:104 - 0:0:0:0:0:0:0:1|> You are great with your grade 5
LoggingFilter.java의 전체 코드
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
public class LoggingFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
MediaType.valueOf("text/*"),
MediaType.APPLICATION_FORM_URLENCODED,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML,
MediaType.valueOf("application/*+json"),
MediaType.valueOf("application/*+xml"),
MediaType.MULTIPART_FORM_DATA
);
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if (isAsyncDispatch(httpServletRequest)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
doFilterWrapped(wrapRequest(httpServletRequest), wrapResponse(httpServletResponse), filterChain);
}
}
protected void doFilterWrapped(ContentCachingRequestWrapper contentCachingRequestWrapper, ContentCachingResponseWrapper contentCachingResponseWrapper, FilterChain filterChain) throws IOException, ServletException {
try {
beforeRequest(contentCachingRequestWrapper);
filterChain.doFilter(contentCachingRequestWrapper, contentCachingResponseWrapper);
} finally {
afterRequest(contentCachingRequestWrapper, contentCachingResponseWrapper);
contentCachingResponseWrapper.copyBodyToResponse();
}
}
protected void beforeRequest(ContentCachingRequestWrapper request) {
if (logger.isInfoEnabled()) {
logRequestHeader(request, request.getRemoteAddr() + "|>");
}
}
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (logger.isInfoEnabled()) {
logRequestBody(request, request.getRemoteAddr() + "|>");
logResponse(response, request.getRemoteAddr() + "|>");
}
}
private static void logRequestHeader(ContentCachingRequestWrapper request, String prefix) {
String queryString = request.getQueryString();
if (queryString == null) {
logger.info("{} {} {}", prefix, request.getMethod(), request.getRequestURI());
} else {
logger.info("{} {} {}?{}", prefix, request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue -> logger.info("{} {} {}", prefix, headerName, headerValue)));
logger.info("{}", prefix);
}
private static void logRequestBody(ContentCachingRequestWrapper request, String prefix) {
byte[] content = request.getContentAsByteArray();
if (content.length > 0) {
logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix);
}
}
private static void logResponse(ContentCachingResponseWrapper response, String prefix) {
int status = response.getStatus();
logger.info("{} {} {}", prefix, status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(header -> response.getHeaders(header).forEach(headerValue -> logger.info("{} {} {}", prefix, header, headerValue)));
logger.info("{}", prefix);
byte[] content = response.getContentAsByteArray();
if (content.length > 0) {
logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix);
}
}
private static void logContent(byte[] content, String contentType, String contentEncoding, String prefix) {
MediaType mediaType = MediaType.valueOf(contentType);
boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
if (visible) {
try {
String contentString = new String(content, contentEncoding);
Stream.of(contentString.split("\r\n|\r\n")).forEach(line -> logger.info("{} {}", prefix, line));
} catch (UnsupportedEncodingException e) {
logger.info("{}, [{} bytes content]", prefix, content.length);
}
} else {
logger.info("{}, [{} bytes content]", prefix, content.length);
}
}
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest httpServletRequest) {
if (httpServletRequest instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) httpServletRequest;
} else {
return new ContentCachingRequestWrapper(httpServletRequest);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse httpServletResponse) {
if (httpServletResponse instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) httpServletResponse;
} else {
return new ContentCachingResponseWrapper(httpServletResponse);
}
}
}
기사가 마음에 들면 구독하고 친구들과 공유하십시오!
관심을 가져 주셔서 감사합니다
Reference
이 문제에 관하여(로그 요청 및 응답을 위한 Spring MVC Logger 작성), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/vrnsky/write-spring-mvc-logger-for-log-request-and-response-5c9m텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)