From a16f2dfc260f123dccdb0581e85bfcaa18f4ed9e Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Sat, 15 Feb 2025 15:46:41 +0900 Subject: [PATCH 01/15] =?UTF-8?q?[#310]=20chore(build.gradle):=20log4j2?= =?UTF-8?q?=EC=99=80=20aop=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build.gradle b/build.gradle index 5e25d2b..08b4083 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,11 @@ configurations { // Configure libraries related to QueryDSL to be required only at compile-time and add QueryDSL configuration to the compile classpath. querydsl.extendsFrom compileClasspath + + // Enable Log4j2 except for the Spring Boot Default Logging Framework (Logback) + configureEach { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + } } repositories { @@ -94,6 +99,12 @@ dependencies { // Prometheus implementation 'io.micrometer:micrometer-registry-prometheus' + + // log4j2 + implementation 'org.springframework.boot:spring-boot-starter-log4j2' + + // AOP + implementation 'org.springframework.boot:spring-boot-starter-aop' } jar { From 1333d65d724c4d69eaf68b2d4a8be0edc9fbdae1 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Sat, 15 Feb 2025 18:26:49 +0900 Subject: [PATCH 02/15] =?UTF-8?q?[#310]=20fix(TokenRepository):=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/beat/global/auth/jwt/dao/TokenRepository.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/beat/global/auth/jwt/dao/TokenRepository.java b/src/main/java/com/beat/global/auth/jwt/dao/TokenRepository.java index e348fd6..d49ef06 100644 --- a/src/main/java/com/beat/global/auth/jwt/dao/TokenRepository.java +++ b/src/main/java/com/beat/global/auth/jwt/dao/TokenRepository.java @@ -9,6 +9,4 @@ public interface TokenRepository extends CrudRepository { Optional findByRefreshToken(final String refreshToken); - - Optional findById(final Long id); } From 4e3fc67b92d6cd2dcb46422648fde41b91f645cb Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Sat, 15 Feb 2025 19:22:02 +0900 Subject: [PATCH 03/15] =?UTF-8?q?[#310]=20chore(Token):=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=B4=20RedisHash=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/beat/global/auth/redis/Token.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/beat/global/auth/redis/Token.java b/src/main/java/com/beat/global/auth/redis/Token.java index 05ab249..c49a3ac 100644 --- a/src/main/java/com/beat/global/auth/redis/Token.java +++ b/src/main/java/com/beat/global/auth/redis/Token.java @@ -8,9 +8,9 @@ import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.index.Indexed; -@RedisHash(value = "refreshToken", timeToLive = 1209600) @Getter @Builder +@RedisHash(value = "refreshToken", timeToLive = 1209600) public class Token { @Id From 28c2261031ca8674b7df8f86adbc92208c109e1b Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 16:50:01 +0900 Subject: [PATCH 04/15] =?UTF-8?q?[#310]=20feat(log4j2-spring.xml):=20log4j?= =?UTF-8?q?2=20=EB=8F=84=EC=9E=85=20=EB=B0=8F=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=84=20=EB=A1=9C=EA=B9=85=20=EC=84=A4=EC=A0=95=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/log4j2-spring.xml | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/resources/log4j2-spring.xml diff --git a/src/main/resources/log4j2-spring.xml b/src/main/resources/log4j2-spring.xml new file mode 100644 index 0000000..6ab7d80 --- /dev/null +++ b/src/main/resources/log4j2-spring.xml @@ -0,0 +1,43 @@ + + + + + + %style{%d{yyyy-MM-dd HH:mm:ss,SSS}}{blue} %highlight{[%-5p]}{FATAL=bg_red, ERROR=red, WARN=yellow, INFO=green, DEBUG=bright_blue, TRACE=cyan} [%t] %style{[%c{1}.%M():%L]}{BRIGHT_BLACK} - %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8e43b66f522f828392aa4e4f75cb5b24c1d991ca Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 16:51:01 +0900 Subject: [PATCH 05/15] =?UTF-8?q?[#310]=20chore(build.gradle):=20common-lo?= =?UTF-8?q?gging=20=EB=AA=A8=EB=93=88=20=EC=A0=9C=EC=99=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 08b4083..41b4fc2 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,7 @@ configurations { // Enable Log4j2 except for the Spring Boot Default Logging Framework (Logback) configureEach { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + exclude group: 'commons-logging', module: 'commons-logging' } } From 295e71425891983f0e3de5c326625927a7d570e3 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 16:52:30 +0900 Subject: [PATCH 06/15] =?UTF-8?q?[#310]=20chore(application.yml):=20xml=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EA=B2=BD=EB=A1=9C=EB=AA=85=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 2 ++ src/main/resources/application-prod.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index ac363f2..b7fe1bc 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -95,6 +95,8 @@ cloud: logging: level: root: info + config: classpath:log4j2-spring.xml + cors: allowed-origins: ${DEV_ALLOWED_ORIGINS} diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 177cfef..403f2df 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -95,6 +95,7 @@ cloud: logging: level: root: info + config: classpath:log4j2-spring.xml cors: allowed-origins: ${PROD_ALLOWED_ORIGINS} From bfe2a52be93a35c4bcda23ec8be3c875d9b4ec43 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 17:00:14 +0900 Subject: [PATCH 07/15] =?UTF-8?q?[#310]=20feat(RepositoryConfig):=20JPA=20?= =?UTF-8?q?=EB=B0=8F=20Redis=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=B6=84=EB=A6=AC=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스프링에서 JPA와 Redis 레포지토리 간 충돌 문제 해결을 위한 설정 추가 --- .../common/config/RepositoryConfig.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/beat/global/common/config/RepositoryConfig.java diff --git a/src/main/java/com/beat/global/common/config/RepositoryConfig.java b/src/main/java/com/beat/global/common/config/RepositoryConfig.java new file mode 100644 index 0000000..29ffba6 --- /dev/null +++ b/src/main/java/com/beat/global/common/config/RepositoryConfig.java @@ -0,0 +1,21 @@ +package com.beat.global.common.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; + +@Configuration +@EnableJpaRepositories( + basePackages = "com.beat", + excludeFilters = @ComponentScan.Filter( + type = FilterType.REGEX, + pattern = "com\\.beat\\.global\\.auth\\.jwt\\.dao\\..*" + ) +) +@EnableRedisRepositories( + basePackages = "com.beat.global.auth.jwt.dao" +) +public class RepositoryConfig { +} From 00af3c9977c4565de69c2d0e48b2d1c1acb0e2e2 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 17:04:05 +0900 Subject: [PATCH 08/15] =?UTF-8?q?[#310]=20feat(ControllerLoggingAspect):?= =?UTF-8?q?=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=9A=94=EC=B2=AD/?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=EC=9D=84=20=EC=9C=84=ED=95=9C=20AOP=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컨트롤러의 요청 정보를 로깅하는 @Before 추가 - 컨트롤러의 정상 응답을 로깅하는 @AfterReturning 추가 - 컨트롤러에서 예외 발생 시 로깅하는 @AfterThrowing 추가 - HTTP 요청의 파라미터를 JSON 형태로 변환하여 로깅 - Order(2) 적용하여 트랜잭션 관리(AOP)보다 후순위로 실행되도록 설정 --- .../common/aop/ControllerLoggingAspect.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java diff --git a/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java b/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java new file mode 100644 index 0000000..4c749d1 --- /dev/null +++ b/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java @@ -0,0 +1,104 @@ +package com.beat.global.common.aop; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import net.minidev.json.JSONObject; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Aspect +@Order(2) +@Component +public class ControllerLoggingAspect { + + private static final String REQUEST_URI = "requestURI"; + private static final String CONTROLLER = "controller"; + private static final String METHOD = "method"; + private static final String HTTP_METHOD = "httpMethod"; + private static final String LOG_TIME = "logTime"; + private static final String PARAMS = "params"; + + /** Controller 요청 로깅 */ + @Before("com.beat.global.common.aop.Pointcuts.allController()") + public void logControllerRequest(JoinPoint joinPoint) { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) return; + + HttpServletRequest request = attributes.getRequest(); + Map logInfo = new HashMap<>(); + + logInfo.put(CONTROLLER, joinPoint.getSignature().getDeclaringType().getSimpleName()); + logInfo.put(METHOD, joinPoint.getSignature().getName()); + logInfo.put(PARAMS, getParams(request)); + logInfo.put(LOG_TIME, System.currentTimeMillis()); + logInfo.put(HTTP_METHOD, request.getMethod()); + + try { + logInfo.put(REQUEST_URI, URLDecoder.decode(request.getRequestURI(), StandardCharsets.UTF_8)); + } catch (Exception e) { + logInfo.put(REQUEST_URI, request.getRequestURI()); + log.error("[로깅 에러] URL 디코딩 실패", e); + } + + log.info("[HTTP {}] {} | {}.{}() | Params: {}", + logInfo.get(HTTP_METHOD), logInfo.get(REQUEST_URI), + logInfo.get(CONTROLLER), logInfo.get(METHOD), + logInfo.get(PARAMS)); + } + + /** Controller 정상 반환 로깅 */ + @AfterReturning(value = "com.beat.global.common.aop.Pointcuts.allController()", returning = "result") + public void logControllerResponse(JoinPoint joinPoint, Object result) { + log.info("[Controller 정상 반환] {}.{}() | 반환 값: {}", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + result); + } + + /** Controller 예외 발생 시 로깅 */ + @AfterThrowing(value = "com.beat.global.common.aop.Pointcuts.allController()", throwing = "ex") + public void logControllerException(JoinPoint joinPoint, Exception ex) { + log.error("[Controller 예외 발생] {}.{}() | 예외 메시지: {}", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + ex.getMessage(), ex); + } + + /** HTTP 요청 파라미터를 JSON 형태로 변환 */ + private static JSONObject getParams(HttpServletRequest request) { + JSONObject jsonObject = new JSONObject(); + Enumeration params = request.getParameterNames(); + + while (params.hasMoreElements()) { + String param = params.nextElement(); + String replacedParam = param.replace(".", "-"); + String[] values = request.getParameterValues(param); + + if (values == null || values.length == 0) { + jsonObject.put(replacedParam, ""); // 값이 없을 경우 빈 문자열 저장 + } else if (values.length > 1) { + jsonObject.put(replacedParam, values); // 여러 값이 있는 경우 배열로 저장 + } else { + jsonObject.put(replacedParam, values[0]); // 단일 값이면 문자열로 저장 + } + } + return jsonObject; + } +} From d3889a95c34ac1e416c5096b5c99e0195dc6a1f2 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 17:08:24 +0900 Subject: [PATCH 09/15] =?UTF-8?q?[#310]=20feat(ServiceLoggingAspect):=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B3=84=EC=B8=B5=EC=9D=98=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=8B=A4=ED=96=89=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EB=A1=9C=EA=B9=85=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서비스 메서드 실행 전 로깅 (@Before) - 서비스 메서드 정상 반환 시 로깅 (@AfterReturning) - 서비스 메서드 실행 후 로깅 (@After) - 예외 발생 시 로깅 (@AfterThrowing) - 실행 시간 측정을 위한 @Around 로직 추가 - Order(3) 적용하여 트랜잭션 로깅 및 컨트롤러 로깅 이후에 실행되도록 설정 --- .../common/aop/ServiceLoggingAspect.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java diff --git a/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java b/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java new file mode 100644 index 0000000..a2f6424 --- /dev/null +++ b/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java @@ -0,0 +1,73 @@ +package com.beat.global.common.aop; + +import java.util.Arrays; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Aspect +@Order(3) +@Component +public class ServiceLoggingAspect { + /** 실행 시간 측정 */ + @Around("com.beat.global.common.aop.Pointcuts.allApplicationLogic()") + public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + try { + return joinPoint.proceed(); + } finally { + long end = System.currentTimeMillis(); + long timeInMs = end - start; + log.info("[실행 시간] {}.{}() | time = {}ms", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + timeInMs); + } + } + + /** Service 메서드 실행 전 로깅 */ + @Before("com.beat.global.common.aop.Pointcuts.allService()") + public void doLog(JoinPoint joinPoint) { + log.info("[메서드 실행] {}.{}() | 인자: {}", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + Arrays.toString(joinPoint.getArgs())); + } + + /** Service 정상 반환 로깅 */ + @AfterReturning(value = "com.beat.global.common.aop.Pointcuts.allService()", returning = "result") + public void logReturn(JoinPoint joinPoint, Object result) { + log.info("[Service 정상 반환] {}.{}() | 반환 값: {}", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + result); + } + + /** 예외 발생 시 로깅 */ + @AfterThrowing(value = "com.beat.global.common.aop.Pointcuts.allService()", throwing = "ex") + public void logException(JoinPoint joinPoint, Exception ex) { + log.error("[예외 발생] {}.{}() | 예외 메시지: {}", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + ex.getMessage()); + } + + /** 메서드 실행 후 로깅 */ + @After("com.beat.global.common.aop.Pointcuts.allService()") + public void doAfter(JoinPoint joinPoint) { + log.info("[메서드 종료] {}.{}()", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName()); + } +} From f8aedbb764e6193f6c2ab50bbdfb318f40111195 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 17:13:58 +0900 Subject: [PATCH 10/15] =?UTF-8?q?[#310]=20feat(TxAspect):=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=98=20=EB=A1=9C=EA=B9=85=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20TxAspect=20AOP=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서비스 레이어에서 트랜잭션의 시작, 커밋, 롤백, 리소스 릴리즈를 로깅하도록 TxAspect 구현 - @Transactional 애너테이션 정보를 읽어 트랜잭션 속성(readOnly, propagation, isolation) 로깅 추가 - 트랜잭션 실행 시간을 측정하여 성능 모니터링 가능하도록 개선 - 트랜잭션이 적용되는 메서드를 Pointcut으로 지정하여 관리 --- .../com/beat/global/common/aop/TxAspect.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/com/beat/global/common/aop/TxAspect.java diff --git a/src/main/java/com/beat/global/common/aop/TxAspect.java b/src/main/java/com/beat/global/common/aop/TxAspect.java new file mode 100644 index 0000000..69d0e6c --- /dev/null +++ b/src/main/java/com/beat/global/common/aop/TxAspect.java @@ -0,0 +1,71 @@ +package com.beat.global.common.aop; + +import java.lang.reflect.Method; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Aspect +@Order(1) +@Component +public class TxAspect { + + @Around("com.beat.global.common.aop.Pointcuts.allService()") + public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable { + // 메서드 정보를 추출 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + + // @Transactional 애너테이션 정보 확인 (메서드 혹은 클래스 레벨) + Transactional transactional = method.getAnnotation(Transactional.class); + if (transactional == null) { + transactional = joinPoint.getTarget().getClass().getAnnotation(Transactional.class); + } + + boolean readOnly = false; + Propagation propagation = Propagation.REQUIRED; + Isolation isolation = Isolation.DEFAULT; + if (transactional != null) { + readOnly = transactional.readOnly(); + propagation = transactional.propagation(); + isolation = transactional.isolation(); + } + + // 트랜잭션 시작 로깅 (옵션 포함) + log.info("[트랜잭션 시작] {}.{}() | readOnly={} | propagation={} | isolation={}", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + method.getName(), readOnly, propagation, isolation); + + // 실행 시간 측정을 위한 시작 시간 + long start = System.currentTimeMillis(); + try { + // 실제 비즈니스 로직 실행 + Object result = joinPoint.proceed(); + long elapsed = System.currentTimeMillis() - start; + log.info("[트랜잭션 커밋] {}.{}() | 소요 시간: {}ms", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + method.getName(), elapsed); + return result; + } catch (Exception e) { + long elapsed = System.currentTimeMillis() - start; + log.error("[트랜잭션 롤백] {}.{}() | 소요 시간: {}ms", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + method.getName(), elapsed, e); + throw e; + } finally { + // 리소스 릴리즈 로깅 + log.info("[리소스 릴리즈] {}.{}()", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + method.getName()); + } + } +} From 598d1bab7b548b12a4721d94e5d58dba7925b80a Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 17:17:03 +0900 Subject: [PATCH 11/15] =?UTF-8?q?[#310]=20feat(Pointcuts):=20AOP=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=EC=BB=B7=20=EC=A0=95=EC=9D=98?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20Pointcuts=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=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 - 컨트롤러, 서비스, 애플리케이션 로직을 대상으로 하는 AOP 포인트컷 정의 - `allController()`: 모든 컨트롤러 메서드를 대상으로 지정 - `allService()`: 서비스, UseCase, Facade 계층의 메서드를 대상으로 지정 - `allApplicationLogic()`: 글로벌 설정을 제외한 모든 애플리케이션 로직을 대상으로 지정 - AOP 기반의 로깅 및 트랜잭션 관리를 위한 공통 포인트컷으로 활용 가능 --- .../com/beat/global/common/aop/Pointcuts.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/com/beat/global/common/aop/Pointcuts.java diff --git a/src/main/java/com/beat/global/common/aop/Pointcuts.java b/src/main/java/com/beat/global/common/aop/Pointcuts.java new file mode 100644 index 0000000..cebf1f4 --- /dev/null +++ b/src/main/java/com/beat/global/common/aop/Pointcuts.java @@ -0,0 +1,15 @@ +package com.beat.global.common.aop; + +import org.aspectj.lang.annotation.Pointcut; + +public class Pointcuts { + @Pointcut("execution(* com.beat..*Controller.*(..))") + public void allController() {} + + @Pointcut("execution(* com.beat..*Service.*(..)) || execution(* com.beat..*UseCase.*(..)) || execution(* com.beat..*Facade.*(..))") + public void allService() {} + + @Pointcut("execution(* com.beat..*(..))" + + " && !within(com.beat.global..*)") + public void allApplicationLogic() {} +} From 60d2b69b3441b2b60301ca4182ce2fa761630cc1 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Mon, 17 Feb 2025 17:18:53 +0900 Subject: [PATCH 12/15] =?UTF-8?q?[#310]=20feat(BeatApplication):=20AOP=20?= =?UTF-8?q?=ED=94=84=EB=A1=9D=EC=8B=9C=20=ED=99=9C=EC=84=B1=ED=99=94?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=B4=20@EnableAspectJAutoProxy=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/beat/BeatApplication.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/beat/BeatApplication.java b/src/main/java/com/beat/BeatApplication.java index da27299..3dc7492 100644 --- a/src/main/java/com/beat/BeatApplication.java +++ b/src/main/java/com/beat/BeatApplication.java @@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignAutoConfiguration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @@ -12,6 +13,7 @@ @EnableFeignClients @EnableScheduling @EnableAsync +@EnableAspectJAutoProxy @ImportAutoConfiguration({FeignAutoConfiguration.class}) public class BeatApplication { From f66d6c2567a69e2e374ea4bbf6b83e1cb339724a Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Wed, 19 Feb 2025 20:57:12 +0900 Subject: [PATCH 13/15] =?UTF-8?q?[#310]=20chore:=20test=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=20AOP=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/beat/global/common/aop/ControllerLoggingAspect.java | 2 ++ .../java/com/beat/global/common/aop/ServiceLoggingAspect.java | 2 ++ src/main/java/com/beat/global/common/aop/TxAspect.java | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java b/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java index 4c749d1..9df6c51 100644 --- a/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java +++ b/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java @@ -12,6 +12,7 @@ import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; +import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; @@ -26,6 +27,7 @@ @Aspect @Order(2) @Component +@Profile("!test") public class ControllerLoggingAspect { private static final String REQUEST_URI = "requestURI"; diff --git a/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java b/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java index a2f6424..4f4c124 100644 --- a/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java +++ b/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java @@ -10,6 +10,7 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; +import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @@ -19,6 +20,7 @@ @Aspect @Order(3) @Component +@Profile("!test") public class ServiceLoggingAspect { /** 실행 시간 측정 */ @Around("com.beat.global.common.aop.Pointcuts.allApplicationLogic()") diff --git a/src/main/java/com/beat/global/common/aop/TxAspect.java b/src/main/java/com/beat/global/common/aop/TxAspect.java index 69d0e6c..65dea89 100644 --- a/src/main/java/com/beat/global/common/aop/TxAspect.java +++ b/src/main/java/com/beat/global/common/aop/TxAspect.java @@ -5,6 +5,7 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Isolation; @@ -17,6 +18,7 @@ @Aspect @Order(1) @Component +@Profile("!test") public class TxAspect { @Around("com.beat.global.common.aop.Pointcuts.allService()") From 3eca382f9f3f56faefc1619fddf9db46f473e20b Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Thu, 20 Feb 2025 21:52:02 +0900 Subject: [PATCH 14/15] =?UTF-8?q?[#310]=20refactor(ControllerLoggingAspect?= =?UTF-8?q?):=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=A0=95=EC=83=81?= =?UTF-8?q?=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EA=B7=B8=20debug=20=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/beat/global/common/aop/ControllerLoggingAspect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java b/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java index 9df6c51..75d377e 100644 --- a/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java +++ b/src/main/java/com/beat/global/common/aop/ControllerLoggingAspect.java @@ -68,7 +68,7 @@ public void logControllerRequest(JoinPoint joinPoint) { /** Controller 정상 반환 로깅 */ @AfterReturning(value = "com.beat.global.common.aop.Pointcuts.allController()", returning = "result") public void logControllerResponse(JoinPoint joinPoint, Object result) { - log.info("[Controller 정상 반환] {}.{}() | 반환 값: {}", + log.debug("[Controller 정상 반환] {}.{}() | 반환 값: {}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName(), result); From 91fb3ea8300fd4a4841e479555d010c84d5af699 Mon Sep 17 00:00:00 2001 From: DongHoon Lee Date: Thu, 20 Feb 2025 21:53:04 +0900 Subject: [PATCH 15/15] =?UTF-8?q?[#310]=20feat:=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=B8=A1=EC=A0=95=20AOP=20=ED=81=B4?= =?UTF-8?q?=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 - prod 환경에서는 서비스 계층의 실행속도만 로깅 - local, dev 환경에서는 모든 계층의 실행속도를 로깅 --- .../common/aop/ExecutionTimeLoggerAspect.java | 55 +++++++++++++++++++ .../common/aop/ServiceLoggingAspect.java | 15 ----- 2 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/beat/global/common/aop/ExecutionTimeLoggerAspect.java diff --git a/src/main/java/com/beat/global/common/aop/ExecutionTimeLoggerAspect.java b/src/main/java/com/beat/global/common/aop/ExecutionTimeLoggerAspect.java new file mode 100644 index 0000000..e03de34 --- /dev/null +++ b/src/main/java/com/beat/global/common/aop/ExecutionTimeLoggerAspect.java @@ -0,0 +1,55 @@ +package com.beat.global.common.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.context.annotation.Profile; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Aspect +@Component +@Order(0) +public class ExecutionTimeLoggerAspect { + + private ExecutionTimeLoggerAspect() { + } + + /** prod 환경에서는 서비스 계층만 실행 시간 측정 */ + @Aspect + @Component + @Profile("prod") + public static class ExecutionTimeLoggerForProd { + @Around("com.beat.global.common.aop.Pointcuts.allService()") + public Object logServiceExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { + return measureExecutionTime(joinPoint); + } + } + + /** local/dev 환경에서는 전체 애플리케이션 로직 실행 시간 측정 */ + @Aspect + @Component + @Profile({"local", "dev"}) + public static class ExecutionTimeLoggerForLocalDev { + @Around("com.beat.global.common.aop.Pointcuts.allApplicationLogic()") + public Object logApplicationExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { + return measureExecutionTime(joinPoint); + } + } + + /** 실행 시간 측정 공통 메서드 */ + private static Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + try { + return joinPoint.proceed(); + } finally { + long timeInMs = System.currentTimeMillis() - start; + log.info("[실행 시간] {}.{}() | time = {}ms", + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + timeInMs); + } + } +} diff --git a/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java b/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java index 4f4c124..df02d87 100644 --- a/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java +++ b/src/main/java/com/beat/global/common/aop/ServiceLoggingAspect.java @@ -22,21 +22,6 @@ @Component @Profile("!test") public class ServiceLoggingAspect { - /** 실행 시간 측정 */ - @Around("com.beat.global.common.aop.Pointcuts.allApplicationLogic()") - public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { - long start = System.currentTimeMillis(); - try { - return joinPoint.proceed(); - } finally { - long end = System.currentTimeMillis(); - long timeInMs = end - start; - log.info("[실행 시간] {}.{}() | time = {}ms", - joinPoint.getSignature().getDeclaringType().getSimpleName(), - joinPoint.getSignature().getName(), - timeInMs); - } - } /** Service 메서드 실행 전 로깅 */ @Before("com.beat.global.common.aop.Pointcuts.allService()")