Spring Scheduler 로그 관리 방법: 장애 원인을 빠르게 찾는 기본 구조
Spring Scheduler 로그 관리는 단순히 "스케줄러가 잘 돌고 있나?"를 확인하는 용도가 아닙니다.
백그라운드에서 실행되는 스케줄러 특성상, 웹 요청처럼 사용자가 즉각 에러 화면을 보고 제보해 주지 않습니다. 스케줄러가 늦게 돌았는지, 중간에 실패했는지, 어느 구간에서 병목이 생겼는지 확인하려면 처음부터 로그 구조를 명확히 잡아야 합니다.
장애 발생 시 실행 여부조차 몰라 헤매지 않도록, 실무에서 반드시 구축해야 할 Spring Scheduler 로그 관리 구조와 단계별 점검 방법을 정리합니다.
스케줄러 로그가 부실할 때 생기는 문제
운영 환경에서 로그가 부족하면 다음과 같은 답답한 상황을 마주하게 됩니다.
- 배치성 작업이 정상적으로 실행은 되었는지 실행 자체를 모름
- 스케줄러가 중간에 실패했는데, 로그 시스템에는 아무 기록도 남지 않음
- 전체 처리 시간은 늘어났는데 DB 조회 때문인지, 외부 API 때문인지 구분이 안 됨
- 서버 다중화 환경에서 어떤 인스턴스가 해당 작업을 수행했는지 추적이 불가능함
- DB 저장이 누락되었으나 어떤 데이터(파라미터) 조건에서 오류가 났는지 알 수 없음
결국 스케줄러 장애 추적의 핵심은 시작 시간, 종료 시간, 소요 시간, 성공 여부, 실패 데이터 기준값, 예외 스택 트레이스를 한눈에 볼 수 있도록 구조화하는 것입니다.
왜 Spring Scheduler 로그 관리가 중요할까?
1) 백그라운드에서 '조용히 실패'하기 때문
웹 요청은 에러가 나면 500 에러나 API 응답으로 즉시 티가 납니다. 반면 스케줄러는 예외가 발생해도 시스템이 먹통이 되지 않고 그냥 조용히 다음 주기로 넘어가 버리는 경우가 많습니다. 로그를 제대로 남기지 않으면 결과 데이터가 누락된 것을 한참 뒤에나 발견하게 됩니다.
2) '시간 지연' 장애는 로그 없이는 추적이 불가능하기 때문
단순 에러로 인한 실패보다 처리 속도가 밀리는 지연 장애가 훨씬 까다롭습니다. 작업 자체는 성공했기 때문에 정상인 것처럼 보이지만, 데이터 처리 시점이 계속 밀려 비즈니스 싱크가 어긋나게 됩니다. 이때 타임스탬프 기반의 로그가 없다면 원인 분석이 사실상 불가능합니다.
Spring Scheduler 로그 관리 4단계 가이드
1단계. 시작(Start)과 종료(End) 로그 배치하기
가장 기본이 되는 단계입니다. 로직의 시작과 끝을 감싸는 로그를 의무적으로 작성해야 합니다.
log.info("[scheduler] anomaly job start");
try {
// 핵심 비즈니스 로직
} finally {
log.info("[scheduler] anomaly job end");
}
- 시작 로그는 있는데 종료 로그가 없다면: 로직 중간에 무한 루프에 빠졌거나, 트랜잭션 락이 걸렸거나, 혹은 치명적인 예외로 인해 중간에 멈춘 상태입니다.
- 시작 로그 자체가 찍히지 않았다면: 스케줄러 등록 조건(Cron 표현식 설정 등)이나 서버의 배치 실행 환경 자체를 점검해야 합니다.
2단계. 실행 소요 시간(Elapsed Time) 기록하기
스케줄러가 밀리는 원인을 분석하기 위한 핵심 지표입니다. finally 블록을 활용해 성공, 실패 여부와 상관없이 무조건 소요 시간이 남도록 구현합니다.
long start = System.currentTimeMillis();
try {
log.info("[scheduler] job start");
// 비즈니스 로직 실행
} catch (Exception e) {
log.error("[scheduler] job failed", e);
} finally {
long elapsed = System.currentTimeMillis() - start;
log.info("[scheduler] job end. elapsed={}ms", elapsed);
}
- 평소 5~10초 내외로 끝나던 작업이 특정 시점부터 수 분 이상 소요된다면, 데이터 급증이나 인프라 병목(DB, API, 서버 리소스)을 빠르게 의심해 볼 수 있습니다.
3단계. 구간별(Step) 로그로 병목 지점 분리하기
전체 소요 시간만 가지고는 구체적으로 어느 코드 영역에서 느려졌는지 알 수 없습니다. 주요 트랜잭션이나 외부 연동 구간을 쪼개어 로그를 남겨야 합니다.
log.info("[scheduler] DB select start");
// 대용량 데이터 조회 로직
log.info("[scheduler] DB select end");
log.info("[scheduler] External API call start");
// 외부 API 호출 로직
log.info("[scheduler] External API call end");
- 구간을 분리해 두면, DB 조회 구간이 밀릴 때는 쿼리 튜닝과 인덱스를 점검하면 되고, API 호출 구간이 밀릴 때는 타임아웃(Timeout) 설정이나 재시도 정책을 튜닝하는 쪽으로 명확한 방향을 잡을 수 있습니다.
4단계. 에러 로그 작성 시 '식별 가능한 기준값' 포함하기
단순히 "에러가 발생했다"는 사실보다 "어떤 데이터를 처리하다가 에러가 났는가"가 훨씬 중요합니다. 장애는 보통 특정 데이터의 예외 케이스 때문에 발생하기 때문입니다.
// 잘못된 예시: 어떤 데이터에서 터졌는지 알 수 없음
log.error("scheduler failed");
// 올바른 예시: 대상 식별자와 파라미터를 함께 기록
log.error("scheduler failed. plantId={}, targetDate={}", plantId, targetDate, e);
- 오류가 발생한 타깃 식별자(ID, 날짜, 요청 파라미터 등)를 함께 기록해 두어야 운영 환경에서 수동으로 데이터를 보정하거나 재처리 배치를 돌릴 때 신속하게 대응할 수 있습니다.
실전에서 가장 많이 하는 실수
e.getMessage()만 덜렁 남기는 행위
// 절대 금지
log.error(e.getMessage());
이렇게 로그를 남기면 예외의 한 줄 메시지만 찍히고, 실제 코드가 몇 번째 라인에서 에러를 던졌는지 알려주는 스택 트레이스(Stack Trace)가 완전히 유실됩니다. 디버깅이 불가능해지므로, 반드시 예외 객체 전체를 인자로 넘겨주어야 합니다. (log.error("message", e);)
성공 메시지만 남기고 예외 처리를 먹어버리는 행위
try-catch로 예외를 잡은 뒤, 로그를 남기지 않거나 대충 처리하고 넘어가면 시스템은 성공으로 인지합니다. 실패한 이력과 원인이 기록되지 않는 로그는 장애 방치로 이어집니다.
마무리
안정적인 Spring Scheduler 운영의 첫걸음은 장애가 터지기 전, 코드를 작성하는 단계부터 로그 구조를 설계하는 것입니다.
지금 개발 중인 스케줄러가 있다면 최소한 시작/종료 시점, 총 소요 시간, 실패 시 파라미터와 전체 스택 트레이스가 정확히 남는지 파악해 보세요. 이 기본 구조만 유지하더라도 장애 발생 시 원인 분석 시간이 몇 시간에서 몇 분 단위로 단축되는 효과를 보실 수 있습니다.
'IT > java' 카테고리의 다른 글
| Spring Scheduler 실행 밀림 원인과 5가지 체크리스트 (0) | 2026.06.02 |
|---|---|
| Spring Scheduler 중복 실행 해결 가이드 (0) | 2026.05.26 |
| Docker Health Check 기반으로 Thread.sleep 제거하기 (0) | 2026.03.06 |
| Java로 SSH 접속, Docker 제어, GitHub Actions 트리거 구현기 (0) | 2026.02.26 |
| 원인을 찾지 못한 트랜잭션 에러, HikariCP를 의심하게 되기까지의 기록 (0) | 2026.01.30 |