본문 바로가기

IT 개발/Spring

AOP를 사용한 로그 DB에 저장(코드포함)

반응형

이전에도 포스팅을 짧게 했었습니다.

https://chobi-meow.tistory.com/12

위에서는 너무 간단하게 방식정도와 간단한 어노테이션 개념정도만 정리해서 좀 더 정리가 필요할듯 하여

한번 더 포스팅하려고 합니다.

로그를 보통 logback.xml 로 서버로그에 담기도록 하는게 보통이지만

DB에 저장하는 방식을 정리해보려고 합니다.

@Entity
@Getter
@Builder
@DynamicInsert
@DynamicUpdate
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Table(name = "user_action_log")
@AttributeOverride(name = "id", column = @Column(name = "id"))
public class UserActionLog extends BaseEntity {

    @JoinColumn(name = "user_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
    @Comment("아이디")
    private Long id;

    @Column(name = "user_nm")
    @Comment("사용자 이름")
    private String user_nm;

    @Column(name = "user_ip")
    @Comment("로그인한 사용자 아이피")
    private String adminIp;

    @Column(name = "request_param", length = 5000)
    @Comment("요청파라미터")
    private String requestParam;

    @Column(name = "request_url")
    @Comment("요청 URL")
    private String requestUrl;

    @Column(name = "response_data", columnDefinition = "TEXT")
    @Comment("리턴 데이터")
    private String responseData;

}

먼저 엔티티를 작성해줍니다.
나는 JPA와 Sping boot를 사용하다보니 위와 같은 형식으로 엔티티를 생성해줍니다.

JpaRepository도 생성해주고
save 명령어만 쓸예정이라 쿼리문은 따로 써주지 않았습니다.

@Aspect
@Component
public class LoggingAspect {

    private final UserActionLogRepository userActionLogRepository;
    private final UserRepository userRepository;

    public LoggingAspect(UserRepository userRepository, UserActionLogRepository userActionLogRepository) {
        this.userRepository = userRepository;
        this.userActionLogRepository = userActionLogRepository;
    }

    @Pointcut("execution(* com.sample.test.*.controller.*.*(..))")
    public void controller() {
    }

LoggingAspect 라는 클래스를 만들어주고
@Aspect 어노테이션을 붙여 포인트컷(Pointcut)과 어드바이스(Advice)로 구성된 어드바이저(Advisor)의 생성을 편리하게 해주는 기능을 가진 어노테이션입니다. 실제 실무에서는 직접 어드바이저를 생성하는 것이 아닌 @Aspect 어노테이션을 이용하여 AOP를 적용하게 됩니다.
이후 사용할 Reposiory의 생성자를 만들어주었습니다.

그리고 @Pointcut이라는 표현식을 사용하여 프로젝트 내에 있는 모든 컨트롤러 경로를 지정해줍니다.

@AfterReturning(pointcut = "controller()", returning = "result")
    public void logAfter(JoinPoint joinPoint, Object result) throws JsonProcessingException {

        //url 정보
        StringBuffer url = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getRequestURL();
        //String 내에서 20621 이후의 문자열을 추출
        String urlStr = url.substring(url.indexOf("20621") + 5);
        JLog.logd(String.format("url = %s", urlStr));

        //IP 정보
        String ip = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getRemoteAddr();
        JLog.logd(String.format("ip = %s", ip));

        //파라미터 정보
        String params = Arrays.toString(joinPoint.getArgs());
        JLog.logd(String.format("params = %s", params));

        Long id = null;
        String userNm = null;
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonResult = null;
        //로그인한 사용자 정보
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated() && authentication.getPrincipal() instanceof UserDetails userDetails) {
            JLog.logd(String.format("userDetails = %s", userDetails.getUsername()));

            User user = userRepository.findExistUser(userDetails.getUsername()).orElse(null);

            id = user == null ? 0 : user.getId();
            JLog.logd(String.format("id = %s", id));

            userNm = user == null ? "" : user.getUserNm();
            JLog.logd(String.format("userNm = %s", userNm));
            jsonResult = objectMapper.writeValueAsString(result);
        } else {
            JLog.logd("사용자 정보를 가져올 수 없습니다.");
            // 사용자 정보가 없을 때 수행할 작업을 여기에 추가

            jsonResult = objectMapper.writeValueAsString(result);
        }

        JLog.logd(String.format("response = %s", jsonResult));

        //Log 저장
        UserActionLog userActionLog = new UserActionLog(id, userNm, ip, params, urlStr, jsonResult);
        userActionLogRepository.save(userActionLog);

    }
}

@AfterReturning 이 실행되는 시점은 대상 메서드가 정상적으로 종료한 후입니다.
상황에 따라 @Before @AfterThrowing @Around 등 사용할 수 있는데 정상적으로 메서드가 동작 한 후 데이터를 다루기 위한 부분이다보니 AfterReturning을 사용하였습니다.

returning = "result" 를 사용하게되면 리턴되는 값을 가져올수 있습니다.
처음에 returning 부분을 사용하지 않았더니.. 아무리 디버깅해봐도 response_data 부분이 안나오더군요..ㅜㅜ
이후 불러와진 값을 저장합니다.

반응형