Skip to content
Open
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,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);
}
}
Original file line number Diff line number Diff line change
@@ -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();}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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<String> 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()
);
}
}
Original file line number Diff line number Diff line change
@@ -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 = "<X-";

private ThreadLocal<TraceId> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Loading