본문 바로가기

Spring Boot

로깅 : 로그 분석과 활용

이 로깅을 어떻게 짜며, 어떻게 활용할 것인가???

 

 - 로그 기록 지점을 식별하고 이해

 - 로그를 검색하고 분석하기

 - 로그를 기반으로 문제 재현, 해결하기

 - 로그 필터링, 키워드 분석, 정규 표현식 등등..

 

 

로그 분석과 활용

1. 주요 로깅 포인트

애플리케이션 수명주기(LifeCycle) 중 시작과 종료 시점을 로그로 남겨두면, 장애 상황이나 비정상적인 종료시 디버깅이 수월함!

@SpringBootApplication
public class MyApplication {
	private static final Logger log = LoggerFactory.getLogger(Myapplication.class);
    
    public static void main(String[] args) {
    	logger.info("애플리케이션 시작중");
        SpringApplication.run(MyApplication.class, args); // 스프링 부팅
        logger.info("애플리케이션 성공적!");
    }
    
    @Component
    public class ApplicationShutdownHandler {
    	private static final Logger logger = LoggerFactory.getLogger(ApplicationShutdownHandler.class);
        
        @PreDestroy // @PreDestroy를 이용한 종료 훅
        public void onShutDown() {
        	logger.info("애플리케이션 종료중..");
        }
    }
}

 

 

2. 요청 처리의 시작과 종료

HTTP기반 애플리케이션에서는 요청 단위로 로깅하는게 중요함

필터를 사용해서 요청의 시작과 종료를 로그로 남길수도 있다

@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        String requestURI = request.getRequestURI();
        String method = request.getMethod(); // method

        logger.info("요청 시작: {} {}", method, requestURI); // 요청 시작 로그

        try {
            filterChain.doFilter(request, response); // 실제 요청 처리
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            int status = response.getStatus(); // status

            // 상태 코드에 따라 로그 레벨을 다르게 설정
            if (status >= 400) {
                logger.warn("요청 종료: {} {} - 상태 코드: {}, 처리 시간: {}ms", method, requestURI, status, duration);
            } else {
                logger.info("요청 종료: {} {} - 상태 코드: {}, 처리 시간: {}ms", method, requestURI, status, duration);
            }
        }
    }
}

 

 

3. 비즈니스 로직

주문처리, 회원가입, 결제 등의 도메인 핵심 로직에서는 적절한 시점마다 로깅을 추가하여 흐름 추적 및 예외 디버깅을 해야함

@Service
public class OrderService {
	private static final Logger log = LoggerFactory.getLogger(OrderService.class);
    
    @Transactional
    public OrderResult processOrder(OrderRequest orderRequest) {
    	log.info("주문 처리 시작: requestId : {}, 상품수 : {}", orderRequest.getRequestId(),
                      orderRequest.getItems().size());
        
        // 재고 확인
        boolean stockAvailable = checkStock(orderRequest.getItems());
        if(!stockAvailable) {
        	log.warn("재고 부족으로 실패 requestId : {}", orderRequest.getRequestId());
            throw new InsufficientStockException("재고가 부족합니다");
        }
        
        // 결제처리
        log.info("결제 처리중 requestId : {}, 금액 : {}", orderRequest.getRequestId(), 
                 orderRequest.getTotalAmount());
        PaymentResult paymentResult = paymentService.processPayment(orderRequest.getPayment());
        
        // 주문생성
        Order order = createOrder(orderRequest, paymentResult);
        log.info("주문생성완료 orderId : {}, 상태 : {}", order.getId(), order.getStatus());
    }
}

 

사이사이에 넣어줘야 정확한 로깅이 가능하다

 

4. 외부 시스템 연동

DB조회, API호출, Redis 접근 등 외부 리소스에 대한 성능 병목 파악을 위해서 로그가 반드시 필요함

@Repository
public class ProductRepository {
    private static final Logger logger = LoggerFactory.getLogger(ProductRepository.class);

    @PersistenceContext
    private EntityManager entityManager;

