Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게도 처리할 수 있군요!

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;
Expand All @@ -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;
Expand All @@ -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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestId 는 어떤지 궁금합니다! (같은 유저가 여러 요청을 보내도 매번 달라지기 때문)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thread id로 구분해도 큰 문제는 없을 것 같습니다! 하지만 스프링에서는 스레드 풀을 사용하기에 만약 스레드 풀을 재사용할 때 id가 중복돼어 로그 보는데 헷갈릴 수도 있을 것 같아 설정해봤습니다!

같은 유저가 여러 요청을 보내는 것에 대해선 저는 독립적인 요청으로 봤습니다!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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();
Expand Down