삽질 피하기

[삽질 피하기] 로그 - 히스토리 기능 (AOP)

ozofweird 2021. 4. 21. 16:27

1. 로그 (AOP)

1) MainApplication

package com.example.jpa.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling // 스케쥴러 설정
@EnableAspectJAutoProxy // aop 동작 설정
@SpringBootApplication
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }

}

2) Logs

히스토리를 저장할 Entity, Repository, Service를 구현해준다.

...

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Logs {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String text;

    @Column
    private LocalDateTime regDate;
}
...

import org.springframework.data.jpa.repository.JpaRepository;

public interface LogsRepository extends JpaRepository<Logs, Long> {
}
...

public interface LogService {

    void add(String text);

    void deleteLog();
}
...

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@RequiredArgsConstructor
@Service
public class LogServiceImpl implements LogService {

    private final LogsRepository logsRepository;

    @Override
    public void add(String text) {

        logsRepository.save(Logs.builder()
                .text(text)
                .regDate(LocalDateTime.now())
                .build());
    }

    @Override
    public void deleteLog() {
        logsRepository.deleteAll();
    }
}

3) Logger

로그-히스토리 기능은 주로 AOP 기술을 활용한다.

...

import lombok.RequiredArgsConstructor;
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.stereotype.Component;

@Slf4j // 로그 찍어보는 용도로 사용
@RequiredArgsConstructor
@Aspect
@Component
public class LoginLogger {

    private final LogService logService;

    @Around("execution(* com.example.jpa.sample..*.*Service*.*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {

        log.info("#################################");
        log.info("서비스 호출 전");

        Object result = joinPoint.proceed();

//        joinPoint.getArgs() : 서비스 호출시 넘기는 파라미터
//        joinPoint.getSignature() : 메시지 정보 (ex. 메서드의 경우 getDeclaringTypeName(), getName() 사용)

        if("login".equals(joinPoint.getSignature().getName())) {

            StringBuilder sb = new StringBuilder();

            sb.append("\n");
            sb.append("함수명: " + joinPoint.getSignature().getDeclaringTypeName() + ", " + joinPoint.getSignature().getName());
            sb.append("\n");
            sb.append("매개변수: ");

            Object[] args = joinPoint.getArgs();
            if(args != null && args.length > 0) {
                for(Object o : args) {
                    if(o instanceof UserLogin) {
//                        String email = ((UserLogin)o).getEmail();
//                        String password = ((UserLogin)o).getPassword();
                        sb.append(((UserLogin)o).toString()); // @ToString

                        sb.append("리턴값: " + ((User)result).toString());
                    }
                }
                logService.add(sb.toString());
                log.info(sb.toString());
            }
        }

        log.info("#################################");
        log.info("서비스 호출 후");

        return result;
    }


}
...

import lombok.RequiredArgsConstructor;
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.stereotype.Component;

@Slf4j // 로그 찍어보는 용도로 사용
@RequiredArgsConstructor
@Aspect
@Component
public class BoardLogger {

    private final LogService logService;

    @Around("execution(* com.example.jpa.sample..*.*Controller.detail(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {

        log.info("#################################");
        log.info("컨트롤러 Detail 서비스 호출 전");

        Object result = joinPoint.proceed();

        if(joinPoint.getSignature().getDeclaringTypeName().contains("ApiBoardController")
            && "detail".equals(joinPoint.getSignature().getName())) {

            StringBuilder sb = new StringBuilder();
            sb.append("파라미터: ");

            Object[] args = joinPoint.getArgs();
            for(Object o : args) {
                sb.append(o.toString());
            }

            sb.append("결과: ");
            sb.append(result.toString());
            log.info(sb.toString());

            logService.add(sb.toString());
        }


        log.info("#################################");
        log.info("컨트롤러 Detail 호출 후");

        return result;
    }


}

4) application.yml

별도로 로그를 저장을 할 수 있다.

logging:
  level:
    org.hibernate.SQL: trace
    org.hibernate.type: trace

  file:
    name: ./logs/spring-jpa.log
728x90