본문 바로가기

Spring Boot

로깅 : 로깅(Logging)의 필요성에 대해

로깅(Logging)

로그를 남기는 것

 

 - 로깅이 필요한 이유

 - 로깅 위치, 메시지 수준 선정 전략

 - 디버깅용, 검사용 로그의 차이점

 - 파일 기반 로깅 및 외부 시스템 연동 구조에 대해

 

 

로깅

애플리케이션이 실행되는 동안 발생하는 다양한 정보를 기록해서 저장하는 행위

예외가 발생했을 때 이유를 확인하고 보안 검사를 위해 기록할때도 쓰임

목적  
애플리케이션 동작 상황 파악 시스템이 어떻게 처리되고 있는가?
문제 상황 분석 예외가 발생했을 때 어떻게 발생했고, 가장 중요한 원인이 무엇인가?
비즈니스 이벤트 추적 사용자 로그인, 주문 생성 등 중요 통계 기록 가능
보안 검사 누가 어느때, 어느 데이터에 접근했는가?

 

 

로깅 예시

1. 애플리케이션 동작 상황 파악하기(서비스임)

@Service // 서비스임
public class UserService {
	// Logger타입의 필드 생성
	private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public User getUserById(Long id) {
    	logger.debug("getUserById 메서드 호출 : id : {}", id);
    }
    
    // 메서드 실행 시간을 체크함
    long startTime = System.currentTimeMillis(); // 먼저 startTime을 생성하고
    User user = userRepository.findById(id).orElse(null); // user객체를 생성한 다음에
    long endTime = System.currentTimeMillis(); // endTime을 생성한다
    
    if(user != null) {
    	logger.debug("사용자 조회 완 : {}, 소용시간 : {}ms", user.getUsername(),(endTime - startTime));
    } else {
    	logger.warn("ID가 {}인 사용자를 찾을 수 없음", id);
    }
    
    return user;
}

 

debug()의 {} 안에 들어갈 인자는 콤마(,)를 통해 구분해서 지정할 수 있나보다. 마치 printf() 같은 느낌

 

 

2. 문제 상황 분석과 해결하기(컨트롤러임)

@Controller // 컨트롤러임
public class ProductController {
    // Logger 필드 생성
	private static final Logger logger = LoggerFactory.getLogger(ProductController.class);
    
    @GetMapping("/products/{id}")
    public String getProduct(@PathVariable Long id, Model model) {
    	try {
        	Product product = productService.getProductById(id); // 서비스 불러옴
            model.addAttribute("product", product); // 모델
            logger.info("제품 페이지 조회 성공! id = {}, name = {}", id, product.getName());
            return "product/detail";
        } catch(ProductNotFoundException e) { // 첫번째 catch문
        	logger.warn("존재하지 않는 제품이에요! id = {}",id, e); // id가 {}안에 들어간다
            return "product/not-found";
        } catch(Exception e) { // 두번째 catch문
        	logger.error("제품 조회 중 예상치 못한 오류 발생! id = {}", id, e);
            return "error/500";
        }
    }
}

 

try - catch문에서 catch문이 2개 들어가도 된다는걸 방금 알게 되었다.

오...

 

3. 비즈니스 이벤트 추적 및 기록(로그), 서비스임

@Service // 서비스임
public class OrderService {
	private static final Logger logger = LoggerFactory.getLogger(OrderService.class); // 필드
    
    @Transactional
    public Order placeOrder(OrderRequest orderRequest, User user) { // orderRequest, user를 받음
    	logger.info("주문시작: 사용자 = {}, 상품 수 = {}, 총액 = {}",
        	user.getUsername(),
            orderRequest.getItems().size(),
            orderRequest.getTotalAmount()
        ); // 로직을 실행한다. 빵 틀임 재료 안들어감
        
        Order order = new Order();
        // 핵심 로직이 들어가는 곳, 주문 처리
        
        logger.info("주문 완료 : 주문번호 = {}, 사용자 = {}, 결제방법 = {}",
        	order.getOrderNumber(),
            user.getUsername(),
            order.getPaymentMethod()
        );
        
        return order;
    }
}

 

로그를 남기는 형식은 정해져있는 것 같다. 숙지하면 잘 쓸 수 있을 것 같은 기분

 

4. 보안 검사(audit), 서비스임

@Service // 서비스임
public class UserSecurityService { // User에 대한 보안 로직은 담당하는 서비스같음
    // 생각해보니 Logger 필드는 항상 private static final이 붙는구나
	private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY_AUDIT");
    
    public void login(String username, String inAddress) {
    	sequrityLogger.info("사용자 로그인 : username = {}, ip = {}, time = {}",
        	username, ipAddress, LocalDateTime.now());
    }
    
    public void accessSensitiveData(Data data, String dataType) {
    	securityLogger.info("민감한 데이터 접근 : username = {}, dataType = {}, time = {}",
        	user.getUsername(), dataType, LocalDateTime.now());
    }
    
    public void changeUserRole(User admin, User targetUser, String newRole) {
    	securityLogger.info("사용자 권한 변경 : admin = {}, targetUser = {}, newRole = {}, time = {}",
        	admin.getUsername(), targetUser.getUsername(), newRole, LocalDateTime.now());
    }
}

 

서비스로 따로 관리한다

 

* 로그는 검사용, 디버그용, 보안검사용으로 목적이 다르면 다르게 구성해야한다

* 메세지 레벨(INFO / WARN / ERROR)을 명확하게 구분해서 사용해야함

* 로그에 민감한 정보(비밀번호, 카드번호 등)는 절대 기록되지 않게 해야함