[์ผ] Spring AOP ์ฌ์ฉํ์ฌ Loggingํ๊ธฐ

์ด์ ์๋ ๊ฐ๊ฐ์ controller ์ต์๋จ์์ ์ด๋ค ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์๋์ง ์ง์ ๋ก๊น ์ ์งํํ๋ค
๊ทธ๋ฆฌ๊ณ ์คํ ์๊ฐ์ ๋ํ ๋ก๊น ์ ํ์ง ์์๋ค.
๋น์ฆ๋์ค ์ฝ๋์ ์ํฅ์ ์ฃผ์ง ์์ผ๋ฉด์๋ (log.debug ์์ฒญ ๋ง์ผ๋๊น ๋ณต์กํ๋๋ผ)
Controller, Service, Repository ๋ฉ์๋ ํธ์ถํ ๋๋ง๋ค ์์์๊ฐ, ๋์๊ฐ์ ๋จ๊ธฐ๊ณ ์ถ์๋ค.
๋ฐ๋ผ์, Spring AOP ์ฌ์ฉํ์ฌ Logging ์์ ์ ์งํํ๊ธฐ๋ก ํ๋ค.
๋๋ต ์ด๋ ๊ฒ ์์ฑํ๋ค
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// ThreadLocal๋ก ์์ ์๊ฐ์ ์ ์ฅ
private static ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
@Pointcut("within(com.exciting.vvue..*) && within(@org.springframework.web.bind.annotation.RestController *)")
public void controllerMethods() {}
@Pointcut("within(com.exciting.vvue..*) && within(@org.springframework.stereotype.Service *)")
public void serviceMethods() {}
@Pointcut("within(com.exciting.vvue..*) && within(@org.springframework.stereotype.Repository *)")
public void repositoryMethods() {}
@Around("controllerMethods() || serviceMethods() || repositoryMethods()")
public Object logAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// ์์ ์๊ฐ์ ThreadLocal์ ์ ์ฅ
long startTime = System.nanoTime();
startTimeThreadLocal.set(startTime);
String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
logger.info("[INFO] [{}] [{}] {} | started with arguments: {}",
resolvePointcutName(proceedingJoinPoint),
currentTime,
proceedingJoinPoint.getSignature().toShortString(),
proceedingJoinPoint.getArgs());
try {
// ์ค์ ๋ฉ์๋ ํธ์ถ
Object result = proceedingJoinPoint.proceed(); // ProceedingJoinPoint์์ proceed() ํธ์ถ
// ๋ฉ์๋ ์คํ ํ ์ข
๋ฃ ์๊ฐ ๊ณ์ฐ
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // ๋ฐ๋ฆฌ์ด๋ก ๋ณํ
logger.info("[INFO] [{}] [{}] {} | duration: {} ms. Return value: {}",
resolvePointcutName(proceedingJoinPoint),
currentTime,
proceedingJoinPoint.getSignature().toShortString(),
duration,
result);
return result;
} catch (Throwable throwable) {
// ์์ธ ๋ฐ์ ์ ์ฒ๋ฆฌ
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // ๋ฐ๋ฆฌ์ด๋ก ๋ณํ
logger.error("[ERROR] [{}] [{}] {} | threw exception after {} ms: {}",
resolvePointcutName(proceedingJoinPoint),
currentTime,
proceedingJoinPoint.getSignature().toShortString(),
duration,
throwable.getMessage());
throw throwable;
} finally {
// ThreadLocal ๊ฐ ์ ๋ฆฌ
startTimeThreadLocal.remove();
}
}
private String resolvePointcutName(ProceedingJoinPoint proceedingJoinPoint) {
Class<?> clazz = proceedingJoinPoint.getSignature().getDeclaringType();
Class<?> superClass = clazz.getSuperclass();
if (clazz.isAnnotationPresent(org.springframework.web.bind.annotation.RestController.class)
|| superClass != null && superClass.isAnnotationPresent(org.springframework.web.bind.annotation.RestController.class)) {
return "controller";
}
if (clazz.isAnnotationPresent(org.springframework.stereotype.Service.class)
|| superClass != null && superClass.isAnnotationPresent(org.springframework.stereotype.Service.class)) {
return "service";
}
if (clazz.isAnnotationPresent(org.springframework.stereotype.Repository.class)
|| superClass != null && superClass.isAnnotationPresent(org.springframework.stereotype.Repository.class)) {
return "repository";
}
return "unknown";
}
}
๋ก๊น ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ๋ค

๋ง์ฝ์ ํน์ ํจ์์๋ง ๋ก๊น ์ ์งํํ๊ณ ์ถ๋ค๋ฉด,
์๋์ ๊ฐ์ด ์ด๋ ธํ ์ด์ ์ ์์ฑํ๊ณ
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecuteTime {
}
@Aspect
@Component
public class ExecuteTimeAspect {
@Around("@annotation(LogExecuteTime)") // ์ด๋
ธํ
์ด์
๊ธฐ์ค์ผ๋ก ์งํ
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // ์ค์ ์คํ
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
return result;
}
}