diff --git a/build.gradle b/build.gradle index be635860..aa7f3cfc 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + // json + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1' } tasks.named('test') { diff --git a/src/main/java/com/first/flash/account/auth/application/CustomUserDetailsService.java b/src/main/java/com/first/flash/account/auth/application/CustomUserDetailsService.java index c7bc2c61..dd321127 100644 --- a/src/main/java/com/first/flash/account/auth/application/CustomUserDetailsService.java +++ b/src/main/java/com/first/flash/account/auth/application/CustomUserDetailsService.java @@ -19,6 +19,7 @@ public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(final String id) throws UsernameNotFoundException { Member foundMember = memberService.findById(UUID.fromString(id)); - return new CustomUserDetails(foundMember.getId(), foundMember.getRole()); + return new CustomUserDetails(foundMember.getId(), foundMember.getRole(), + foundMember.getNickName()); } } diff --git a/src/main/java/com/first/flash/account/auth/application/dto/CustomUserDetails.java b/src/main/java/com/first/flash/account/auth/application/dto/CustomUserDetails.java index 4827fc34..9d6b4446 100644 --- a/src/main/java/com/first/flash/account/auth/application/dto/CustomUserDetails.java +++ b/src/main/java/com/first/flash/account/auth/application/dto/CustomUserDetails.java @@ -8,13 +8,17 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -public record CustomUserDetails(UUID id, Role role) implements UserDetails { +public record CustomUserDetails(UUID id, Role role, String nickName) implements UserDetails { @Override public Collection getAuthorities() { return Collections.singletonList(new SimpleGrantedAuthority(role.name())); } + public String getNickName() { + return nickName; + } + @Override public String getPassword() { return null; diff --git a/src/main/java/com/first/flash/global/aspect/LoggingAspect.java b/src/main/java/com/first/flash/global/aspect/LoggingAspect.java index de7ec490..078da0cf 100644 --- a/src/main/java/com/first/flash/global/aspect/LoggingAspect.java +++ b/src/main/java/com/first/flash/global/aspect/LoggingAspect.java @@ -1,17 +1,19 @@ package com.first.flash.global.aspect; -import com.first.flash.global.util.AuthUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.first.flash.account.auth.application.dto.CustomUserDetails; +import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; -import java.lang.reflect.Method; -import java.util.Enumeration; -import java.util.Objects; +import java.util.HashMap; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.reflect.MethodSignature; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; @@ -23,92 +25,122 @@ @RequiredArgsConstructor public class LoggingAspect { - @Before("com.first.flash.global.aspect.PointCuts.allController()") - public void doBeforeController() { - HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) - .getRequest(); - - log.info("======= Controller Start ======="); - log.info("Request URI: {}", request.getRequestURI()); - log.info("HTTP Method: {}", request.getMethod()); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final ThreadLocal> logContext = ThreadLocal.withInitial( + HashMap::new); - Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - log.info("Header: {} = {}", headerName, request.getHeader(headerName)); - } + @PostConstruct + public void setUp() { + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // ISO-8601 형식으로 출력 + } - Enumeration parameterNames = request.getParameterNames(); - while (parameterNames.hasMoreElements()) { - String parameterName = parameterNames.nextElement(); - log.info("Parameter: {} = {}", parameterName, request.getParameter(parameterName)); - } + @AfterReturning(pointcut = "execution(* com.first.flash.account.auth.application.CustomUserDetailsService.loadUserByUsername(..))", returning = "userDetails") + public void logUserDetails(final CustomUserDetails userDetails) { + Map logData = logContext.get(); + logData.put("requester", userDetails.getNickName()); + logContext.set(logData); } - @Before("com.first.flash.global.aspect.PointCuts.verifiedControllers()") - public void doBeforeVerifiedController() { - log.info("user id: {}", AuthUtil.getId()); + @Before("com.first.flash.global.aspect.PointCuts.allController()") + public void logRequestInfo(final JoinPoint joinPoint) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + + Map logData = logContext.get(); + logData.put("request URI", request.getRequestURI()); + logData.put("HTTP method", request.getMethod()); + logContext.set(logData); } - @AfterReturning(value = "com.first.flash.global.aspect.PointCuts.allController()", returning = "result") - public void afterReturnController(final Object result) { - if (result instanceof ResponseEntity responseEntity) { - int statusCode = responseEntity.getStatusCode().value(); - Object responseBody = responseEntity.getBody(); +// @Before("com.first.flash.global.aspect.PointCuts.allService() || com.first.flash.global.aspect.PointCuts.allRepository()") +// public void logMethodDetails(final JoinPoint joinPoint) { +// if (log.isDebugEnabled()) { +// String key = generateLogKey(joinPoint); +// +// Map methodDetails = new HashMap<>(); +// Object[] args = joinPoint.getArgs(); +// if (args.length > 0) { +// methodDetails.put("params", args); +// } else { +// methodDetails.put("params", "no params"); +// } +// +// updateLogContext(key, methodDetails); +// } +// } +// +// @AfterReturning(pointcut = "com.first.flash.global.aspect.PointCuts.allService() || com.first.flash.global.aspect.PointCuts.allRepository()", returning = "result") +// public void logMethodReturn(final JoinPoint joinPoint, final Object result) { +// if (log.isDebugEnabled()) { +// String key = generateLogKey(joinPoint); +// +// Map methodDetails = new HashMap<>(); +// methodDetails.put("returnValue", result); +// +// updateLogContext(key, methodDetails); +// } +// } + + @AfterReturning(pointcut = "com.first.flash.global.aspect.PointCuts.allController()", returning = "result") + public void logResponse(final Object result) { + Map logData = logContext.get(); + logData.put("type", "log"); - log.info("Response Status Code: {}", statusCode); - if (!Objects.isNull(responseBody)) { - log.info("Response Body: {}", responseBody); - } else { - log.info("no Response Body"); - } + if (result instanceof ResponseEntity responseEntity) { + logData.put("response status", responseEntity.getStatusCode().value()); + logData.put("response body", responseEntity.getBody()); } else { - if (!Objects.isNull(result)) { - log.info("Response: {}", result); - } else { - log.info("no Response"); - } + logData.put("response", result); } - log.info("======= Controller End ======="); - } - @Before("com.first.flash.global.aspect.PointCuts.allService() || com.first.flash.global.aspect.PointCuts.allRepository()") - public void doBefore(final JoinPoint joinPoint) { - Method method = getMethod(joinPoint); - String objectName = joinPoint.getTarget() - .getClass() - .getSimpleName(); - log.info("======= {} Start =======", objectName); - log.info("method name = {}", method.getName()); - Object[] args = joinPoint.getArgs(); - if (Objects.isNull(args) || args.length == 0) { - log.info("no parameter"); - return; - } - for (Object arg : args) { - if (Objects.isNull(arg)) { - continue; - } - log.info("parameter type = {}", arg.getClass().getSimpleName()); - log.info("parameter value = {}", arg); + try { + log.info(objectMapper.writeValueAsString(logData)); + } catch (Exception e) { + log.error("Error converting log to JSON", e); + } finally { + logContext.remove(); } } - @AfterReturning(value = "com.first.flash.global.aspect.PointCuts.allService() || com.first.flash.global.aspect.PointCuts.allRepository()", returning = "result") - public void afterReturnLog(final JoinPoint joinPoint, final Object result) { - if (Objects.isNull(result)) { - return; + @AfterReturning(pointcut = "com.first.flash.global.aspect.PointCuts.allExceptionHandler()", returning = "exception") + public void logException(final Object exception) { + Map logData = logContext.get(); + logData.put("type", "error"); + + if (exception instanceof ResponseEntity exceptionResponse) { + logData.put("error status", exceptionResponse.getStatusCode().value()); + logData.put("error body", exceptionResponse.getBody()); + } else { + logData.put("error", exception); } - log.info("return type = {}", result.getClass().getSimpleName()); - log.info("return value = {}", result); - String objectName = joinPoint.getTarget() - .getClass() - .getSimpleName(); - log.info("======= {} End =======", objectName); - } - private Method getMethod(final JoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - return signature.getMethod(); + try { + log.info(objectMapper.writeValueAsString(logData)); + } catch (Exception e) { + log.error("Error converting log to JSON", e); + } finally { + logContext.remove(); + } } + +// private String generateLogKey(final JoinPoint joinPoint) { +// MethodSignature signature = (MethodSignature) joinPoint.getSignature(); +// String className = joinPoint.getTarget().getClass().getSimpleName(); +// String methodName = signature.getMethod().getName(); +// return className + "." + methodName; +// } +// +// private void updateLogContext(final String key, final Map methodDetails) { +// Map logData = logContext.get(); +// +// if (logData.containsKey(key)) { +// Map existingDetails = (Map) logData.get(key); +// existingDetails.putAll(methodDetails); +// logData.put(key, existingDetails); +// } else { +// logData.put(key, methodDetails); +// } +// +// logContext.set(logData); +// } } diff --git a/src/main/java/com/first/flash/global/aspect/PointCuts.java b/src/main/java/com/first/flash/global/aspect/PointCuts.java index a3ee2ae1..148fe6f3 100644 --- a/src/main/java/com/first/flash/global/aspect/PointCuts.java +++ b/src/main/java/com/first/flash/global/aspect/PointCuts.java @@ -16,8 +16,11 @@ public void allService() { public void allRepository() { } + @Pointcut("execution(* com.first.flash..*ExceptionHandler.*(..))") + public void allExceptionHandler() { + } + @Pointcut("execution(* com.first.flash.climbing..*Controller.*(..))") public void verifiedControllers() { - } }