이 로깅을 어떻게 짜며, 어떻게 활용할 것인가???
- 로그 기록 지점을 식별하고 이해
- 로그를 검색하고 분석하기
- 로그를 기반으로 문제 재현, 해결하기
- 로그 필터링, 키워드 분석, 정규 표현식 등등..
로그 분석과 활용
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와 같은 도구와 연계하여 시각화 분석까지 이어지는 로그 전략이 중요함
'Spring Boot' 카테고리의 다른 글
| 유저 관리 기능 : 인증 워크플로우 (0) | 2025.09.24 |
|---|---|
| 유저 관리 기능 : 유저 기능의 필요성과 활용 (0) | 2025.09.23 |
| 로깅 : 로그 레벨별 활용전략 (1) | 2025.08.21 |
| 로깅 : 환경별 로깅 전략 (6) | 2025.08.13 |
| 로깅 : SLF4J, Logback (5) | 2025.08.12 |