728x90
반응형

전체 순서

  1. 인터셉터 ( 요청 객체 로깅 ) 
  2. Basic Aspect ( try catch 에러 핸들링 ) - Order(2) , 모든 controller, service, repository
  3. LogTraceAspect ( log 추적기 ) - Order(1), @LogTrace 어노테이션 지정한 곳
  4. 로직 실행
  5. LogTraceAspect ( log 추적기 ) - Order(1)
  6. Basic Aspect ( try catch 에러 핸들링 ) - Order(2)
  7. 인터셉터 ( 응답 객체 로깅 )

 

LogTrace는 필요한 경우에만 사용하도록 하자.

 

1. BasicAspect 생성

/backend/aop/BasicAspect

package web.backend.aop;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;

@Slf4j
@Aspect
@Order(2)
public class BasicAspect {

    @Around("execution(* web.backend.module..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {

        try {

            Object result = joinPoint.proceed();

            return result;
        } catch (Exception e) {
            throw e;
        }
    }
}

 

2. TraceStatus

backend/aop/trace/TraceStatus

package web.backend.aop.trace;

public class TraceStatus {

    private TraceId traceId;
    private Long startTimeMs;
    private String message;

    public TraceStatus(TraceId traceId, Long startTimeMs, String message) {
        this.traceId = traceId;
        this.startTimeMs = startTimeMs;
        this.message = message;
    }

    public Long getStartTimeMs() {
        return startTimeMs;
    }

    public String getMessage() {
        return message;
    }

    public TraceId getTraceId() {
        return traceId;
    }
}

 

3. TraceId

backend/aop/trace/TraceId

package web.backend.aop.trace;

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() {
        return UUID.randomUUID().toString().substring(0, 8);
    }

    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;
    }
}

 

4. LogTrace Interface

backend/aop/trace/logtrace/LogTrace

package web.backend.aop.logtrace;

import hello.proxy.trace.TraceStatus;

public interface LogTrace {

    TraceStatus begin(String message);
    void end(TraceStatus status);
    void exception(TraceStatus status, Exception e);
}

 

5. ThreadLocalLogTrace

backend/aop/trace/logtrace/ThreadLocalLogTrace

package web.backend.aop.trace.logtrace;

import lombok.extern.slf4j.Slf4j;
import web.backend.aop.trace.TraceId;
import web.backend.aop.trace.TraceStatus;

@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();
    }
}

 

6. LogTrace Annotation

backend/annotation/LogTrace

package web.backend.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTrace {
}

 

7. LogTraceAspect

backend/aop/LogTraceAspect

package web.backend.aop;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import web.backend.aop.trace.TraceStatus;
import web.backend.aop.trace.logtrace.LogTrace;

@Slf4j
@Aspect
@Order(1)
public class LogTraceAspect {

    private final LogTrace logTrace;

    public LogTraceAspect(LogTrace logTrace) {
        this.logTrace = logTrace;
    }

    @Around("@annotation(web.backend.annotation.LogTrace)")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        TraceStatus status = null;

        try {
            String message = joinPoint.getSignature().toShortString();
            status = logTrace.begin(message);

            Object result = joinPoint.proceed();

            logTrace.end(status);
            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }

}

 

8. AopConfig 생성

backend/aop/AopConfig

package web.backend.aop;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import web.backend.aop.trace.logtrace.LogTrace;
import web.backend.aop.trace.logtrace.ThreadLocalLogTrace;

@Configuration
public class AopConfig {

    @Bean
    public BasicAspect basicAspect() {
        return new BasicAspect();
    }

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }

    @Bean
    public LogTraceAspect logTraceAspect() {
        return new LogTraceAspect(logTrace());
    }
}

9. WebConfig 등록

backend/WebConfig

package web.backend;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import web.backend.aop.AopConfig;
import web.backend.interceptor.LogInterceptor;

@Configuration
@Import(AopConfig.class)
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/css/**", "/*.ico"
                        , "/error", "/error-page/**" //오류 페이지 경로
                );
    }

}

 

 


테스트

 

save 로직에 @LogTrace 어노테이션을 주고 findAll() 메소드와 비교해보겠다.

 

// controller    
    @PostMapping
    @LogTrace
    public String save(@RequestBody User user) {
        return userService.save(user);
    }
// service
    @LogTrace
    public String save(User user) {
        userSpringJPARepository.save(user);
        return "ok";
    }
// repository
    @Override
    @LogTrace
    <S extends User> S save(S entity);

 

 

Find 

 

Save

728x90
반응형

+ Recent posts