From 564deb76a2048f9aaa0503bd0cc9e70141119d8f Mon Sep 17 00:00:00 2001 From: Lee SeungHeon <51286325+dev-Crayon@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:14:35 +0900 Subject: [PATCH] =?UTF-8?q?Spring=20AOP=EB=A5=BC=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20request=20&=20response=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20(#68)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Feat]: Spring AOP 의존성 추가 및 LoggingConfig 설정 Spring AOP 의존성 추가 LoggingConfig 설정 Related to: #67 * [Feat]: request & response 로깅 적용 redis repository 범위 지정 request & response 로깅 Related to: #67 --- build.gradle | 4 + .../src/main/resources/logback-spring.xml | 68 +++++++++++++++ .../diary/constant/SensitiveKeyword.java | 6 ++ .../fourcut/diary/logging/LoggingConfig.java | 9 ++ .../logging/RequestResponseLoggingAspect.java | 84 +++++++++++++++++++ .../com/fourcut/diary/util/StringUtil.java | 13 +++ .../com/fourcut/diary/config/RedisConfig.java | 2 +- 7 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 module-api/src/main/resources/logback-spring.xml create mode 100644 module-common/src/main/java/com/fourcut/diary/constant/SensitiveKeyword.java create mode 100644 module-common/src/main/java/com/fourcut/diary/logging/LoggingConfig.java create mode 100644 module-common/src/main/java/com/fourcut/diary/logging/RequestResponseLoggingAspect.java create mode 100644 module-common/src/main/java/com/fourcut/diary/util/StringUtil.java diff --git a/build.gradle b/build.gradle index 2ab5985..9ea6619 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,10 @@ subprojects { dependencies { // 모든 하위 모듈에 추가 될 의존성 목록입니다. implementation 'org.springframework.boot:spring-boot-starter-web' + // log + implementation 'org.springframework.boot:spring-boot-starter-logging' + implementation 'org.springframework.boot:spring-boot-starter-aop' + // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'com.h2database:h2' diff --git a/module-api/src/main/resources/logback-spring.xml b/module-api/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..35d81f8 --- /dev/null +++ b/module-api/src/main/resources/logback-spring.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n%throwable + + + + + + ${LOG_PATH}/request.log + + ${LOG_PATH}/request.%d{yyyy-MM}.log + 12 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [TRACE_ID:%X{traceId}] %msg%n + + + + + + ${LOG_PATH}/response.log + + ${LOG_PATH}/response.%d{yyyy-MM}.log + 12 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [TRACE_ID:%X{traceId}] %msg%n + + + + + + ${LOG_PATH}/error.log + + ${LOG_PATH}/error.%d{yyyy-MM}.log + 12 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [TRACE_ID:%X{traceId}] %msg%n + + + + + + + + + + + + + + + + + + + + + diff --git a/module-common/src/main/java/com/fourcut/diary/constant/SensitiveKeyword.java b/module-common/src/main/java/com/fourcut/diary/constant/SensitiveKeyword.java new file mode 100644 index 0000000..7cac7bf --- /dev/null +++ b/module-common/src/main/java/com/fourcut/diary/constant/SensitiveKeyword.java @@ -0,0 +1,6 @@ +package com.fourcut.diary.constant; + +public class SensitiveKeyword { + + public static final String[] SENSITIVE_KEYWORDS = {"authorizationCode", "fcmToken", "accessToken", "refreshToken", "presignedUrl"}; +} diff --git a/module-common/src/main/java/com/fourcut/diary/logging/LoggingConfig.java b/module-common/src/main/java/com/fourcut/diary/logging/LoggingConfig.java new file mode 100644 index 0000000..6938baf --- /dev/null +++ b/module-common/src/main/java/com/fourcut/diary/logging/LoggingConfig.java @@ -0,0 +1,9 @@ +package com.fourcut.diary.logging; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@Configuration +@EnableAspectJAutoProxy +public class LoggingConfig { +} diff --git a/module-common/src/main/java/com/fourcut/diary/logging/RequestResponseLoggingAspect.java b/module-common/src/main/java/com/fourcut/diary/logging/RequestResponseLoggingAspect.java new file mode 100644 index 0000000..12e7265 --- /dev/null +++ b/module-common/src/main/java/com/fourcut/diary/logging/RequestResponseLoggingAspect.java @@ -0,0 +1,84 @@ +package com.fourcut.diary.logging; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fourcut.diary.util.StringUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import java.util.stream.Collectors; + +@Aspect +@Component +@Slf4j +public class RequestResponseLoggingAspect { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private static final Logger requestLogger = LoggerFactory.getLogger("com.fourcut.diary.logging.request"); + private static final Logger responseLogger = LoggerFactory.getLogger("com.fourcut.diary.logging.response"); + private static final Logger errorLogger = LoggerFactory.getLogger("com.fourcut.diary.logging.error"); + + @Around("execution(* com.fourcut.diary..*Controller.*(..))") + public Object logRequestAndResponse(ProceedingJoinPoint joinPoint) throws Throwable { + String traceId = UUID.randomUUID().toString(); + MDC.put("traceId", traceId); + + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + + String method = request.getMethod(); + String uri = request.getRequestURI(); + String queryString = request.getQueryString(); + String clientIP = request.getRemoteAddr(); + + String headers = Collections.list(request.getHeaderNames()).stream() + .map(header -> header + "=" + request.getHeader(header)) + .collect(Collectors.joining(", ")); + + String params = Arrays.stream(joinPoint.getArgs()) + .map(arg -> { + try { + return StringUtil.maskSensitiveFields(objectMapper.writeValueAsString(arg)); + } catch (JsonProcessingException e) { + return arg.toString(); + } + }) + .collect(Collectors.joining(", ")); + + long start = System.currentTimeMillis(); + + try { + requestLogger.info("[REQUEST] {} {}{} | IP: {} | Headers: {} | Params: {}", + method, uri, queryString != null ? "?" + queryString : "", clientIP, headers, params); + + Object result = joinPoint.proceed(); + + String responseJson = objectMapper.writeValueAsString(result); + responseJson = StringUtil.maskSensitiveFields(responseJson); + long duration = System.currentTimeMillis() - start; + + responseLogger.info("[RESPONSE] {} {} | IP: {} | Response: {} | Time: {}ms", + method, uri, clientIP, responseJson, duration); + + return result; + } catch (Throwable e) { + long duration = System.currentTimeMillis() - start; + errorLogger.error("[ERROR] {} {} | IP: {} | Error: {} | Time: {}ms", + method, uri, clientIP, e.getMessage(), duration); + throw e; + } finally { + MDC.clear(); + } + } +} diff --git a/module-common/src/main/java/com/fourcut/diary/util/StringUtil.java b/module-common/src/main/java/com/fourcut/diary/util/StringUtil.java new file mode 100644 index 0000000..19bc691 --- /dev/null +++ b/module-common/src/main/java/com/fourcut/diary/util/StringUtil.java @@ -0,0 +1,13 @@ +package com.fourcut.diary.util; + +import com.fourcut.diary.constant.SensitiveKeyword; + +public class StringUtil { + + public static String maskSensitiveFields(String json) { + for (String key : SensitiveKeyword.SENSITIVE_KEYWORDS) { + json = json.replaceAll("(\"" + key + "\"\\s*:\\s*\")[^\"]*\"", "$1****\""); + } + return json; + } +} diff --git a/module-domain/src/main/java/com/fourcut/diary/config/RedisConfig.java b/module-domain/src/main/java/com/fourcut/diary/config/RedisConfig.java index 0cb769a..44e2b45 100644 --- a/module-domain/src/main/java/com/fourcut/diary/config/RedisConfig.java +++ b/module-domain/src/main/java/com/fourcut/diary/config/RedisConfig.java @@ -11,7 +11,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration -@EnableRedisRepositories +@EnableRedisRepositories(basePackages = "com.fourcut.diary.auth.repository") public class RedisConfig { @Value("${spring.data.redis.host}")