
스택트레이스에 가장 자주 등장하는 클래스가 범인일 거라고 AI는 말합니다. 근데 ANR의 진짜 범인은 보통 '아무것도 안 하고 대기 중인 스레드'에 있지요.
얼마 전에 Play Console 에서 ANR 발생률이 임계 0.47% 선을 슬슬 건드리길래, 클로드 코드(Claude Code) 와 챗GPT 한테 logcat 덤프를 통째로 던져봤습니다. 의외로 빨리 좁혀주는 영역이 있고, 진짜 엉뚱한 방향을 가리키는 영역이 명확히 갈리더라고요. 6년차 안드로이드(Android) 개발자 입장에서 "AI 한테 먼저 읽혀도 되는 자리" 와 "절대 그러면 안 되는 자리" 를 나눠봤습니다.
AI 크래시 로그 분석, 토큰 빈도 오진이 가장 큰 함정
AI 가 스택트레이스를 읽는 방식은 결국 토큰 빈도 기반입니다. Activity, Handler, Looper 같이 자주 등장하는 클래스가 보이면 거기를 의심하라고 하는 식이지요. 그런데 ANR 의 진짜 원인은 대부분 "활발히 일하는 스레드" 가 아니라 "락을 잡고 멍하니 대기 중인 스레드" 에 있습니다. 빈도가 가장 높은 자리와 범인이 있는 자리가 어긋나는 구조라, 토큰 통계로는 잡히기 어려운 영역이지요.
게다가 AI 는 ANR(응답 없음) 과 Crash(강제 종료) 도 자주 헷갈리더군요. "NPE 로 인한 ANR" 같은 존재하지 않는 조합을 천연덕스럽게 내놓는 경우를 여러 번 봤습니다. ANR 트레이스에는 예외 자체가 안 찍히는데도 그러더군요.
다만 1차 분류 속도는 확실히 빠릅니다. I/O 인지, 바인더(Binder) 대기인지, 락 경합인지 카테고리로 묶는 작업은 손으로 30분 걸리던 게 5분이면 끝나는 일이 많거든요. AI 한테는 '원인' 을 묻지 말고 '먼저 볼 곳' 을 묻는다고 생각하시면 됩니다.

ANR 진단에서 AI 가 빗나가는 오진 패턴 5가지
1. 키워드만 보고 OOM 단정
로그에 OutOfMemoryError 키워드만 있으면 일단 메모리 누수로 결론을 내립니다. 실제로는 메인 스레드가 바인더 응답을 기다리다가 GC 가 한 번 더 돌면서 따라 터진 케이스인데, AI 는 거기까지는 못 보는 일이 많아요.
2. "비동기로 옮기세요" 일반론
SharedPreferences.commit() 남용 같은 구체적인 API 오용이 원인인데, "I/O 를 비동기로 옮기세요" 라는 교과서 답안을 내놓습니다. apply() 한 줄 바꾸면 끝날 일을 코루틴 설계부터 다시 짜라고 하는 식이지요.
3. SystemServer 지연을 앱 결함으로 오인
BinderProxy.transactNative 에서 멈춰 있는 트레이스를 AI 는 거의 항상 "메인 스레드 차단" 으로 분류합니다. 사실은 OEM 시스템 서버 응답이 늦는 케이스라 앱은 무죄인데, 그 차이를 못 잡는 일이 잦아요.
4. Native Crash 의 SIGSEGV 를 NPE 로 단정
주소가 0x0 이면 거의 Java NPE 라고 결론을 내립니다. JNI 참조 누수나 ABI 불일치는 후보군에 들어가지도 못하는 경우가 많은데, 빌드 설정 정보가 같이 들어가지 않으니 당연한 한계예요.
5. 데드락을 '느린 함수' 로 환원
두 스레드가 서로 다른 락을 잡고 마주 본 상황에서, AI 는 그중 한쪽 트레이스에 보이는 특정 함수 하나를 범인으로 지목합니다. 그 함수는 증상일 뿐 원인이 아닌데, 근거 제시 없이 단정해버리니 그대로 믿으면 엉뚱한 곳을 한 시간씩 파게 됩니다.

logcat 분석에서 AI 가 정확히 짚어준 사례 3가지
반대로 잘 맞춘 자리도 있었어요. 메인 스레드에서 commit() 을 호출한 코드는 AI 가 첫 시도에 잡아냈습니다. Parcel.readException 과 BinderProxy.transactNative 조합도 "외부 프로세스 응답 지연" 으로 깔끔히 분류해주더라고요.
데드락 후보 압축도 인상적이었습니다. 두 스레드 트레이스를 나란히 던지면 락 잡는 순서가 어긋난 지점을 90초 안에 짚어주는 일이 많았습니다. 손으로 비교하면 20분 걸리던 작업이지요. 다만 AI 가 내놓은 건 '범인' 이 아니라 '비교 대상' 이라는 점은 짚고 가야 합니다.
mapping.txt 와 전후 logcat 으로 진단 정확도 끌어올리기
같은 AI 한테 같은 로그를 줘도, 입력 형태에 따라 정확도가 크게 갈립니다. 제가 체감한 세 가지 권장 방식은 이렇습니다.
- 난독화된 스택트레이스에는
mapping.txt를 같이 붙여서 클래스명 복원부터 시키세요. - 크래시 시각 전후 30초 logcat 을 함께 던지면 AI 가 추측 대신 실제 라인 번호를 인용하게 됩니다.
- 프롬프트에 "결론의 근거가 된 로그 라인을 인용해 달라" 를 명시하면 출처 없는 단정이 확연히 줄어듭니다.
개인적으로는 클로드 코드에 프로젝트 폴더와 mapping.txt 를 같이 마운트해두고 묻는 방식이 가장 깔끔했습니다. 매번 복붙으로 트레이스를 옮기는 것보다 훨씬 빠르고, 코드 참조까지 같이 들어가니까 추측이 줄어드는 게 체감되더라고요.

AI 에 맡길 일과 개발자가 직접 봐야 할 일
정리하자면 AI 는 '의심 영역을 좁히는 도구' 까지가 한계입니다. 1차 카테고리 분류, mapping.txt 복원, 유사 스택 그룹화, 의심 스레드 후보 선정 — 여기까지는 안심하고 맡겨도 시간을 크게 아낄 수 있어요.
반대로 최종 원인 확정, 네이티브 크래시 분석, 사내 코드가 들어간 보안 민감 영역은 결국 사람이 봐야 합니다. 특히 ANR 은 "트레이스에 안 찍힌 다른 스레드의 상태" 가 답인 경우가 많아서, AI 가 단정한 결론을 그대로 푸시에 반영했다가 며칠 더 헤맨 적이 몇 번 있었어요.
AI 진단을 받을 땐 "그 결론을 어떤 로그 라인에서 봤는지" 를 같이 묻는 습관 하나만 들여도 헛다리 짚는 시간이 절반으로 줄어듭니다. AI 가 근거를 못 내놓으면, 그 진단은 그냥 토큰 빈도 추측이라고 보시면 됩니다.