diff --git a/src/main/java/umc/th/juinjang/apiPayload/exception/handler/TempHandler.java b/src/main/java/umc/th/juinjang/apiPayload/exception/handler/TempHandler.java deleted file mode 100644 index 502f1375..00000000 --- a/src/main/java/umc/th/juinjang/apiPayload/exception/handler/TempHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -package umc.th.juinjang.apiPayload.exception.handler; - - -import umc.th.juinjang.apiPayload.code.BaseErrorCode; -import umc.th.juinjang.apiPayload.exception.GeneralException; - -public class TempHandler extends GeneralException { - - public TempHandler(BaseErrorCode errorCode) { - super(errorCode); - } -} diff --git a/src/main/java/umc/th/juinjang/config/ApiFilterConfig.java b/src/main/java/umc/th/juinjang/config/ApiFilterConfig.java new file mode 100644 index 00000000..cf6e6bfa --- /dev/null +++ b/src/main/java/umc/th/juinjang/config/ApiFilterConfig.java @@ -0,0 +1,27 @@ +package umc.th.juinjang.config; + +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import umc.th.juinjang.controller.monitoring.APILoggerFilter; + +@Configuration +@Slf4j +public class ApiFilterConfig { + + @Value("${logging.api.excluded-paths}") + private List excludedUrls; + + @Bean + public FilterRegistrationBean loggingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new APILoggerFilter(excludedUrls)); + registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 순서 설정 + registrationBean.setUrlPatterns(List.of("/*")); + return registrationBean; + } +} diff --git a/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerFilter.java b/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerFilter.java new file mode 100644 index 00000000..b999f12e --- /dev/null +++ b/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerFilter.java @@ -0,0 +1,56 @@ +package umc.th.juinjang.controller.monitoring; + +import static umc.th.juinjang.utils.LoggerProvider.getLogger; +import static umc.th.juinjang.utils.LoggerProvider.registerRequestId; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.MDC; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingResponseWrapper; + +@Slf4j +public class APILoggerFilter extends OncePerRequestFilter { + + private final APILoggerPrinter apiLoggerPrinter = new APILoggerPrinter(); + private static final Logger logger = getLogger(APILoggerFilter.class); + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + private final List EXCLUDED_URLS; + + public APILoggerFilter(List EXCLUDED_URLS) { + this.EXCLUDED_URLS = EXCLUDED_URLS; + } + + @Override + protected void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain chain ) { + CustomContentCachingHttpRequestWrapper requestWrapper = new CustomContentCachingHttpRequestWrapper(servletRequest); + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(servletResponse); + registerRequestId(UUID.randomUUID().toString()); + + try { + if (shouldNotFilter(requestWrapper)) { + chain.doFilter(requestWrapper, responseWrapper); + return; + } + apiLoggerPrinter.print(new APIRequestLoggerGenerator(requestWrapper)); + chain.doFilter(requestWrapper, responseWrapper); + apiLoggerPrinter.print(new APIResponseLoggerGenerator(responseWrapper)); + responseWrapper.copyBodyToResponse(); + } catch (Exception e) { + logger.error("APILogger 필터 오류"); + } finally { + MDC.clear(); + } + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return EXCLUDED_URLS.stream().anyMatch(pattern -> pathMatcher.match(pattern, request.getRequestURI())); + } +} diff --git a/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerGenerator.java b/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerGenerator.java new file mode 100644 index 00000000..d0fe23ce --- /dev/null +++ b/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerGenerator.java @@ -0,0 +1,24 @@ +package umc.th.juinjang.controller.monitoring; + +import static umc.th.juinjang.utils.LoggerProvider.getLogger; + +import org.slf4j.Logger; +import org.slf4j.MDC; + +public abstract class APILoggerGenerator { + + protected static final Logger logger = getLogger(APILoggerGenerator.class); + + public APILoggerGenerator() { + } + + protected StringBuilder getBaseLogInfo() { + StringBuilder logBuilder = new StringBuilder(); + logBuilder.append("[request_id] ").append(MDC.get("request_id")).append(" "); + return logBuilder; + } + + abstract public String generateLog(); + + abstract protected String getBody(byte[] info); +} diff --git a/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerPrinter.java b/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerPrinter.java new file mode 100644 index 00000000..8cbe628f --- /dev/null +++ b/src/main/java/umc/th/juinjang/controller/monitoring/APILoggerPrinter.java @@ -0,0 +1,19 @@ +package umc.th.juinjang.controller.monitoring; + +import static umc.th.juinjang.utils.LoggerProvider.getLogger; + +import java.util.List; +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +@Component +public class APILoggerPrinter { + private static final Logger logger = getLogger(APILoggerPrinter.class); + + public APILoggerPrinter() { + } + + public void print(APILoggerGenerator apiLoggerGenerator) { + logger.info(apiLoggerGenerator.generateLog()); + } +} diff --git a/src/main/java/umc/th/juinjang/controller/monitoring/APIRequestLoggerGenerator.java b/src/main/java/umc/th/juinjang/controller/monitoring/APIRequestLoggerGenerator.java new file mode 100644 index 00000000..db0d58ce --- /dev/null +++ b/src/main/java/umc/th/juinjang/controller/monitoring/APIRequestLoggerGenerator.java @@ -0,0 +1,51 @@ +package umc.th.juinjang.controller.monitoring; + +import jakarta.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.stream.Collectors; + +public class APIRequestLoggerGenerator extends APILoggerGenerator{ + + private final CustomContentCachingHttpRequestWrapper request; + + public APIRequestLoggerGenerator(CustomContentCachingHttpRequestWrapper request) { + this.request = request; + } + + @Override + public String generateLog() { + StringBuilder logBuilder = new StringBuilder(); + + logBuilder.append("Request : ").append(" "); + logBuilder.append(getBaseLogInfo()); + logBuilder.append("[method] ").append(request.getMethod()).append(" "); + logBuilder.append("[uri] ").append(getQuery()).append(" "); + logBuilder.append("[headers] ").append(getHeadersAsString(request)).append(" "); + logBuilder.append("[requestBody] ").append(getBody(request.getCachedBody())).append(" "); + + return logBuilder.toString(); + } + + private String getQuery() { + String uri = request.getRequestURI(); + String queryString = request.getQueryString(); + + if (queryString != null) { + uri += "?" + queryString; + } + return uri; + } + + private String getHeadersAsString(HttpServletRequest request) { + return Collections.list(request.getHeaderNames()).stream() + .filter(headerName -> !headerName.equalsIgnoreCase("Authorization")) + .filter(headerName -> !headerName.equalsIgnoreCase("refresh-token")) + .map(headerName -> headerName + "=" + request.getHeader(headerName)) + .collect(Collectors.joining(", ")); + } + + protected String getBody(byte[] info) { + return new String(info, StandardCharsets.UTF_8).replace("\n", "").replace("\r", ""); + } +} diff --git a/src/main/java/umc/th/juinjang/controller/monitoring/APIResponseLoggerGenerator.java b/src/main/java/umc/th/juinjang/controller/monitoring/APIResponseLoggerGenerator.java new file mode 100644 index 00000000..d3317df5 --- /dev/null +++ b/src/main/java/umc/th/juinjang/controller/monitoring/APIResponseLoggerGenerator.java @@ -0,0 +1,43 @@ +package umc.th.juinjang.controller.monitoring; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.charset.StandardCharsets; +import org.springframework.web.util.ContentCachingResponseWrapper; + +public class APIResponseLoggerGenerator extends APILoggerGenerator { + private final ContentCachingResponseWrapper response; + + public APIResponseLoggerGenerator(ContentCachingResponseWrapper response) { + this.response = response; + } + + @Override + public String generateLog() { + StringBuilder logBuilder = new StringBuilder(); + + logBuilder.append("Response : ").append(" "); + logBuilder.append(getBaseLogInfo()); + logBuilder.append("[status] ").append(response.getStatus()).append(" "); + logBuilder.append("[responseBody] ").append(getBody(response.getContentAsByteArray())).append(" "); + + return logBuilder.toString(); + } + + protected String getBody(byte[] info) { + String responseBody = ""; + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(new String(info, StandardCharsets.UTF_8)); + responseBody = replaceToken(rootNode.path("message").asText("N/A")); + } catch (Exception e) { + logger.error("responseBody 추출 오류"); + } + return responseBody; + } + + private String replaceToken(String responseBody) { + return responseBody.replaceAll("\"accessToken\":\"[^\"]*\"", "\"accessToken\":\"[TOKEN]\"") + .replaceAll("\"refreshToken\":\"[^\"]*\"", "\"refreshToken\":\"[TOKEN]\""); + } +} diff --git a/src/main/java/umc/th/juinjang/controller/monitoring/CustomContentCachingHttpRequestWrapper.java b/src/main/java/umc/th/juinjang/controller/monitoring/CustomContentCachingHttpRequestWrapper.java new file mode 100644 index 00000000..5b85b6fb --- /dev/null +++ b/src/main/java/umc/th/juinjang/controller/monitoring/CustomContentCachingHttpRequestWrapper.java @@ -0,0 +1,57 @@ +package umc.th.juinjang.controller.monitoring; + +import static umc.th.juinjang.utils.LoggerProvider.getLogger; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import org.slf4j.Logger; + +public class CustomContentCachingHttpRequestWrapper extends HttpServletRequestWrapper { + + private final Logger logger = getLogger(CustomContentCachingHttpRequestWrapper.class); + private final byte[] cachedBody; + + public CustomContentCachingHttpRequestWrapper(HttpServletRequest request) { + super(request); + byte[] tempBody = new byte[0]; + try { + tempBody = request.getInputStream().readAllBytes(); + } catch (Exception e) { + logger.error("CustomContentCachingHttpRequestWrapper init error {}", e.getMessage()); + } finally { + this.cachedBody = tempBody; + } + } + + @Override + public ServletInputStream getInputStream() { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); + return new ServletInputStream() { + @Override + public int read() { + return byteArrayInputStream.read(); + } + + @Override + public boolean isFinished() { + return byteArrayInputStream.available() <= 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } + + public byte[] getCachedBody() { + return this.cachedBody; + } +} diff --git a/src/main/java/umc/th/juinjang/service/auth/JwtService.java b/src/main/java/umc/th/juinjang/service/auth/JwtService.java index 32c4b6d8..26cb3d16 100644 --- a/src/main/java/umc/th/juinjang/service/auth/JwtService.java +++ b/src/main/java/umc/th/juinjang/service/auth/JwtService.java @@ -1,15 +1,28 @@ package umc.th.juinjang.service.auth; +import static umc.th.juinjang.utils.LoggerProvider.registerUserId; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; import jakarta.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.util.Base64; +import java.util.Date; +import java.util.Map; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -22,12 +35,7 @@ import umc.th.juinjang.model.dto.auth.apple.AppleInfo; import umc.th.juinjang.repository.limjang.MemberRepository; import umc.th.juinjang.utils.ApplePublicKeyGenerator; - -import java.nio.charset.StandardCharsets; -import java.security.PublicKey; -import java.util.Base64; -import java.util.Date; -import java.util.Map; +import umc.th.juinjang.utils.LoggerProvider; @Service @RequiredArgsConstructor @@ -130,6 +138,7 @@ public Authentication getAuthentication(String token) { System.out.println(this.getMemberIdFromJwtToken(token)); UserDetails userDetails = userDetailService.loadUserByUsername(this.getMemberIdFromJwtToken(token).toString()); + registerUserId(String.valueOf(this.getMemberIdFromJwtToken(token))); return new UsernamePasswordAuthenticationToken(userDetails, token, userDetails.getAuthorities()); } diff --git a/src/main/java/umc/th/juinjang/service/record/RecordService.java b/src/main/java/umc/th/juinjang/service/record/RecordService.java index c8980469..61b93e35 100644 --- a/src/main/java/umc/th/juinjang/service/record/RecordService.java +++ b/src/main/java/umc/th/juinjang/service/record/RecordService.java @@ -25,6 +25,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; +import java.util.Objects; import java.util.UUID; @Service @@ -85,27 +86,19 @@ public RecordResponseDTO.RecordDTO uploadRecord(Member member, RecordRequestDTO. public String deleteRecord(Member member, Long recordId) { - //db에서 id 찾기 - - Record record = recordRepository.findById(recordId) .orElseThrow(() -> new ExceptionHandler(ErrorStatus.RECORD_NOT_FOUND)); - Limjang limjang = limjangRepository.findById(record.getLimjangId().getLimjangId()).orElseThrow(() - -> new ExceptionHandler(ErrorStatus.LIMJANG_NOTFOUND_ERROR)); - - if(limjang.getMemberId().getMemberId() != member.getMemberId()){ + if (!Objects.equals(record.getLimjangId().getMemberId().getMemberId(), member.getMemberId())) { throw new ExceptionHandler(ErrorStatus.RECORD_NOT_FOUND); } try{ String keyName = record.getRecordUrl().replace(defaultUrl+"/", ""); - log.info("Record deleted: {}", keyName); boolean isObjectExist = amazonS3Client.doesObjectExist(bucket, keyName); - log.info("Record isObjectExist: {}", isObjectExist); if (!isObjectExist) { throw new FileNotFoundException(); } else { @@ -114,15 +107,11 @@ public String deleteRecord(Member member, Long recordId) { //db에서 삭제 recordRepository.deleteById(recordId); - - - log.info("Record deleted: {}", recordId); // 추가 } } catch (Exception e) { // throw new Exception(e); } - log.info("Record deleted: {}", recordId); // 추가 return "삭제 성공했습니다."; } diff --git a/src/main/java/umc/th/juinjang/utils/LoggerProvider.java b/src/main/java/umc/th/juinjang/utils/LoggerProvider.java new file mode 100644 index 00000000..5e94b3ce --- /dev/null +++ b/src/main/java/umc/th/juinjang/utils/LoggerProvider.java @@ -0,0 +1,24 @@ +package umc.th.juinjang.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; + +public class LoggerProvider { // 커스텀 로거 생성 메서드 + + private static final String REQUEST_ID = "request_id"; + private static final String USER_ID = "user_id"; + + public static Logger getLogger(Class clazz) { + return LoggerFactory.getLogger(clazz); + } + + public static void registerRequestId(String requestId) { + MDC.put(REQUEST_ID, requestId); + } + + public static void registerUserId(String userId) { + MDC.put(USER_ID, userId); + } +} diff --git a/src/main/resources/logback-appender.xml b/src/main/resources/logback-appender.xml index ec604638..68ca4fdd 100644 --- a/src/main/resources/logback-appender.xml +++ b/src/main/resources/logback-appender.xml @@ -3,9 +3,13 @@ - - - + + + + + + + @@ -15,7 +19,7 @@ - ${CONSOLE_LOG_PATTERN} + ${API_CONSOLE_LOG_PATTERN} @@ -73,57 +77,16 @@ - - ${LOG_PATH}/api/info-${SERVER_INFO}-api.log - - INFO - ACCEPT - DENY - + + ${LOG_PATH}/api/${SERVER_INFO}-api.log - ${LOG_PATH}/api/info-${SERVER_INFO}-api-%d{yyyy-MM-dd}.%i.txt + ${LOG_PATH}/api/${SERVER_INFO}-api-%d{yyyy-MM-dd}.%i.txt 20MB 7 100MB - ${INFO_LOG_PATTERN} - - - - - ${LOG_PATH}/api/warn-${SERVER_INFO}-api.log - - WARN - ACCEPT - DENY - - - ${LOG_PATH}/api/warn-${SERVER_INFO}-api-%d{yyyy-MM-dd}.%i.txt - 20MB - 7 - 100MB - - - ${WARN_LOG_PATTERN} - - - - - ${LOG_PATH}/api/error-${SERVER_INFO}-api.log - - ERROR - ACCEPT - DENY - - - ${LOG_PATH}/api/error-${SERVER_INFO}-api-%d{yyyy-MM-dd}.%i.txt - 20MB - 7 - 100MB - - - ${ERROR_LOG_PATTERN} + ${API_FILE_LOG_PATTERN} \ No newline at end of file diff --git a/src/main/resources/logback-dev.xml b/src/main/resources/logback-dev.xml index 491dcc47..4185066a 100644 --- a/src/main/resources/logback-dev.xml +++ b/src/main/resources/logback-dev.xml @@ -12,12 +12,10 @@ - - - - - - + + + + diff --git a/src/main/resources/logback-local.xml b/src/main/resources/logback-local.xml index 0f97c2b1..4866531b 100644 --- a/src/main/resources/logback-local.xml +++ b/src/main/resources/logback-local.xml @@ -12,12 +12,12 @@ - - - - - + + + + + \ No newline at end of file diff --git a/src/main/resources/logback-prod.xml b/src/main/resources/logback-prod.xml index 51eb5ff9..7516e4e8 100644 --- a/src/main/resources/logback-prod.xml +++ b/src/main/resources/logback-prod.xml @@ -12,12 +12,10 @@ - - - - - - + + + +