

이전에 만들었던 로그 추적기에 스레드 로컬을 적용해서 각 로그마다 고유한 값인 traceId를 주어서 로그 추적을 해보자.
traceId를 사용하면 동시성 문제 없이 로그 흐름을 추적 가능하다는 장점이 있다.
// 이 클래스는 스레드마다 고유한 요청 ID를 저장하고 꺼내는 역할을 한다.
public class TraceIdHolder {
//Traceid : 하나의 요청을 고유하게 식별하는 ID(쓰레드로컬)
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
//ThradLocal에 값을 저장하고 꺼낼 set메서드와 get메서드
public static void set(String traceId){
threadLocal.set(traceId);
}
public static String get(){
return threadLocal.get();
}
//하나의 요청이 끝났을 때 ThradLocal에 저장된 값을 지우기 위한 clear 메서드
public static void clear(){
threadLocal.remove();
}
}
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {
/* 생략 */
// HTTP 매 요청마다 호출
@Override
protected void doFilterInternal(HttpServletRequest request, //http 요청
HttpServletResponse response, //http 응답
FilterChain filterChain
) throws ServletException, IOException {
try{
//HTTP요청이 실질적으로 시작되는 곳은 이곳(JwtTokenFilter) 이므로 여기서 TraceID 발급
//TraceId 발급
String traceId = UUID.randomUUID().toString().substring(0,8);
TraceIdHolder.set(traceId);
String accessToken = getTokenFromRequest(request); //요청 헤더에서 토큰 추출
//토큰이 존재하고 유효한 경우
if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) {
UsernamePasswordAuthenticationToken authenticationToken =getAuthentication(accessToken);
//토큰에서 사용자를 꺼내서 담은 사용자 인증 객체
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//http요청으로부터 부가 정보(ip,세션 등)를 추출해서 사용자 인증 객체에 넣어줌
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//토큰에서 사용자 인증정보를 조회해서 인증정보를 현재 스레드에 인증된 사용자로 등록
String url = request.getRequestURI().toString();
String method = request.getMethod(); // "GET, POST, PUT"
log.info("현재 들어온 HTTP 요청 = " + url);
log.info("✅ 토큰 인증 성공: " + accessToken);
}
else {
log.info("❌ 토큰 없음 또는 유효하지 않음: " + accessToken);
}
filterChain.doFilter(request,response); // JwtTokenFilter를 거치고 다음 필터로 넘어감
}finally {
//Http 요청이 끝날 때 데이터를 비워줌
TraceIdHolder.clear();
String afterClear = TraceIdHolder.get();
log.info("TraceHolder 데이터 확인 : {}", afterClear); //null;
}
}
}
clear()를 실행해준다.@Slf4j
@Component
@Aspect //공통으로 관리하고 싶은 기능을 담당하는 클래스에 붙이는 어노테이션
public class LogAspect {
//PointCut
//AOP를 적용할 클래스
@Pointcut("execution(* org.example.backendproject.board.service..*(..)) ||" +
"execution(* org.example.backendproject.Auth.service..*(..)) ||" +
"execution(* org.example.backendproject.user.service..*(..))" )
public void method(){}
//@Aroun는 호출 시작과 종료 모두에 관리할 수 있는 AOP Advice
@Around("method()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
long start = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName(); //aop가 실행된 메서드
try{
log.info("[AOP_LOG][TraceId]{} {} 메서드 호출 시작 ",TraceIdHolder.get(), methodName);
Object result = joinPoint.proceed(); //joinpoint //aop를 적용할 시점
return result;
}
catch (Exception e){
log.error("[AOP_LOG][TraceId]{} {} 메서드 예외 {} ",TraceIdHolder.get(), methodName, e.getMessage());
return e;
}finally {
long end = System.currentTimeMillis();
log.info("[AOP_LOG][TraceId]{} {} 메서드 실행 완료 시간 = {} ",TraceIdHolder.get(), methodName, end - start);
}
}
}

if "AOP_LOG" in [message] {
mutate {
add_field => { "log_type" => "aop" }
}
}
else if "OAuth2_LOG" in [message]{
mutate {
add_field => { "log_type" => "OAuth2" }
}
}
else if "TraceId" in [message] {
mutate{
add_field => {"log_type" => "TraceId"}
}
}