From 74e229c5f74117d44b0ccb49e511296d372e35ce Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Mon, 30 Dec 2024 15:45:19 +0900 Subject: [PATCH 1/8] =?UTF-8?q?chore:=20=EB=A1=9C=EA=B7=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=EB=A5=BC=20git?= =?UTF-8?q?ignore=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -root/logs 디렉토리의 ignore 설정 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9f31018fe..638a9033a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ out/ ### env file ### *.env + +### log file ### +/logs/ From 7b8b9570f80000734cd84a93126c54d2fa3ccdb3 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Sun, 5 Jan 2025 20:40:07 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20loki=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그 저장, 시각화를 위한 loki 의존성 추가 --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index eb5c5d410..7f4292777 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,7 @@ dependencies { // Monitoring implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '3.3.3' implementation 'io.micrometer:micrometer-registry-prometheus' + implementation 'com.github.loki4j:loki-logback-appender:1.5.1' //elastic-search implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' From e2b7eb61aa82fdcde080b4f65770d993e4c58792 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Sun, 5 Jan 2025 20:43:49 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20logback-spring.xml=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - loki 관련 설정 추가 - 로그 패턴의 단일화 적용 - 파일 저장 appender의 설정 변경 --- src/main/resources/logback-spring.xml | 119 ++++++++++++++++++-------- 1 file changed, 81 insertions(+), 38 deletions(-) diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 63a0ef2c9..6fa1c0a9f 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,38 +1,81 @@ - - - - - %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n - UTF-8 - - - - - - 5000 - 0 - - - - ${LOG_DIR:-logs}/application.log - - ${LOG_DIR:-logs}/application.%d{yyyy-MM-dd}.log - 30 - - - %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n - UTF-8 - - - - - - - - - - - - - - + + + + + + + + + + + + + ${LOG_PATTERN} + + + + + + ${LOG_PATH}/${LOG_FILE_NAME}.log + + ${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log + 30 + 3GB + + + ${LOG_PATTERN} + + + + + + + + http://localhost:3100/loki/api/v1/push + 30000 + 15000 + 3 + 1000 + + + 100 + 10000 + 1048576 + + + + + true + true + true + true + JACKSON + @timestamp + false + true + ${LOG_PATTERN} + yyyy-MM-dd HH:mm:ss.SSS + + yyyy-MM-dd'T'HH:mm:ss.SSS'Z' + + + Asia/Seoul + + + + + + + + + + + + + + + + + \ No newline at end of file From 265a3e9bdb896d567d8302b6f8e72cf0513c7271 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Sun, 5 Jan 2025 22:39:58 +0900 Subject: [PATCH 4/8] =?UTF-8?q?chore:=20jacoco=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extractor 클래스의 테스트 커버리지 예외 조건 추가 --- build.gradle | 2 + .../somemore/global/aspect/LoggingAspect.java | 70 ------------------- 2 files changed, 2 insertions(+), 70 deletions(-) delete mode 100644 src/main/java/com/somemore/global/aspect/LoggingAspect.java diff --git a/build.gradle b/build.gradle index 7f4292777..e765995fd 100644 --- a/build.gradle +++ b/build.gradle @@ -117,6 +117,7 @@ def jacocoExcludePatterns = [ '**/*Application.class', '**/*Config*', '**/*Exception*', + '**/*Extractor*', '**/*Request*', '**/*Response*', '**/*Entity*', @@ -136,6 +137,7 @@ def jacocoExcludePatternsForVerify = [ '*.*Application*', '*.*Config*', '*.*Exception*', + '*.*Extractor*', '*.*Request*', '*.*Response*', '*.*Entity*', diff --git a/src/main/java/com/somemore/global/aspect/LoggingAspect.java b/src/main/java/com/somemore/global/aspect/LoggingAspect.java deleted file mode 100644 index ef27d996f..000000000 --- a/src/main/java/com/somemore/global/aspect/LoggingAspect.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.somemore.global.aspect; - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.springframework.stereotype.Component; - -@Slf4j -@Aspect -@Component -public class LoggingAspect { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Pointcut("execution(* com.somemore.domains.*.controller..*.*(..))") - private void pointCut(){} - - @Around("pointCut()") - public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { - - String methodName = joinPoint.getSignature().toShortString(); - String args = convertArgsToJson(joinPoint.getArgs()); - log.info("엔드포인트 호출: {} \n- 파라미터: {}", methodName, args); - - long startTime = System.currentTimeMillis(); - - try { - Object result = joinPoint.proceed(); - long elapsedTime = System.currentTimeMillis() - startTime; - - log.debug("성공: {} \n- 응답: {} \n- 실행 시간: {}ms", - methodName, - convertResultToJson(result), - elapsedTime); - - return result; - } catch (Exception e) { - long elapsedTime = System.currentTimeMillis() - startTime; - - log.warn("에러 발생: {} \n- 에러 타입: {} \n- 에러 메세지: {} \n- 실행 시간: {}ms", - methodName, - e.getClass().getSimpleName(), - e.getMessage(), - elapsedTime); - - throw e; - } - } - - private String convertArgsToJson(Object[] args) { - try { - return objectMapper.writeValueAsString(args != null ? args : new Object[]{}); - } catch (Exception e) { - log.warn("파라미터 변환 실패", e); - return "[파라미터 변환 실패]"; - } - } - - private String convertResultToJson(Object result) { - try { - return objectMapper.writeValueAsString(result != null ? result : "null"); - } catch (Exception e) { - log.warn("응답 변환 실패", e); - return "[응답 변환 실패]"; - } - } -} From bed7f6938b47e92b781103dc26b9dbe03436a49d Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Sun, 5 Jan 2025 22:41:18 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor:=20logging=20AOP=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그 기록 세분화 - 민감한 데이터 마스킹 처리 - 책임에 따른 클래스 분리 - 마스킹 테스트및 검증 완 --- .../global/aspect/log/LoggingAspect.java | 90 ++++++++++++++++++ .../log/extractor/ParameterExtractor.java | 71 +++++++++++++++ .../log/extractor/RequestExtractor.java | 18 ++++ .../log/extractor/ResponseExtractor.java | 54 +++++++++++ .../aspect/log/utils/SensitiveDataMasker.java | 72 +++++++++++++++ .../aspect/log/SensitiveDataMaskerTest.java | 91 +++++++++++++++++++ 6 files changed, 396 insertions(+) create mode 100644 src/main/java/com/somemore/global/aspect/log/LoggingAspect.java create mode 100644 src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java create mode 100644 src/main/java/com/somemore/global/aspect/log/extractor/RequestExtractor.java create mode 100644 src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java create mode 100644 src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java create mode 100644 src/test/java/com/somemore/global/aspect/log/SensitiveDataMaskerTest.java diff --git a/src/main/java/com/somemore/global/aspect/log/LoggingAspect.java b/src/main/java/com/somemore/global/aspect/log/LoggingAspect.java new file mode 100644 index 000000000..95f4fed07 --- /dev/null +++ b/src/main/java/com/somemore/global/aspect/log/LoggingAspect.java @@ -0,0 +1,90 @@ +package com.somemore.global.aspect.log; + +import com.somemore.global.aspect.log.extractor.ParameterExtractor; +import com.somemore.global.aspect.log.extractor.RequestExtractor; +import com.somemore.global.aspect.log.extractor.ResponseExtractor; +import com.somemore.global.common.response.LoggedResponse; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.MDC; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + + +import java.util.UUID; + +@RequiredArgsConstructor +@Slf4j +@Aspect +@Component +public class LoggingAspect { + + private final RequestExtractor requestExtractor; + private final ResponseExtractor responseExtractor; + private final ParameterExtractor parameterExtractor; + + @Pointcut("execution(* com.somemore.domains.*.controller..*.*(..))") + private void controllerPointCut() {} + + @Around("controllerPointCut()") + public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { + String requestId = UUID.randomUUID().toString(); + MDC.put("requestId", requestId); + + try { + return doLogAround(joinPoint); + } finally { + MDC.remove("requestId"); + } + } + + private Object doLogAround(ProceedingJoinPoint joinPoint) throws Throwable { + String methodName = joinPoint.getSignature().toShortString(); + HttpServletRequest request = requestExtractor.getCurrentRequest(); + + MDC.put("method", request.getMethod()); + MDC.put("uri", request.getRequestURI()); + + String params = parameterExtractor.extractParameters(joinPoint); + log.info("엔드포인트 호출: {} \n- URI: {} \n- Method: {} \n- 파라미터: {}", + methodName, + request.getRequestURI(), + request.getMethod(), + params); + + long startTime = System.currentTimeMillis(); + + try { + Object result = joinPoint.proceed(); + long elapsedTime = System.currentTimeMillis() - startTime; + + LoggedResponse loggedResponse = responseExtractor.extractResponse(result); + log.info("호출 성공: {} \n- 응답 코드: {} \n- 응답 값: {} \n- 실행 시간: {}ms", + methodName, + loggedResponse.getStatusCode(), + loggedResponse.getBody(), + elapsedTime); + + return result; + } catch (Exception e) { + long elapsedTime = System.currentTimeMillis() - startTime; + HttpStatus status = responseExtractor.extractExceptionStatus(e); + + log.warn("예외 발생: {} \n- 예외 코드: {} \n- 예외 타입: {} \n- 예외 메세지: {} \n- 실행 시간: {}ms", + methodName, + status, + e.getClass().getSimpleName(), + e.getMessage(), + elapsedTime); + + throw e; + } finally { + MDC.clear(); + } + } +} diff --git a/src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java b/src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java new file mode 100644 index 000000000..8d0f482ed --- /dev/null +++ b/src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java @@ -0,0 +1,71 @@ +package com.somemore.global.aspect.log.extractor; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.somemore.global.aspect.log.utils.SensitiveDataMasker; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +import java.lang.reflect.Parameter; +import java.util.LinkedHashMap; +import java.util.Map; + +@Slf4j +@Component +public class ParameterExtractor { + + private final ObjectMapper objectMapper; + private final SensitiveDataMasker sensitiveDataMasker; + + public ParameterExtractor(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + this.sensitiveDataMasker = new SensitiveDataMasker(); + } + + public String extractParameters(ProceedingJoinPoint joinPoint) { + try { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Parameter[] parameters = signature.getMethod().getParameters(); + Object[] args = joinPoint.getArgs(); + + if (parameters.length == 0) { + return "{}"; + } + + Map paramMap = new LinkedHashMap<>(); + for (int i = 0; i < parameters.length; i++) { + if (args[i] != null) { + addParameter(paramMap, parameters[i], args[i]); + } + } + + return objectMapper.writeValueAsString(paramMap); + } catch (Exception e) { + log.warn("파라미터 변환 실패: {}", e.getMessage()); + return "{}"; + } + } + + private void addParameter(Map paramMap, Parameter parameter, Object value) throws JsonProcessingException { + String paramName = extractParamName(parameter); + paramMap.put(paramName, sensitiveDataMasker.maskSensitiveData(paramName, value, objectMapper)); + } + + private String extractParamName(Parameter parameter) { + PathVariable pathVariable = parameter.getAnnotation(PathVariable.class); + if (pathVariable != null && !pathVariable.value().isEmpty()) { + return pathVariable.value(); + } + + RequestParam requestParam = parameter.getAnnotation(RequestParam.class); + if (requestParam != null && !requestParam.value().isEmpty()) { + return requestParam.value(); + } + + return parameter.getName(); + } +} diff --git a/src/main/java/com/somemore/global/aspect/log/extractor/RequestExtractor.java b/src/main/java/com/somemore/global/aspect/log/extractor/RequestExtractor.java new file mode 100644 index 000000000..50401b9a8 --- /dev/null +++ b/src/main/java/com/somemore/global/aspect/log/extractor/RequestExtractor.java @@ -0,0 +1,18 @@ +package com.somemore.global.aspect.log.extractor; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@Component +public class RequestExtractor { + + public HttpServletRequest getCurrentRequest() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + throw new IllegalStateException("요청을 찾을수 없습니다."); + } + return attributes.getRequest(); + } +} diff --git a/src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java b/src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java new file mode 100644 index 000000000..7dddf3259 --- /dev/null +++ b/src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java @@ -0,0 +1,54 @@ +package com.somemore.global.aspect.log.extractor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.somemore.global.common.response.LoggedResponse; +import com.somemore.global.exception.DuplicateException; +import com.somemore.global.exception.ImageUploadException; +import com.somemore.global.exception.BadRequestException; +import com.somemore.global.exception.NoSuchElementException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.MethodArgumentNotValidException; + +@Slf4j +@Component +public class ResponseExtractor { + + private final ObjectMapper objectMapper; + + public ResponseExtractor(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public LoggedResponse extractResponse(Object result) { + try { + if (result == null) { + return new LoggedResponse(HttpStatus.OK, "null"); + } + + if (result instanceof ResponseEntity responseEntity) { + String body = objectMapper.writeValueAsString(responseEntity.getBody()); + return new LoggedResponse(responseEntity.getStatusCode(), body); + } + + return new LoggedResponse(HttpStatus.OK, objectMapper.writeValueAsString(result)); + } catch (Exception e) { + log.warn("응답 변환 실패: {}", e.getMessage()); + return new LoggedResponse(HttpStatus.OK, "[응답 변환 실패]"); + } + } + + public HttpStatus extractExceptionStatus(Exception e) { + if (e instanceof BadRequestException || + e instanceof ImageUploadException || + e instanceof DuplicateException || + e instanceof MethodArgumentNotValidException) { + return HttpStatus.BAD_REQUEST; + } else if (e instanceof NoSuchElementException) { + return HttpStatus.NOT_FOUND; + } + return HttpStatus.INTERNAL_SERVER_ERROR; + } +} diff --git a/src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java b/src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java new file mode 100644 index 000000000..8ce729cea --- /dev/null +++ b/src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java @@ -0,0 +1,72 @@ +package com.somemore.global.aspect.log.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.*; + +@RequiredArgsConstructor +@Slf4j +@Component +public class SensitiveDataMasker { + + private final Set sensitiveFields = new HashSet<>(Arrays.asList( + "password", "token", "secret", "credential", "authorization", + "accessToken", "refreshToken" + )); + + public Object maskSensitiveData(String fieldName, Object value, ObjectMapper objectMapper) throws JsonProcessingException { + if (isSensitiveField(fieldName)) { + return "********"; + } else if (value instanceof Map) { + return maskSensitiveDataInMap((Map) value); + } else if (isComplexObject(value)) { + String json = objectMapper.writeValueAsString(value); + json = maskSensitiveDataInJson(json, objectMapper); + return objectMapper.readValue(json, Object.class); + } + return value; + } + + private boolean isSensitiveField(String fieldName) { + String lowercaseFieldName = fieldName.toLowerCase(); + return sensitiveFields.stream() + .anyMatch(sensitive -> lowercaseFieldName.contains(sensitive.toLowerCase())); + } + + private Map maskSensitiveDataInMap(Map map) { + Map maskedMap = new LinkedHashMap<>(); + + map.forEach((key, value) -> { + String keyStr = String.valueOf(key); + if (isSensitiveField(keyStr)) { + maskedMap.put(keyStr, "********"); + } else if (value instanceof Map) { + maskedMap.put(keyStr, maskSensitiveDataInMap((Map) value)); + } else { + maskedMap.put(keyStr, value); + } + }); + + return maskedMap; + } + + private String maskSensitiveDataInJson(String json, ObjectMapper objectMapper) { + try { + Map jsonMap = objectMapper.readValue(json, Map.class); + Map maskedMap = maskSensitiveDataInMap(jsonMap); + return objectMapper.writeValueAsString(maskedMap); + } catch (Exception e) { + log.warn("JSON 마스킹 처리 실패: {}", e.getMessage()); + return json; + } + } + + private boolean isComplexObject(Object value) { + return !(value instanceof String || value instanceof Number || + value instanceof Boolean || value instanceof Date); + } +} diff --git a/src/test/java/com/somemore/global/aspect/log/SensitiveDataMaskerTest.java b/src/test/java/com/somemore/global/aspect/log/SensitiveDataMaskerTest.java new file mode 100644 index 000000000..059c366d2 --- /dev/null +++ b/src/test/java/com/somemore/global/aspect/log/SensitiveDataMaskerTest.java @@ -0,0 +1,91 @@ +package com.somemore.global.aspect.log; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.somemore.global.aspect.log.utils.SensitiveDataMasker; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SensitiveDataMaskerTest { + + private SensitiveDataMasker sensitiveDataMasker; + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + sensitiveDataMasker = new SensitiveDataMasker(); + } + + @DisplayName("민감한 필드 이름이 주어지면 데이터를 마스킹할 때 마스킹된 값을 반환해야 한다.") + @Test + void givenSensitiveField_whenMaskSensitiveData_thenMasked() throws JsonProcessingException { + // Given + String fieldName = "password"; + String value = "mySecretPassword"; + + // When + Object result = sensitiveDataMasker.maskSensitiveData(fieldName, value, objectMapper); + + // Then + assertEquals("********", result); + } + + @DisplayName("민감한 데이터가 포함된 Map에서 민감한 필드는 마스킹되어야 한다.") + @Test + void givenMapWithSensitiveData_whenMaskSensitiveData_thenMasked() throws JsonProcessingException { + // Given + Map data = new HashMap<>(); + data.put("password", "123456"); + data.put("username", "user123"); + + // When + Object result = sensitiveDataMasker.maskSensitiveData("testField", data, objectMapper); + + // Then + assertEquals("********", ((Map) result).get("password")); + assertEquals("user123", ((Map) result).get("username")); + } + + @DisplayName("민감한 데이터를 포함하는 객체는 민감한 필드를 마스킹 해야한다.") + @Test + void givenComplexObject_whenMaskSensitiveData_thenMasked() throws JsonProcessingException { + // Given + Map nestedData = new HashMap<>(); + nestedData.put("token", "abcd1234"); + nestedData.put("email", "test@example.com"); + + Map data = new HashMap<>(); + data.put("user", nestedData); + data.put("username", "user123"); + + // When + Object result = sensitiveDataMasker.maskSensitiveData("testField", data, objectMapper); + + // Then + Map maskedUser = (Map) ((Map) result).get("user"); + assertEquals("********", maskedUser.get("token")); + assertEquals("test@example.com", maskedUser.get("email")); + assertEquals("user123", ((Map) result).get("username")); + } + + @DisplayName("일반 데이터는 마스킹되지 않아야 한다.") + @Test + void givenPrimitiveValue_whenMaskSensitiveData_thenUnchanged() throws JsonProcessingException { + // Given + int intValue = 12345; + boolean boolValue = true; + String textValue = "text"; + + // When & Then + assertEquals(intValue, sensitiveDataMasker.maskSensitiveData("field", intValue, objectMapper)); + assertEquals(boolValue, sensitiveDataMasker.maskSensitiveData("field", boolValue, objectMapper)); + assertEquals(textValue, sensitiveDataMasker.maskSensitiveData("field", textValue, objectMapper)); + } +} From 7fa86a996e4883966601dedd643f45272c0e707e Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Sun, 5 Jan 2025 22:41:55 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20log=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그 응답에 대한 Response 객체 생성 --- .../global/common/response/LoggedResponse.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/com/somemore/global/common/response/LoggedResponse.java diff --git a/src/main/java/com/somemore/global/common/response/LoggedResponse.java b/src/main/java/com/somemore/global/common/response/LoggedResponse.java new file mode 100644 index 000000000..9d33a1819 --- /dev/null +++ b/src/main/java/com/somemore/global/common/response/LoggedResponse.java @@ -0,0 +1,15 @@ +package com.somemore.global.common.response; + +import lombok.Getter; +import org.springframework.http.HttpStatusCode; + +@Getter +public class LoggedResponse { + private final HttpStatusCode statusCode; + private final String body; + + public LoggedResponse(HttpStatusCode statusCode, String body) { + this.statusCode = statusCode; + this.body = body; + } +} From 2cb9f3b24d95b578c8ed0105502038cf702eed22 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Sun, 5 Jan 2025 23:03:52 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor:=20=EB=AA=85=EC=8B=9C=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=EB=B0=8F=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ParameterExtractor의 생성자 코드 제거및 RequiredArgs 어노테이션 추가 - ResponseExtractor의 생성자 코드 제거및 RequiredArgs 어노테이션 추가 - SensitiveDataMasker의 불필요한 어노테이션 제거 --- .../global/aspect/log/extractor/ParameterExtractor.java | 7 ++----- .../global/aspect/log/extractor/ResponseExtractor.java | 6 ++---- .../global/aspect/log/utils/SensitiveDataMasker.java | 2 -- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java b/src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java index 8d0f482ed..9c1e0cc7c 100644 --- a/src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java +++ b/src/main/java/com/somemore/global/aspect/log/extractor/ParameterExtractor.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.somemore.global.aspect.log.utils.SensitiveDataMasker; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; @@ -14,6 +15,7 @@ import java.util.LinkedHashMap; import java.util.Map; +@RequiredArgsConstructor @Slf4j @Component public class ParameterExtractor { @@ -21,11 +23,6 @@ public class ParameterExtractor { private final ObjectMapper objectMapper; private final SensitiveDataMasker sensitiveDataMasker; - public ParameterExtractor(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - this.sensitiveDataMasker = new SensitiveDataMasker(); - } - public String extractParameters(ProceedingJoinPoint joinPoint) { try { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); diff --git a/src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java b/src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java index 7dddf3259..3433ffcba 100644 --- a/src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java +++ b/src/main/java/com/somemore/global/aspect/log/extractor/ResponseExtractor.java @@ -6,22 +6,20 @@ import com.somemore.global.exception.ImageUploadException; import com.somemore.global.exception.BadRequestException; import com.somemore.global.exception.NoSuchElementException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.bind.MethodArgumentNotValidException; +@RequiredArgsConstructor @Slf4j @Component public class ResponseExtractor { private final ObjectMapper objectMapper; - public ResponseExtractor(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } - public LoggedResponse extractResponse(Object result) { try { if (result == null) { diff --git a/src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java b/src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java index 8ce729cea..6addf11fb 100644 --- a/src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java +++ b/src/main/java/com/somemore/global/aspect/log/utils/SensitiveDataMasker.java @@ -2,13 +2,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.*; -@RequiredArgsConstructor @Slf4j @Component public class SensitiveDataMasker { From fd484d22e3db05517549c048cf5269b0e7054fdf Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Sun, 5 Jan 2025 23:57:25 +0900 Subject: [PATCH 8/8] =?UTF-8?q?chore:=20=EC=9E=A0=EC=9E=AC=EC=A0=81=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=98=88=EB=B0=A9=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EA=B0=9C=ED=96=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - logback-spring.xml 파일의 마지막 라인에 개행 추가 --- src/main/resources/logback-spring.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 6fa1c0a9f..bb55d6c1c 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -78,4 +78,4 @@ - \ No newline at end of file +