Java 앱 크래시, 어떻게 감지하고 알림을 받을까? — 크래시 리포팅 구축 체크리스트
1. 문제 정의
운영 중인 Java 애플리케이션이 어느 날 죽었지만, 개발자는 뒤늦게서야 사용자 신고를 통해야 알게 됐다. “이미 JVM이 오류를 기록해 두지 않았나?” 하는 걱정이 들지만, 기록(log)과 감지·알림(alert)은 전혀 다른 일이다. 크래시가 실제로 발생했는지, 어디에 흔적이 남는지, 그리고 그 흔적을 누가 확인하는지가 끊겨 있으면 아무리 자세한 로그를 남겨도 아무도 모른 채 지나간다.
이 글의 출처(Stack Overflow 880179)는 **“Java용 크래시 리포팅 시스템이나 이를 쓰는 오픈소스 프로젝트를 아는가”**라는 질문으로 시작한다. 단순히 “좋은 도구 하나 추천해 달라"는 질문이지만, 답을 살펴보면 실제로는 감지하고 알림을 받는 체계를 세우는 문제가 본질이라는 점을 알 수 있다.
2. 빠른 진단 체크리스트
크래시 리포팅 체계를 구축(또는 점검)할 때 아래 4가지를 순서대로 확인하면 현재 어느 구간이 비어 있는지 파악할 수 있다. 각 항목은 “확인 명령"과 연결되어 있다.
| 구간 | 확인 질문 | 비어 있다면 |
|---|---|---|
| 기록 | JVM 크래시 흔적이 남는가? (hs_err_pid, 로그파일) | JVM 크래시 덤프 경로·파일 로테이션 점검 |
| 감지 | 그 흔적을 자동으로 읽는 프로세스가 있는가? | 스크립트/모니터링 에이전트 도입 |
| 알림 | 감지 시 누구에게 전달되는가? (메일·메신저) | 알림 채널 연동 |
| 통합 | 데스크톱(Swing 등) 앱은 화면에서 표시하는가? | UncaughtExceptionHandler·콘솔 패널 구성 |
3. 증상 fingerprint (증상 지문)
크래시를 판단할 때 자주 헷갈리는 두 가지 “증상"이 있다. 둘은 감지 경로가 다르므로 먼저 구분해야 한다.
- JVM 레벨 크래시(진짜 프로세스 종료): native 코드 오류, JVM 내부 fatal error, native 메모리 부족으로 프로세스가 죽는 경우. 일반적으로
hs_err_pid<pid>.log같은 크래시 덤프가 프로세스가 실행되던 디렉터리에 텍스트 파일로 남는다. 홈 디렉터리·프로세스 작업 디렉터리에 파일이 생기므로 “파일 존재 여부"로 감지할 수 있다. - 자바 예외(JVM은 살아 있음): 애플리케이션 스레드 안에서
Exception/Error가 발생하고 미처리된 채 스레드가 종료되는 경우. 프로세스 전체는 살아 있으므로 크래시 덤프보다는 미처리 예외 핸들러나 로그로 감지해야 한다.
이 두 가지를 구분하지 않으면 “크래시 덤프를 못 찾는” 쪽(JVM 크래시)에 집중하다가 실제로는 자바 예외가 쌓이고 있는 경우를 놓치기 쉽고, 그 반대도 마찬가지다.
4. 원인 후보 매트릭스 (root cause)
“알림을 못 받는” 상황 뒤에는 여러 원인이 있을 수 있다. 아래 매트릭스로 하나씩 확인하면서 배제하면 된다.
| 원인 후보 | 확인 명령 / 확인 방법 | 맞다면 보이는 증상 | 해결 방향 |
|---|---|---|---|
| 크래시 덤프가 남지 않음 | ls -la hs_err_pid*.log (작업 디렉터리 확인) | 덤프 파일이 아예 없음 | -XX:ErrorFile로 덤프 경로 명시, 쓰기 권한 확인 |
| 흔적은 남지만 감지 주체 없음 | 스케줄러·모니터링 에이전트 존재 여부 확인 | 덤프 파일은 쌓이는데 아무도 안 봄 | 위치를 주기적으로 검사하는 스크립트/에이전트 추가 |
| 감지는 되는데 알림 채널 미연동 | 스크립트에 메일/알림 호출이 있는지 확인 | “감지됨” 로그만 있고 알림 발송이 없음 | 메일 발송·메신저 webhook 연동 |
| 자바 예외는 예외 핸들러 미설정 | setDefaultUncaughtExceptionHandler 설정 여부 확인 | 스레드 예외가 조용히 삼켜짐 | 미처리 예외 핸들러로 로그·알림 연결 |
| 데스크톱 앱이라 사용자 화면 표시 필요 | Swing/AWT 콘솔·메시지 패널 여부 확인 | 사용자가 오류를 “말로"만 전달 | zeus-jscl 等의 화면 패널로 스택트레이스 노출 |
5. 해결책 비교와 선택 이유
출처 답변과 일반적인 실무 관점을 종합하면 크게 세 가지 접근으로 나뉜다. 반드시 하나만 고를 필요는 없고, 크래시의 종류에 따라 섞어 쓸 수 있다.
| 접근 | 감지 대상 | 장점 | 단점 | 적합한 상황 |
|---|---|---|---|---|
| 크래시 덤프 감지 스크립트 | JVM 레벨 크래시 | 별도 인프라 불필요, 텍스트만 다루면 됨 | 스크립트를 직접 만들어야 함 | 서버 앱, 미니멀 환경 |
| 데스크톱 오류 표시 라이브러리 (예: zeus-jscl) | Swing 데스크톱 앱 예외 | 사용자 화면에서 즉시 스택트레이스 노출 | 로컬 편의에 그침, 서버 집계 아님 | 데스크톱 GUI 앱 |
| 운영 통합 모니터링 | 자바 예외 + 시스템 건강 | 중앙 집계·대시보드·알림까지 종합 | 도입·운영 비용 존재, 설정 필요 | 운영 규모가 커진 서비스 |
왜 “크래시 덤프 감지 스크립트"를 첫 접근으로 채택했나
출처에서 채택(accepted)된 답변은 첫 번째 접근이며, 원문 그대로의 핵심은 다음과 같다.
“The crash dump is just a text file. You could write a script which repeatedly runs your problem and mail you/notifies you of any new crash dumps.” (크래시 덤프는 그저 텍스트 파일이므로, 프로그램을 반복 실행하면서 새 크래시 덤프가 생기면 메일로 알려주는 스크립트를 짜면 된다.)
이 접근을 첫 해결책으로 채택한 이유는 다음 세 가지다.
- 의존성이 없다 — 애플리케이션 코드를 고치지 않고, 추가 프로세스 설치 없이 운영 체제의 스케줄러(cron)와 쉘 스크립트만으로 동작한다. 운영 초기나 미니멀 환경에서 가장 빠르게 적용할 수 있다.
- 출처가 명시적으로 제시한 방식 — 원문이 “스크립트로 새 크래시 덤프 감지 → 알림"을 직접 답으로 제시하고 있어, 번역·각색이 아닌 원본 사실에 근거한다.
- JVM 레벨 크래시는 예외 핸들러로는 잡을 수 없다 — 프로세스가 죽어 버리는 native/fatal 크래시는 애플리케이션 안의
try/catch나 예외 핸들러로 감지할 수 없다. 오직 프로세스 밖에서 “덤프 파일이 생겼나"를 관찰하는 방식만이 진짜 프로세스 종료를 잡아낸다.
여기에 대안들에 대한 실행·판단 착오를 미리 짚어둔다. (1) 데스크톱 표시 라이브러리는 서버 크래시 감지를 대체하지 못한다 — zeus-jscl 류는 출처에도 “보내는 곳은 없고 로컬 편의"라고 명시되어 있듯 원격 집계·알림이 아니다. (2) 운영 모니터링은 기능이 많지만 그만큼 설정 부담이 있어, 운영 초기에 감지 경로 하나도 없는 상황에서 한 번에 도입하기에는 과하고 오히려 감지 사각지대가 생길 수 있다. (3) 예외 핸들러만으로 “크래시 리포팅"을 완성했다고 오해하면 안 된다 — 프로세스가 살아 있는 자바 예외만 잡고, JVM이 죽는 크래시는 여전히 놓친다.
6. 실제 재현 결과 (본인 검증 — 실행착오 포함)
출처의 첫 접근(감지 스크립트)과 예외 핸들러를 실제로 실행해 동작을 검증했다. 검증 환경은 §8에 명시했다. 아래는 실제 터미널 출력이다.
6-1. 자바 미처리 예외 → 전역 핸들러 (OpenJDK 17)
데몬 스레드에서 try/catch 없이 예외가 미처리 상태로 종료되는 상황을 재현하고, Thread.setDefaultUncaughtExceptionHandler가 호출되는지 확인했다.
| |
실제 실행 결과:
| |
→ 미처리 예외가 실제로 전역 핸들러로 전달됨을 확인했다 (handlerHits=1). 검증 환경: OpenJDK 17.0.20.
6-2. 크래시 덤프 감지 스크립트 — 최초 구현은 실패해서 수정했다 (실행착오)
출처 답변이 제시한 “텍스트 파일을 주기적으로 검사해 새 덤프가 생기면 알림"을 쉘로 옮긴 첫 버전은 find -newer가 의도대로 동작하지 않아 신규 덤프를 전혀 감지하지 못했다.
첫 버전의 문제 코드:
| |
실제 실행 실패 양상 (기준 파일을 touch로 갱신한 뒤 새 덤프를 만들어도 감지 안 됨):
| |
실행착오 원인: touch "$KNOWN_FILE"이 스크립트 실행 시작마다 기준 파일의 mtime을 “지금(가장 최신 시각)“으로 덮어써서, find -newer "$KNOWN_FILE"이 “기준 파일보다 최신인 파일"을 찾게 되는데 기준이 항상 최신이므로 어떤 덤프도 -newer 조건을 통과하지 못한다. 즉 기준 mtime을 갱신하는 시점이 잘못된 것이다.
수정: 기준 파일은 “마지막 검사가 끝난 시점"을 나타내야 하므로, touch를 두 가지로 바꿨다. (1) 파일이 없을 때만 처음 한 번 초기화하고, (2) 검사 루프가 끝난 뒤에만 mtime을 갱신한다.
수정 후 4단계 실행 검증:
| |
실제 실행 결과 $ cat state/hs_err_seen.list (제대로 추적됨):
| |
→ 신규 덤프만 알리고, 이미 본 덤프는 반복 경고하지 않는 동작을 검증했다. 이 실행착오는 곧 ‘감지는 되는데 정작 감지가 안 되는’ 흔한 함정의 실물 사례로, 프로덕션에 그대로 배포하면 조용히 실패하는 위험을 보여준다.
7. 코드 해결책 (검증된 최종 구현)
7-1. 크래시 덤프 감지 스크립트 (검증 완료)
| |
cron으로 주기 실행하는 예:
| |
7-2. 자바 미처리 예외 핸들러 (검증 완료)
§6-1의 CrashProbe.install()이 그대로 적용 가능한 뼈대다. 전역 핸들러는 try/catch로 명시적으로 처리되는 예외가 아닌, “미처리” 상태로 스레드를 종료시키는 예외만 받는 것이 설계 의도다.
| |
실제 운영에서는 로그 라이브러리/모니터링 SDK의 예외 캡처 API로 교체하는 것이 일반적이다. 단, JVM 레벨 크래시(프로세스 종료)는 이 핸들러로 잡을 수 없으므로 §7-1의 덤프 감지 스크립트와 병행해야 한다.
8. 검증 환경
아래는 본문의 “재현 결과”(§6)를 실제로 실행한 환경이다. Java 런타임과 컴파일러는 Ubuntu 저장소에서 설치했고, 스크립트는 GNU bash로 실행했다.
| 항목 | 값 |
|---|---|
| Java 런타임 | OpenJDK 17.0.20 (build 17.0.20+8-1-22.04-Ubuntu) |
| Java 컴파일러 | javac 17.0.20 |
| JVM | OpenJDK 64-Bit Server VM (HotSpot) |
| OS | Ubuntu 22.04 (jammy) |
| 커널/아키텍처 | Linux 6.8.0-1049-oracle, aarch64 |
| 셸 | GNU bash 5.1.16 |
| 메일 MTA | 미구성 → 감지 스크립트의 알림은 notify()(echo)로 대체해 동작 검증 |
Java 크래시 덤프(
hs_err_pid)의 정확한 파일명·경로·알림 채널은 JVM 버전과 실행 OS, 그리고 배포 환경에 따라 달라질 수 있다. 위 환경은 “동작 원리"를 검증하기 위한 것이며, 운영 환경과 동일한 구성을 보장하지는 않는다.
9. 버전별 차이 (호환성 주의)
크래시 리포팅을 구축할 때 신경 쓸 JVM 버전·API 차이를 정리한다. 아래 내용은 일반적으로 알려진 기준이며 환경에 따라 다를 수 있으므로 실제 사용하는 JVM 버전에서 재확인해야 한다.
| 항목 | Java 5 미만(구형) | Java 5~8 | Java 9~11 | Java 17+ |
|---|---|---|---|---|
Thread.setDefaultUncaughtExceptionHandler | 없음 (커스텀 ThreadGroup으로 uncaughtException 오버라이드 필요) | 제공됨 | 그대로 제공 | 그대로 제공 (API 안정) |
| 미처리 예외 감지 기본 수단 | ThreadGroup.uncaughtException | 전역 핸들러 | 전역 핸들러 | 전역 핸들러 |
| 크래시 덤프 기본 파일명 | hs_err_pid<pid>.log | 동일 | 동일 (경로는 -XX:ErrorFile로 변경 가능) | 동일 |
-XX:ErrorFile 경로 지정 | 지원 | 지원 | 지원 | 지원 |
CreateCoredumpOnCrash | OS ulimit에 의존 | 제어 플래그 존재 | JDK 10+ 도입·기본 true | 기본 true |
주요 포인트:
- 예외 핸들러 API:
Thread.setDefaultUncaughtExceptionHandler는 Java 5에서 도입됐다. 그 이전 JVM에서는ThreadGroup을 상속해uncaughtException을 오버라이드하는 방식이었다. Java 5 이후로는 API가 안정적이라 기존 코드를 그대로 재사용할 수 있다. - 크래시 덤프 파일명·경로: JVM이 native/fatal 크래시 때 남기는
hs_err_pid<pid>.log명명과 “작업 디렉터리” 기본 위치는 오랜 기간 유지됐다. 이 경로는-XX:ErrorFile옵션으로 변경할 수 있으며(예:-XX:ErrorFile=/var/log/java/hs_err_pid%p.log),%p(pid),%t(타임스탬프) 확장자를 지원한다. 실제 측정(OpenJDK 17)에서 기본ErrorFile은 비어 있어 작업 디렉터리 기준hs_err_pid이름을 사용함을 확인했다. CreateCoredumpOnCrash: 네이티브 크래시 시 코어 덤프 작성 여부를 제어하는 플래그로, JDK 10/11 시점에 도입되어 현대 JDK에서는 기본true다. 측정(OpenJDK 17) 결과CreateCoredumpOnCrash=true.- 데이터 수집 관점: Java 9+의 통합 로깅(
-Xlog) 등장과 별개로hs_err_pid덤프 생성 동작은 유지되지만, 세부 옵션명이 바뀐 것들이 있어(예: 일부-XX플래그가-Xlog로 이동) 크래시 분석 옵션은 JVM 버전에 맞춰 확인해야 한다. OutOfMemoryError구분: 자바 힙OutOfMemoryError는 잡을 수 있는 예외지만, native 메모리 부족으로 JVM이 fatal error를 내는 경우는-XX:+CrashOnOutOfMemoryError(JDK 7u/8 이후)로 덤프를 남기고 죽도록 만들 수 있다. 감지 대상이 어느 쪽인지에 따라 전략이 달라진다.
10. 검증 명령 (verification)
구축 후 “실제로 동작하는지"를 확인하는 명령이다. 환경에 따라 다를 수 있으므로 결과 기준(exit code / 출력)으로 판단한다.
| |
검증 명령 1~2는 JVM 설정과 대상 버전을 확인하기 위한 일반적인 점검 기준이고, 3~4는 “알림/핸들러가 정말 나가는가"를 확인하기 위한 실전용 체크다. 주의: 명령 3의 크래시 덤프 감지 스크립트는 §6-2에서 지적한 대로, 실행 중간에 기준 파일을 touch하면 신규 덤프를 놓치므로 검증된 구현(§7-1)을 그대로 사용해야 한다.
11. 잘못된 해결책 (임시방편에 주의)
- 크래시 덤프 파일을 그냥 무작정 켜 놓고 매뉴얼로 확인 — 덤프가 쌓이는 것과 감지·알림은 별개다. 파일이 생겼는지 아무도 안 보면 소용없다.
- 미처리 예외를 전역 핸들러 하나로 “삼키기” — 예외를 잡아서 아무것도 안 하거나 표준출력에만 찍는 것은 운영에서 알림의 부재와 같다. 반드시 로그/알림에 연결해야 한다.
- 운영 도구 없이 데스크톱 표시만으로 운용 — zeus-jscl 류의 로컬 표시는 데스크톱 사용자 편의 목적이며, 출처에도 “보내는 곳은 없고 로컬 편의"라고 명시되어 있다. 서버 크래시 탐지를 이걸로 대체하면 안 된다.
- 검증 없이 감지 스크립트를 그대로 배포 — §6-2의 실행착오(기준 파일을 실행 중간에
touch)처럼, 스크립트가 “조용히 실패"하면 감지·알림이 전혀 안 되는 채로 운영된다. 배포 전에 반드시 임의의 덤프 파일을 만들어 알림이 나가는지 확인하라. - 예외 핸들러만으로 “크래시 리포팅 완성"으로 착각 — 프로세스가 죽는 JVM 크래시는 예외 핸들러로 잡을 수 없다. 덤프 감지 경로와 병행해야 한다.
12. 재발 방지 체크 (운영 예방책)
- 크래시 덤프 경로와 파일 로테이션을 정기적으로 점검한다 (디스크 가득 참 방지).
- 미처리 예외 핸들러가 있는지, 그리고 그 핸들러가 실제 알림 채널과 연결됐는지를 CI/코드리뷰에서 확인한다.
- 감지 스크립트의 기준 파일(seen 목록)이 영속 위치에 저장되는지 확인하고, 재부팅 후에도 중복·누락 없이 동작하는지 테스트한다.
- 데스크톱 앱이라면 스택트레이스를 사용자 화면에 보여주는 동시에 백그라운드에서도 기록이 남도록 둘을 함께 구성한다.
- 크래시가 잦은 버전 업데이트 후에는 꼭 §10의 검증 명령으로 “감지 스크립트가 신규 덤프를 알리는지, 예외 핸들러가 호출되는지"를 한 번 재실행한다.
- 운영 모니터링 도구로 확장할 때는 자바 예외 수집과 JVM 성능 메트릭을 한 곳에 모아 대시보드로 봐야 골든 시그널을 놓치지 않는다.
13. DevTrace verdict
크래시 리포팅은 “좋은 도구 하나"로 끝나는 문제가 아니라, 미처리 예외(자바 예외 경로)와 크래시 덤프(JVM 레벨 경로)라는 서로 다른 종류의 크래시를 각각 감지하고 알림에 이어주는 것이 본질이다. 가장 간단한 출발점은 채택 답변대로 “크래시 덤프는 텍스트 파일이므로 신규 파일을 스크립트로 감지해 알리는 것"이며, 본문에서 검증한 것처럼 이 감지 스크립트는 여러 번의 조정(특히 기준 파일 mtime 관리) 없이는 조용히 실패할 수 있으므로 반드시 테스트로 검증해야 한다. 그 위에 자바 예외 핸들러와 운영 모니터링을 상황에 맞게 얹으면 된다.
출처: StackOverflow 880179 — Crash reporting for java (채택 답변: Peter Lawrey / 그 외 답변: Boris Daich, 2009)
본문에서 별도로 실행·검증한 재현(§6)과 JVM 옵션 측정(§8·§9)은 OpenJDK 17(Ubuntu 22.04, aarch64) 환경에서의 결과이며, JVM 버전·OS·배포 환경에 따라 구체적인 파일 경로·알림 채널·덤프 동작은 달라질 수 있다. 원문 출처에 없는 항목은 일반적인 점검 기준 또는 환경 의존 내용으로 구분해 서술했다.