-
Notifications
You must be signed in to change notification settings - Fork 0
[UNI-139] refactor: 로깅 메세지 가독성 좋게 변경 #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package com.softeer5.uniro_backend.common.logging; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public enum ArgType { | ||
| NULL, | ||
| LIST, | ||
| CUSTOM_DTO, | ||
| ENTITY, | ||
| OTHER; | ||
|
|
||
| public static ArgType getArgType(Object arg) { | ||
| if (arg == null) { | ||
| return NULL; | ||
| } else if (arg instanceof List<?>) { | ||
| return LIST; | ||
| } else if (isCustomDto(arg)) { | ||
| return CUSTOM_DTO; | ||
| } else if (isEntity(arg)) { | ||
| return ENTITY; | ||
| } else { | ||
| return OTHER; | ||
| } | ||
| } | ||
|
|
||
| private static boolean isCustomDto(Object arg) { | ||
| return arg.getClass().getName().contains("dto"); | ||
| } | ||
|
|
||
| private static boolean isEntity(Object arg) { | ||
| return arg.getClass().getName().contains("entity"); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,8 @@ | ||
| package com.softeer5.uniro_backend.common.logging; | ||
|
|
||
| import java.lang.annotation.Annotation; | ||
| import java.lang.reflect.Field; | ||
| import java.util.Arrays; | ||
| import java.util.Enumeration; | ||
| import java.util.Objects; | ||
| import java.util.List; | ||
| import java.util.UUID; | ||
|
|
||
| import org.aspectj.lang.ProceedingJoinPoint; | ||
|
|
@@ -12,7 +11,6 @@ | |
| import org.springframework.context.annotation.Profile; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.util.StopWatch; | ||
| import org.springframework.web.bind.annotation.RequestMethod; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.context.request.RequestContextHolder; | ||
| import org.springframework.web.context.request.ServletRequestAttributes; | ||
|
|
@@ -30,87 +28,127 @@ public class ExecutionLoggingAop { | |
|
|
||
| @Around("execution(* com.softeer5.uniro_backend..*(..)) " | ||
| + "&& !within(com.softeer5.uniro_backend.common..*) " | ||
| + "&& !within(com.softeer5.uniro_backend.resolver..*) " | ||
| ) | ||
| public Object logExecutionTrace(ProceedingJoinPoint pjp) throws Throwable { | ||
| // 요청에서 userId 가져오기 (ThreadLocal 사용) | ||
| String userId = userIdThreadLocal.get(); | ||
| if (userId == null) { | ||
| userId = UUID.randomUUID().toString().substring(0, 12); | ||
| userIdThreadLocal.set(userId); | ||
| } | ||
|
|
||
| Object target = pjp.getTarget(); | ||
| Annotation[] declaredAnnotations = target.getClass().getDeclaredAnnotations(); | ||
| boolean isController = isRestController(target); | ||
|
|
||
| String className = pjp.getSignature().getDeclaringType().getSimpleName(); | ||
| String methodName = pjp.getSignature().getName(); | ||
| String task = className + "." + methodName; | ||
|
|
||
| for(Annotation annotation : declaredAnnotations){ | ||
| if(annotation instanceof RestController){ | ||
| logHttpRequest(userId); | ||
|
|
||
| HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest(); | ||
| RequestMethod httpMethod = RequestMethod.valueOf(request.getMethod()); | ||
|
|
||
| log.info(""); | ||
| log.info("🚨 [ userId = {} ] {} Start", userId, className); | ||
| log.info("[ userId = {} ] [Call Method] {}: {}", userId, httpMethod, task); | ||
| } | ||
| if (isController) { | ||
| logHttpRequest(userId); | ||
| } | ||
| HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); | ||
| log.info("✅ [ userId = {} Start] [Call Method] {}: {}", userId, request.getMethod(), task); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. requestId 는 어떤지 궁금합니다! (같은 유저가 여러 요청을 보내도 매번 달라지기 때문)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thread id로 구분해도 큰 문제는 없을 것 같습니다! 하지만 스프링에서는 스레드 풀을 사용하기에 만약 스레드 풀을 재사용할 때 id가 중복돼어 로그 보는데 헷갈릴 수도 있을 것 같아 설정해봤습니다! 같은 유저가 여러 요청을 보내는 것에 대해선 저는 독립적인 요청으로 봤습니다!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋습니다! 확실히 thread ID는 자주 중복되니깐 불편하겠군요 |
||
|
|
||
| Object[] paramArgs = pjp.getArgs(); | ||
| for (Object object : paramArgs) { | ||
| if (Objects.nonNull(object)) { | ||
| log.info("[Parameter] {} {}", object.getClass().getSimpleName(), object); | ||
|
|
||
| String packageName = object.getClass().getPackage().getName(); | ||
| if (packageName.contains("java")) { | ||
| break; | ||
| } | ||
|
|
||
| Field[] fields = object.getClass().getDeclaredFields(); | ||
| for (Field field : fields) { | ||
| field.setAccessible(true); | ||
| try { | ||
| Object value = field.get(object); | ||
| log.info("[Field] {} = {}", field.getName(), value); | ||
| } catch (IllegalAccessException e) { | ||
| log.warn("[Field Access Error] Cannot access field: {}", field.getName()); | ||
| } | ||
| } | ||
| } | ||
| try{ | ||
| logParameters(pjp.getArgs()); | ||
| } | ||
| catch (Exception e){ | ||
| // 로깅 중에 발생한 에러는 무시하고 로깅을 계속 진행 | ||
| log.error("🚨🚨🚨 [ userId = {} ] {} 메서드 파라미터 로깅 중 에러 발생 : {} 🚨🚨🚨", userId, task, e.getMessage()); | ||
| } | ||
| log.info(""); | ||
|
|
||
| StopWatch sw = new StopWatch(); | ||
| sw.start(); | ||
|
|
||
| Object result = null; | ||
| Object result; | ||
| try { | ||
| result = pjp.proceed(); | ||
| } catch (Exception e) { | ||
| log.warn("[ERROR] [ userId = {} ] {} 메서드 예외 발생 : {}", userId, task, e.getMessage()); | ||
| throw e; | ||
| } finally { | ||
| // Controller 클래스일 때만 ThreadLocal 값 삭제 | ||
| for(Annotation annotation : declaredAnnotations){ | ||
| if(annotation instanceof RestController){ | ||
| userIdThreadLocal.remove(); | ||
| } | ||
| if (isController) { | ||
| userIdThreadLocal.remove(); | ||
| } | ||
| } | ||
|
|
||
| sw.stop(); | ||
| long executionTime = sw.getTotalTimeMillis(); | ||
|
|
||
| log.info("[ExecutionTime] {} --> {} (ms)", task, executionTime); | ||
| log.info("🚨 [ userId = {} ] {} End", userId, className); | ||
| log.info(""); | ||
| log.info("[ExecutionTime] {} --> {} (ms)", task, sw.getTotalTimeMillis()); | ||
| log.info("🚨 [ userId = {} ] {} End\n", userId, className); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private boolean isRestController(Object target) { | ||
| return Arrays.stream(target.getClass().getDeclaredAnnotations()) | ||
| .anyMatch(RestController.class::isInstance); | ||
| } | ||
|
|
||
| private void logParameters(Object[] args) { | ||
| StringBuilder parametersLogMessage = new StringBuilder(); | ||
|
|
||
| Arrays.stream(args) | ||
| .forEach(arg -> logDetail(arg, "[Parameter]", parametersLogMessage, 0)); | ||
|
|
||
| log.info("\n{}", parametersLogMessage.toString()); | ||
| } | ||
|
|
||
| private void logDetail(Object arg, String requestType, StringBuilder logMessage, int depth) { | ||
| String indent = " ".repeat(depth); // depth 수준에 따른 들여쓰기 | ||
| ArgType argType = ArgType.getArgType(arg); | ||
|
|
||
| switch (argType) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 가독성이나 확장성 측면에서 좋아진 것 같습니다! |
||
| case NULL -> logMessage.append(indent).append(requestType).append(" null\n"); | ||
| case LIST -> { | ||
| logMessage.append(indent) | ||
| .append(requestType) | ||
| .append(" ") | ||
| .append(arg.getClass().getSimpleName()) | ||
| .append("\n"); | ||
| List<?> list = (List<?>)arg; | ||
| for (int i = 0; i < list.size(); i++) { | ||
| logDetail(list.get(i), "[List Element " + i + "] ", logMessage, depth + 1); | ||
| } | ||
| } | ||
| case CUSTOM_DTO -> { | ||
| logMessage.append(indent) | ||
| .append(requestType) | ||
| .append("DTO: ") | ||
| .append(arg.getClass().getSimpleName()) | ||
| .append("\n"); | ||
| logObjectFields(arg, logMessage, depth + 1); | ||
| } | ||
| case ENTITY -> { | ||
| logMessage.append(indent) | ||
| .append(requestType) | ||
| .append(arg.getClass().getSimpleName()) | ||
| .append(" : ") | ||
| .append("\n"); | ||
| logObjectFields(arg, logMessage, depth + 1); | ||
| } | ||
| default -> logMessage.append(indent) | ||
| .append(requestType) | ||
| .append(" ") | ||
| .append(arg.getClass().getSimpleName()) | ||
| .append(": ") | ||
| .append(arg) | ||
| .append("\n"); | ||
| } | ||
| } | ||
|
|
||
| private void logObjectFields(Object object, StringBuilder logMessage, int depth) { | ||
| String indent = " ".repeat(depth); // depth 기반 들여쓰기 | ||
| Arrays.stream(object.getClass().getDeclaredFields()).forEach(field -> { | ||
| try { | ||
| field.setAccessible(true); | ||
| Object value = field.get(object); | ||
| logDetail(value, "[Field] " + field.getName(), logMessage, depth + 1); | ||
| } catch (IllegalAccessException e) { | ||
| logMessage.append(indent).append("[Field Access Error] Cannot access field: ").append(field.getName()).append("\n"); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private void logHttpRequest(String userId) { | ||
| HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게도 처리할 수 있군요!