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
+