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

feat: 로깅 코드 삽입 #266

Merged
merged 5 commits into from
Aug 8, 2024
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
2 changes: 1 addition & 1 deletion backend/http/offering.http
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Content-Type: application/json
"thumbnailUrl": "www.naver.com/favicon.ico",
"totalCount": 5,
"totalPrice": 10000,
"originPrice": 2000,
"originPrice": null,
"meetingAddress": "서울특별시 광진구 구의강변로 3길 11",
"meetingAddressDetail": "상세주소아파트",
"meetingAddressDong": "구의동",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class StaticRoutingConfig implements WebMvcConfigurer {
public class GlobalWebMvcConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package com.zzang.chongdae.global.exception;

import com.zzang.chongdae.logging.config.CachedHttpServletResponseWrapper;
import com.zzang.chongdae.logging.dto.LoggingErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

Expand Down Expand Up @@ -33,4 +43,35 @@ public ResponseEntity<ErrorMessage> handle(MissingServletRequestParameterExcepti
.badRequest()
.body(errorMessage);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> handle(Exception e, HttpServletRequest request, HttpServletResponse response)
throws IOException {
ErrorMessage errorMessage = new ErrorMessage("서버 관리자에게 문의하세요");

String identifier = UUID.randomUUID().toString();
String httpMethod = request.getMethod();
String uri = request.getRequestURI();
String requestBody = new String(request.getInputStream().readAllBytes());

StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String stackTrace = sw.toString();

CachedHttpServletResponseWrapper cachedResponse = (CachedHttpServletResponseWrapper) response;
String responseBody = new String(cachedResponse.getCachedBody());

long startTime = Long.parseLong(request.getAttribute("startTime").toString());
long endTime = System.currentTimeMillis();
String latency = endTime - startTime + "ms";

LoggingErrorResponse logResponse = new LoggingErrorResponse(identifier, httpMethod, uri, requestBody,
"500", responseBody, latency, stackTrace);
log.error(logResponse.toString());

return ResponseEntity
.internalServerError()
.body(errorMessage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.zzang.chongdae.logging.config;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class CachedHttpServletRequestWrapper extends HttpServletRequestWrapper {

private final byte[] cachedBody;

public CachedHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = inputStreamToByteArray(requestInputStream);
}

@Override
public ServletInputStream getInputStream() {
return new CachedServletInputStream(this.cachedBody);
}

private byte[] inputStreamToByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int read = inputStream.read();
while (read != -1) {
byteArrayOutputStream.write(read);
read = inputStream.read();
}
return byteArrayOutputStream.toByteArray();
}

private static class CachedServletInputStream extends ServletInputStream {

private final ByteArrayInputStream byteArrayInputStream;

public CachedServletInputStream(byte[] cachedBody) {
this.byteArrayInputStream = new ByteArrayInputStream(cachedBody);
}

@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}

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

@Override
public void setReadListener(ReadListener listener) {
}

@Override
public int read() {
return byteArrayInputStream.read();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.zzang.chongdae.logging.config;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

public class CachedHttpServletResponseWrapper extends HttpServletResponseWrapper {

private final ByteArrayOutputStream cachedBody;
private final ServletOutputStream outputStream;
private final PrintWriter writer;

public CachedHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
this.cachedBody = new ByteArrayOutputStream();
this.outputStream = new CachedServletOutputStream(cachedBody);
this.writer = new PrintWriter(cachedBody);
}

@Override
public ServletOutputStream getOutputStream() {
return outputStream;
}

@Override
public PrintWriter getWriter() {
return writer;
}

@Override
public void flushBuffer() throws IOException {
if (writer != null) {
writer.flush();
}
if (outputStream != null) {
outputStream.flush();
}
super.flushBuffer();
}

public byte[] getCachedBody() {
return cachedBody.toByteArray();
}

public void copyBodyToResponse() throws IOException {
if (cachedBody.size() > 0) {
HttpServletResponse response = (HttpServletResponse) getResponse();
ServletOutputStream responseOutputStream = response.getOutputStream();
responseOutputStream.write(cachedBody.toByteArray());
responseOutputStream.flush();
}
}

private static class CachedServletOutputStream extends ServletOutputStream {

private final ByteArrayOutputStream byteArrayOutputStream;

public CachedServletOutputStream(ByteArrayOutputStream byteArrayOutputStream) {
this.byteArrayOutputStream = byteArrayOutputStream;
}

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

@Override
public void setWriteListener(WriteListener listener) {
}

@Override
public void write(int buffer) {
byteArrayOutputStream.write(buffer);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.zzang.chongdae.logging.config;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@AllArgsConstructor
@Configuration
public class LoggingConfig implements WebMvcConfigurer {

private final LoggingInterceptor loggingInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.zzang.chongdae.logging.config;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.stereotype.Component;

@Component
public class LoggingFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest wrappedRequest
= new CachedHttpServletRequestWrapper((HttpServletRequest) request);
CachedHttpServletResponseWrapper wrappedResponse
= new CachedHttpServletResponseWrapper((HttpServletResponse) response);
chain.doFilter(wrappedRequest, wrappedResponse);
wrappedResponse.copyBodyToResponse();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.zzang.chongdae.logging.config;

import com.zzang.chongdae.global.exception.MarketException;
import com.zzang.chongdae.logging.domain.HttpStatusCategory;
import com.zzang.chongdae.logging.dto.LoggingInfoFailResponse;
import com.zzang.chongdae.logging.dto.LoggingInfoSuccessResponse;
import com.zzang.chongdae.logging.dto.LoggingWarnResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Slf4j
@Component
public class LoggingInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}

@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws IOException {

long startTime = Long.parseLong(request.getAttribute("startTime").toString());
long endTime = System.currentTimeMillis();
String latency = endTime - startTime + "ms";

String identifier = UUID.randomUUID().toString();
String httpMethod = request.getMethod();
String uri = request.getRequestURI();
String requestBody = new String(request.getInputStream().readAllBytes());

CachedHttpServletResponseWrapper cachedResponse = (CachedHttpServletResponseWrapper) response;
String statusCode = String.valueOf(cachedResponse.getStatus());
String responseBody = new String(cachedResponse.getCachedBody());

HttpStatusCategory statusCategory = HttpStatusCategory.fromStatusCode(cachedResponse.getStatus());
if (statusCategory == HttpStatusCategory.INFO_SUCCESS) {
LoggingInfoSuccessResponse logResponse = new LoggingInfoSuccessResponse(identifier, httpMethod, uri,
requestBody, statusCode, latency);
log.info(logResponse.toString());
}
if (statusCategory == HttpStatusCategory.INFO_FAIL) {
LoggingInfoFailResponse logResponse = new LoggingInfoFailResponse(identifier, httpMethod, uri,
requestBody, statusCode, responseBody, latency);
log.info(logResponse.toString());
}
if (statusCategory == HttpStatusCategory.WARN && ex instanceof MarketException) {
LoggingWarnResponse logResponse = new LoggingWarnResponse(identifier, httpMethod, uri,
requestBody, statusCode, responseBody, latency);
log.warn(logResponse.toString());
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.zzang.chongdae.logging.domain;

import com.zzang.chongdae.global.exception.MarketException;
import com.zzang.chongdae.logging.exception.LoggingErrorCode;
import java.util.Arrays;

public enum HttpStatusCategory {
INFO_SUCCESS(200, 299),
INFO_FAIL(400, 499),
WARN(500, 599);

private final int min;
private final int max;

HttpStatusCategory(int min, int max) {
this.min = min;
this.max = max;
}

public static HttpStatusCategory fromStatusCode(int statusCode) {
return Arrays.stream(values())
.filter(category -> statusCode >= category.min && statusCode <= category.max)
.findFirst()
.orElseThrow(() -> new MarketException(LoggingErrorCode.INVALID_STATUS));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.zzang.chongdae.logging.dto;

public record LoggingErrorResponse(String identifier,
String httpMethod,
String uri,
String requestBody,
String statusCode,
String errorMessage,
String latency,
String stacktrace) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.zzang.chongdae.logging.dto;

public record LoggingInfoFailResponse(String identifier,
String httpMethod,
String uri,
String requestBody,
String statusCode,
String errorMessage,
String latency) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.zzang.chongdae.logging.dto;

public record LoggingInfoSuccessResponse(String identifier,
String httpMethod,
String uri,
String requestBody,
String statusCode,
String latency) {
}
Loading