Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SO1S-321 backend logging 추가 #35

Merged
merged 8 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

ext {
applicationVersion = "0.4.0"
applicationVersion = "0.4.1"
shinilseop marked this conversation as resolved.
Show resolved Hide resolved
repo = System.getenv("REPO")
set('snippetsDir', file("build/generated-snippets"))
}
Expand Down Expand Up @@ -142,6 +142,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator:2.7.3'
implementation 'io.micrometer:micrometer-registry-prometheus:1.9.3'

// sleuth & zipkin
implementation 'org.springframework.cloud:spring-cloud-starter-sleuth:3.1.4'
implementation 'org.springframework.cloud:spring-cloud-sleuth-zipkin:3.1.4'

}

tasks.named('test') {
Expand Down
105 changes: 105 additions & 0 deletions src/main/java/io/so1s/backend/global/filter/LoggingFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.so1s.backend.global.filter;

import io.so1s.backend.global.filter.wrapper.RequestWrapper;
import io.so1s.backend.global.filter.wrapper.ResponseWrapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;

@Slf4j
@Component
public class LoggingFilter extends OncePerRequestFilter {

private static void logRequest(RequestWrapper requestWrapper) throws IOException {
log.info("[REQUEST] Method=[{}], url=[{}], Header=[{}], Body=[{}]",
requestWrapper.getMethod(), requestWrapper.getRequestURI(),
getRequestHeaders(requestWrapper),
getPayload(requestWrapper.getContentType(), requestWrapper.getInputStream()));
}

private static Map getRequestHeaders(HttpServletRequest request) {
Map headerMap = new HashMap<>();
Enumeration headerArray = request.getHeaderNames();
while (headerArray.hasMoreElements()) {
String headerName = (String) headerArray.nextElement();
headerMap.put(headerName, request.getHeader(headerName));
}
return headerMap;
}

private static void logResponse(ResponseWrapper responseWrapper) throws IOException {
log.info("[RESPONSE] Status=[{}], Header=[{}], Body=[{}]",
responseWrapper.getStatus(),
getResponseHeader(responseWrapper),
getPayload(responseWrapper.getContentType(), responseWrapper.getContentInputStream()));
}

private static Map getResponseHeader(HttpServletResponse response) {
Map headerMap = new HashMap<>();
Collection<String> headerNames = response.getHeaderNames();
for (String headerName : headerNames) {
headerMap.put(headerName, response.getHeader(headerName));
}
return headerMap;
}

private static String getPayload(String contentType, InputStream inputStream) throws IOException {
if (isVisible(MediaType.valueOf(contentType == null ? "application/json" : contentType))) {
byte[] content = StreamUtils.copyToByteArray(inputStream);
if (content.length > 0) {
return new String(content);
}
}
return " - ";
}

private static boolean isVisible(MediaType mediaType) {
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
);

return VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
}

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

if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(new RequestWrapper(request), new ResponseWrapper(response), filterChain);
}
}

protected void doFilterWrapped(RequestWrapper request, ResponseWrapper response,
FilterChain filterChain) throws ServletException, IOException {
try {
logRequest(request);
filterChain.doFilter(request, response);
} finally {
logResponse(response);
response.copyBodyToResponse();
shinilseop marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.so1s.backend.global.filter.wrapper;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

public class RequestWrapper extends ContentCachingRequestWrapper {

private byte[] cachedInputStream;

public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedInputStream = StreamUtils.copyToByteArray(requestInputStream);
}

@Override
public ServletInputStream getInputStream() {
return new ServletInputStream() {
private InputStream cachedBodyInputStream = new ByteArrayInputStream(cachedInputStream);

@Override
public boolean isFinished() {
try {
return cachedBodyInputStream.available() == 0;
} catch (IOException e) {
}
return false;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException();
}

@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.so1s.backend.global.filter.wrapper;

import javax.servlet.http.HttpServletResponse;
import org.springframework.web.util.ContentCachingResponseWrapper;

public class ResponseWrapper extends ContentCachingResponseWrapper {

public ResponseWrapper(HttpServletResponse response) {
super(response);
}
}
6 changes: 6 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ spring:
application:
name: so1s-api-server

zipkin:
base-url: ${zipkin-url} # zipkin server
sleuth:
sampler:
probability: 1.0

logging:
level:
org:
Expand Down