Search

AOP를 이용한 로거

Created
2022/10/16 04:36
AOP는 공통 관심사와 핵심 관심사를 분리하는데 그 목적이 있다. 로거는 인터셉터를 이용해서도 구현할 수 있지만, AOP를 이용하면 비교적 간단하게 구현할 수 있다.
@Slf4j @Aspect @Component public class LogAop { @Around("within(com.pivot.hp.hometownpolitician.controller..*)") public Object log(ProceedingJoinPoint pjp) throws Throwable { Long start = System.currentTimeMillis(); Object returnObj = pjp.proceed(); Long end = System.currentTimeMillis(); logRequest(pjp); logResponse(pjp, returnObj); logSummary(pjp, start, end); return returnObj; } private void logRequest(JoinPoint joinPoint) { log.info("REQUEST of {} ({})\n-> {}", getDeclaringTypeName(joinPoint), getMethod(joinPoint).getName(), getParams(joinPoint)); } private void logResponse(JoinPoint joinPoint, Object returnObj) { log.info("RESPONSE of {} ({})\n-> {}", getDeclaringTypeName(joinPoint), getMethod(joinPoint).getName(), getReturnObj(returnObj)); } private void logSummary(JoinPoint joinPoint, Long start, Long end) { log.info("SUMMARY of {} ({})\n-> {}", getDeclaringTypeName(joinPoint), getMethod(joinPoint).getName(), getSummary(start, end)); } private Method getMethod(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); return signature.getMethod(); } private String getDeclaringTypeName(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); return signature.getDeclaringTypeName(); } private String getParams(JoinPoint joinPoint) { Stream<Object> stream = Arrays.stream(joinPoint.getArgs()); if (joinPoint.getArgs().length == 0) { return "No Parameter"; } return "[\n" + getParamsInternal(stream) + "\n]"; } private String getParamsInternal(Stream<Object> stream) { return stream.map(e -> { Object[] values = new Object[1]; values[0] = e; return String.format("\t%s -> %s", e.getClass().getSimpleName(), Joiner.on(",").join(values)); }).collect(Collectors.joining(",\n")); } private Object getReturnObj(Object returnObj) { Object[] values = new Object[1]; values[0] = returnObj; String obj = String.format("\t%s -> %s", returnObj.getClass().getSimpleName(), Joiner.on(",").join(values)); return "{\n" + obj + "\n}"; } private Object getSummary(Long start, Long end) { return "{\n" + getSummaryInternal(start, end) + "\n}"; } private Object getSummaryInternal(Long start, Long end) { HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); return new Summary(req, start, end); } static class Summary { String addr; String uri; String method; Long latency; public Summary(HttpServletRequest req, Long start, Long end) { addr = getIpAddress(req); uri = getUri(req); method = getMethod(req); latency = end - start; } private String getIpAddress(HttpServletRequest req) { Optional<String> ip = Optional.ofNullable(req.getHeader("X-FORWARDED-FOR")); return ip.orElse(req.getRemoteAddr()); } private String getUri(HttpServletRequest req) { Optional<String> uri = Optional.ofNullable(req.getRequestURI()); return uri.orElse(""); } private String getMethod(HttpServletRequest req) { Optional<String> method = Optional.ofNullable(req.getMethod()); return method.orElse(""); } public String toString() { return "\taddr -> " + addr + "\n\turi -> " + uri + "\n\tmethod -> " + method + "\n\tlatency -> " + Long.toString(latency); } } }
Java
복사
위와 같은 코드를 이용하여 AOP 객체를 구현하면 main 경로의 controller 아래에 존재하는 객체의 메서드를 이용할 때 로그가 남게 된다. controller의 객체들은 모두 API 호출을 위한 객체들이므로, API 호출로 그 결과를 확인할 수 있다.
@RestController public class SampleController { @GetMapping("/sample") public String sampleCall(@RequestParam String args) { return "sample"; } }
Java
복사
작성해둔 간단한 컨트롤러 코드는 위와 같다.
@SpringBootApplication @EnableAspectJAutoProxy public class HometownPoliticianApplication { public static void main(String[] args) { SpringApplication.run(HometownPoliticianApplication.class, args); } }
Java
복사
AOP의 이용을 위해 위처럼 @SpringBootApplication이 있는 진입점에는 @EnableAspectJAutoProxy를 기입해준다.
호출 결과 로깅이 잘 되는 것을 볼 수 있다.