    public List<Product> findProductsByCategory(String category, int limit) {
        String jpql = 
          "SELECT p FROM Product p WHERE p.category = :category ORDER BY p.popularity DESC";

        logger.debug("상품 조회 JPQL 실행: category={}, limit={}, query={}",
                    category, limit, jpql); // 쿼리 실행 전 로그

        long startTime = System.currentTimeMillis();
        List<Product> products = entityManager.createQuery(jpql, Product.class)
                                           .setParameter("category", category)
                                           .setMaxResults(limit)
                                           .getResultList();
        long duration = System.currentTimeMillis() - startTime;

        logger.debug("상품 조회 완료: category={}, 결과 수={}, 소요시간={}ms",
                    category, products.size(), duration);

        if (duration > 1000) {
            logger.warn("상품 조회 쿼리 실행 시간 지연: category={}, 소요시간={}ms",
                       category, duration);
        }

        return products;
    }
}

 

 

5. 로깅이 성능에 영향을 줄 수 있다

너무 과도한 로그출력은 성능저하를 유발할 수 있다

// 🛑 좋지 않은 로깅 예시 - 루프 내부에 과도한 로그
public void processItems(List<Item> items) {
    for (Item item : items) { // for문 돌림
        logger.debug("아이템 처리 중: id={}, name={}", item.getId(), item.getName());
        processItem(item);
    }
}

// ✅ 개선된 로깅 예시 - 시작과 끝만 로깅
public void processItems(List<Item> items) {
    logger.debug("아이템 처리 시작: 총 {}개", items.size());

    for (Item item : items) { // for문 돌림
        processItem(item);
    }

    logger.debug("아이템 처리 완료: 총 {}개", items.size());
}

 

로그를 출력하는데 반복문을 돌리는 경우가 있을 순 있는데, 너무 과하게 출력되면 오히려 성능저하를 유발할 수 있음

 

 

로그 검색, 분석

로그는 기록만으로는 의미가 없으며 이 기록을 통해 문제의 원인을 찾아야 비로소 빛을 발한다

로그 검색은 언제 필요한가?

 - 장애가 발생했을 때 원인 확인

 - 사용자의 민원이 발생하면 로그를 추적

 - 응답이 느린곳의 병목 위치 파악

 - 외부 API 오류 확인

 - 예상치 못한 예외가 발생하면 재현 조건을 파악함

 

로그 검색 방법

매우 기본적인 로그 검색법

# 키워드 기반 검색
$grep "ERROR" app.log
$grep "[USER_CREATE]" app.log

# 시간 범위를 지정
$ awk '/2024-12-01 10:00/,/2024-12-01 11:00' app.log # 로그가 타임스탬프 포함시

# 특정 사용자의 ID 로그 찾기
$ grep "userId=123" app.log

 

 

정규 표현식을 활용한 고오급 검색

# userId가 숫자인 로그만 추출하기
$ grep -E "userId=[0-9]+" app.log

# 특정 모듈에서 발생한 WARN 이상 로그 찾기
$grep -E "\\[(USER|ORDER)_.*\\]" app.log | grep -E "WARN|ERROR"

 

 

로그 포맷을 기준으로 분석하기

// 예시
2025-08-22 00:59:01.34 [http-nio-8080-exec-1] INFO c.e.UserService - [USER-CREATE] 
사용자 저장 완료 : id=15 username=Alice

 

 

* 로그에는 가능한 사용자 ID, 요청 ID, 트랜잭션 ID를 포함하는게 좋음

* 로그 메시지는 일관된 형식을 유지해야한다

* 복잡한 시스템에서는 로그 상관관계를 분석하기 위해 추적 ID??(correlationId)를 활용하는게 효과적임

* 실무에서는 mdc, 로그 패턴 설계, 로그 레벨 조정 등을 통해 더욱 구조화된 로그를 구성해야하며

ELK와 같은 도구와 연계하여 시각화 분석까지 이어지는 로그 전략이 중요함