diff --git a/backend/src/main/java/me/performancereservation/global/config/AopConfig.java b/backend/src/main/java/me/performancereservation/global/config/AopConfig.java new file mode 100644 index 0000000..8f119f8 --- /dev/null +++ b/backend/src/main/java/me/performancereservation/global/config/AopConfig.java @@ -0,0 +1,15 @@ +package me.performancereservation.global.config; + +import me.performancereservation.global.logtrace.LogTrace; +import me.performancereservation.global.logtrace.LogTraceAspect; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AopConfig { + + @Bean + public LogTraceAspect logTraceAspect(LogTrace logTrace) { + return new LogTraceAspect(logTrace); + } +} diff --git a/backend/src/main/java/me/performancereservation/global/config/LogTraceConfig.java b/backend/src/main/java/me/performancereservation/global/config/LogTraceConfig.java new file mode 100644 index 0000000..72061a1 --- /dev/null +++ b/backend/src/main/java/me/performancereservation/global/config/LogTraceConfig.java @@ -0,0 +1,13 @@ +package me.performancereservation.global.config; + +import me.performancereservation.global.logtrace.LogTrace; +import me.performancereservation.global.logtrace.ThreadLocalLogTrace; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class LogTraceConfig { + + @Bean + public LogTrace logTrace() { return new ThreadLocalLogTrace();} +} diff --git a/backend/src/main/java/me/performancereservation/global/logtrace/LogTrace.java b/backend/src/main/java/me/performancereservation/global/logtrace/LogTrace.java new file mode 100644 index 0000000..93ba9c4 --- /dev/null +++ b/backend/src/main/java/me/performancereservation/global/logtrace/LogTrace.java @@ -0,0 +1,9 @@ +package me.performancereservation.global.logtrace; + +public interface LogTrace { + TraceStatus begin(String message); + + void end(TraceStatus status); + + void exception(TraceStatus status, Exception e); +} diff --git a/backend/src/main/java/me/performancereservation/global/logtrace/LogTraceAspect.java b/backend/src/main/java/me/performancereservation/global/logtrace/LogTraceAspect.java new file mode 100644 index 0000000..9cf92bc --- /dev/null +++ b/backend/src/main/java/me/performancereservation/global/logtrace/LogTraceAspect.java @@ -0,0 +1,109 @@ +package me.performancereservation.global.logtrace; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.performancereservation.domain.user.entitiy.User; +import me.performancereservation.global.security.oauth.user.CustomOAuth2User; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Aspect +@RequiredArgsConstructor +public class LogTraceAspect { + + private final LogTrace logTrace; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Around("execution(* me.performancereservation.domain..*(..)) " + + "|| execution(* me.performancereservation.api..*(..))") + public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { + TraceStatus status = null; + try { + // 기본 info 로그 + String message = joinPoint.getSignature().toShortString(); + status = logTrace.begin(message); + + // 메서드 정보는 debug 레벨로 출력 + String methodInfo = getMethodInfo(joinPoint); + log.debug("[{}] Parameters: {}", status.getTraceId().getId(), methodInfo); + + //로직 호출 + Object result = joinPoint.proceed(); + + logTrace.end(status); + return result; + } catch (Exception e) { + logTrace.exception(status, e); + throw e; + } + } + + private String getMethodInfo(ProceedingJoinPoint joinPoint) { + // 메서드 시그니처와 파라미터 정보 조합 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + String methodName = signature.toShortString(); + String params = getParams(signature, joinPoint.getArgs()); + + return methodName + " " + params; + } + + private String getParams(MethodSignature signature, Object[] args) { + // 파라미터 이름과 값을 매핑 + String[] paramNames = signature.getParameterNames(); + List paramList = new ArrayList<>(); + + for (int i = 0; i < args.length; i++) { + String name = (paramNames != null && i < paramNames.length) + ? paramNames[i] + : "arg" + i; + String value = convertToString(args[i]); + paramList.add(name + "=" + value); + } + + return "[" + String.join(", ", paramList) + "]"; + } + + private String convertToString(Object arg) { + try { + // CustomOAuth2User 처리 + if (arg instanceof CustomOAuth2User) { + return maskSensitiveInfo((CustomOAuth2User) arg); + } + // 다른 민감한 객체 처리 (예: User 엔티티) + if (arg instanceof User) { + return maskUserInfo((User) arg); + } + return objectMapper.writeValueAsString(arg); + } catch (JsonProcessingException e) { + return arg != null ? arg.toString() : "null"; + } + } + + private String maskSensitiveInfo(CustomOAuth2User oauthUser) { + // 민감 정보 마스킹 + return String.format( + "CustomOAuth2User{id=%s, name=%s, role=%s}", + oauthUser.getUser().getId(), + oauthUser.getUser().getName(), + oauthUser.getUser().getRole() + ); + } + + private String maskUserInfo(User user) { + // User 엔티티의 민감 정보 제거 + return String.format( + "User{id=%s, name=%s, role=%s}", + user.getId(), + user.getName(), + user.getRole() + ); + } +} \ No newline at end of file diff --git a/backend/src/main/java/me/performancereservation/global/logtrace/ThreadLocalLogTrace.java b/backend/src/main/java/me/performancereservation/global/logtrace/ThreadLocalLogTrace.java new file mode 100644 index 0000000..ed8afbf --- /dev/null +++ b/backend/src/main/java/me/performancereservation/global/logtrace/ThreadLocalLogTrace.java @@ -0,0 +1,72 @@ +package me.performancereservation.global.logtrace; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ThreadLocalLogTrace implements LogTrace{ + + private static final String START_PREFIX = "-->"; + private static final String COMPLETE_PREFIX = "<--"; + private static final String EX_PREFIX = " traceIdHolder = new ThreadLocal<>(); + + @Override + public TraceStatus begin(String message) { + syncTraceId(); + TraceId traceId = traceIdHolder.get(); + Long startTimeMs = System.currentTimeMillis(); + log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message); + + return new TraceStatus(traceId, startTimeMs, message); + } + + @Override + public void end(TraceStatus status) { + complete(status, null); + } + + @Override + public void exception(TraceStatus status, Exception e) { + complete(status, e); + } + + private void complete(TraceStatus status, Exception e) { + Long stopTimeMs = System.currentTimeMillis(); + long resultTimeMs = stopTimeMs - status.getStartTimeMs(); + TraceId traceId = status.getTraceId(); + if (e == null) { + log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs); + } else { + log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs, e.toString()); + } + + releaseTraceId(); + } + + private void syncTraceId() { + TraceId traceId = traceIdHolder.get(); + if (traceId == null) { + traceIdHolder.set(new TraceId()); + } else { + traceIdHolder.set(traceId.createNextId()); + } + } + + private void releaseTraceId() { + TraceId traceId = traceIdHolder.get(); + if (traceId.isFirstLevel()) { + traceIdHolder.remove();//destroy + } else { + traceIdHolder.set(traceId.createPreviousId()); + } + } + + private static String addSpace(String prefix, int level) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < level; i++) { + sb.append( (i == level - 1) ? "|" + prefix : "| "); + } + return sb.toString(); + } +} diff --git a/backend/src/main/java/me/performancereservation/global/logtrace/TraceId.java b/backend/src/main/java/me/performancereservation/global/logtrace/TraceId.java new file mode 100644 index 0000000..acce2ad --- /dev/null +++ b/backend/src/main/java/me/performancereservation/global/logtrace/TraceId.java @@ -0,0 +1,58 @@ +package me.performancereservation.global.logtrace; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Random; +import java.util.UUID; + +public class TraceId { + private String id; + private int level; + + public TraceId() { + this.id = createId(); + this.level = 0; + } + + private TraceId(String id, int level) { + this.id = id; + this.level = level; + } + + private String createId() { + String timestamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); + String random = getRandomString(4); + + return timestamp + "-" + random; + } + + public TraceId createNextId() { + return new TraceId(id, level + 1); + } + + public TraceId createPreviousId() { + return new TraceId(id, level - 1); + } + + public boolean isFirstLevel() { + return level == 0; + } + + public String getId() { + return id; + } + + public int getLevel() { + return level; + } + + private String getRandomString(int length) { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + StringBuilder sb = new StringBuilder(); + Random rnd = new Random(); + for (int i = 0; i < length; i++) { + sb.append(chars.charAt(rnd.nextInt(chars.length()))); + } + return sb.toString(); + } +} diff --git a/backend/src/main/java/me/performancereservation/global/logtrace/TraceStatus.java b/backend/src/main/java/me/performancereservation/global/logtrace/TraceStatus.java new file mode 100644 index 0000000..3ae5cd2 --- /dev/null +++ b/backend/src/main/java/me/performancereservation/global/logtrace/TraceStatus.java @@ -0,0 +1,12 @@ +package me.performancereservation.global.logtrace; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class TraceStatus { + private final TraceId traceId; + private final Long startTimeMs; + private final String message; +}