728x90
반응형
전체 순서
- 인터셉터 ( 요청 객체 로깅 )
- Basic Aspect ( try catch 에러 핸들링 ) - Order(2) , 모든 controller, service, repository
- LogTraceAspect ( log 추적기 ) - Order(1), @LogTrace 어노테이션 지정한 곳
- 로직 실행
- LogTraceAspect ( log 추적기 ) - Order(1)
- Basic Aspect ( try catch 에러 핸들링 ) - Order(2)
- 인터셉터 ( 응답 객체 로깅 )
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
반응형
'Back-End > Spring Boot' 카테고리의 다른 글
Sping Boot | Backend Project | 사용자 정의 에러 핸들링 ( Custom Excepton Handling with Controller Advice) (0) | 2022.09.28 |
---|---|
Sping Boot | Backend Project | 공통 응답 객체 ( Common Response ) (0) | 2022.09.28 |
Sping Boot | Backend Project | 요청 객체 로깅( Logging Interceptor 로깅 인터셉터 ) (0) | 2022.09.27 |
Sping Boot | Backend Project | CRUD with SpringDataJPA (0) | 2022.09.26 |
Sping Boot | Backend Project | Module (1) | 2022.09.26 |