Repository navigation
ErrorReporter 계측 신설 — 실패가 Logcat 에도 텔레메트리에도 안 남는다 #964
Description
#928 에서 분할된 이슈다. 인프라 전제·제약·판단 기준을 아래에 그대로 복제해 두었으니 이 이슈만 보고 진행하면 된다. 다른 모듈은 건드리지 않으므로 나머지 두 이슈와 파일이 겹치지 않는다.
배경
Result.failure 로 흡수되는 실패는 Crashlytics 자동 수집(uncaught 예외)에 잡히지 않는다. ErrorReporter 를 거쳐 명시적으로 기록해야 릴리즈에서 보인다.
feature/mindrecord 는 전 모듈 중에서도 특이하다 — Log.e 도 ErrorReporter 도 0건이다. 실패가 UI 상태로만 흡수되고 어디에도 기록되지 않아, 릴리즈에서 무슨 일이 있었는지 알 방법이 전혀 없다.
app·feature/onboarding·feature/afternote/presentation 은 이미 채택이 끝났다(30곳). 이 모듈은 계측을 처음부터 세우는 작업이다.
인프라 — 새로 만들 것이 없다
ErrorReporter를 생성자에 주입하면 끝이다. 정책(코루틴 취소 제외·예외 문구 redact)은 인터페이스가 이미 지킨다 — 호출부에서 다시 거를 필요 없다- 실제 Crashlytics 결선은
app의ErrorReportingModule에만 있다 — 이 모듈에 Firebase 의존을 추가하지 않는다. 주입받는 타입은 항상ErrorReporter추상이다 - stage 어휘는 모듈마다 따로 둔다(도메인 어휘라서 의도된 분산이다). 선례 3개:
HomeFailureReporting.kt—appAfternoteFailureReporting.kt—feature/afternoteAuthFailureReporting.kt—feature/onboarding
제약 — 일괄 치환 금지
Crashlytics 는 non-fatal 을 최근 8건만 보관하고 초과분을 오래된 것부터 버린다 (코드 근거 · Firebase 문서).
전부 recordFailure 로 갈아끼우면 네트워크가 한 번 끊길 때 잡음이 한도를 채워 실제 장애를 밀어낸다. 이 작업은 "어떤 실패가 텔레메트리 가치가 있는가" 를 골라내는 분류이지 치환이 아니다.
판단 기준
- 승격(ErrorReporter) — 사용자 작업이 실패했고 서버·기기 문제가 의심되며, 재현이 어려워 실기 QA 로 못 잡는 것 (저장 실패, 세션 만료, 업로드 실패)
- 유지(Logcat) — 개발 중 흐름 추적용이고 정상 경로에서도 발생 가능한 것 (지문 인증 취소 등)
- 삭제 — 이미 상위에서 처리되어 중복이거나, 디버깅이 끝난 흔적
승격한 지점은 어느 단계에서 깨졌는지 구분할 stage 속성을 함께 넘긴다. 개인정보·자격증명은 넣지 않는다.
작업 — 계측 지점 선정 후 MindRecordFailureStage 신설
데이터 계층(DailyQuestionRepositoryImpl 등)이 runCatching 으로 감싸 Result 를 돌려주므로, 계측 지점은 ViewModel 의 onFailure 다. 아래는 실측한 후보 전량이다.
쓰기 실패 — 승격 1순위
사용자가 작성한 내용이 날아가는 경로라 재현이 어렵고 실기 QA 로 못 잡는다.
-
DiaryWriteViewModel.kt#L127— 일기 제출 실패(SubmitState.Failed) -
DiaryWriteViewModel.kt#L182 -
DailyQuestionWriteViewModel.kt#L167— 데일리질문 답변 저장 -
DraftListViewModel.kt#L106— 임시저장 목록. 임시저장은 fix(mindrecord): 데일리질문 임시저장이 저장 후 접근 불가 — today 응답 draft 플래그 미파싱·이어쓰기 부재 #519 에서 실제 결함이 있었던 경로다
읽기 실패 — 선별 승격
목록 로드 실패는 흔해서 전부 넣으면 한도(최근 8건)를 잡음으로 채운다. 화면 단위로 골라낸다.
-
DailyQuestionWriteViewModel.kt#L50— 질문 로드 실패(questionLoadError) -
DiaryListViewModel.kt#L106 -
WeeklyReportViewModel.kt#L112— 주간 리포트 -
ReceiverMindRecordViewModel.kt#L63— 수신자 열람 실패. 유가족이 못 여는 상황이라 읽기지만 승격 가치가 높다
취소 혼입 주의
DailyQuestionListViewModel.kt#L78 · DiaryListViewModel.kt#L100 · WeeklyReportViewModel.kt#L104 에 "repository 가 runCatching 으로 감싸 CancellationException 까지 실패로 바꿔 돌려준다" 는 주석이 이미 달려 있다.
ErrorReporter.recordFailure 는 이 취소를 창구에서 걸러 주므로 호출부에서 따로 막지 않아도 된다. 다만 화면 이탈이 잦은 목록 화면이라, 승격 대상을 고를 때 이 성질을 감안한다.
완료 조건
- 승격 지점이 선정되고
MindRecordFailureStage+recordMindRecordFailure확장이 정의되어 반영 - 승격 지점에 stage 속성이 붙어 있다
-
feature/mindrecord에 Firebase 의존이 추가되지 않았다 - 판정 로직이 생긴 곳은 단위 테스트로 덮는다 (선례 · 이 모듈에는
DailyQuestionWriteViewModelTest가 이미 있다)
Activity
fix(mindrecord): 전달 조건 미충족은 텔레메트리로 올리지 않는다 (#964 리뷰)
Merge pull request #1109 from Afternote/feat/964-mindrecord-error-rep…
merge: develop 병합 — 마음의 기록 오류 리포팅(#964) 반영
[-]refactor(mindrecord): ErrorReporter 계측 신설 — 실패가 Logcat 에도 텔레메트리에도 안 남는다[/-][+]ErrorReporter 계측 신설 — 실패가 Logcat 에도 텔레메트리에도 안 남는다[/+]Merge pull request #1278 from Afternote/fix/964-write-failure-telemetry
fix(mindrecord): 삭제 실패 계측을 전건으로 되돌리고 재진입 이름 갱신을 고친다 (#1693)
fix(mindrecord): 공허했던 재진입 음성 테스트를 실제로 돌게 한다 (#964)
fix(mindrecord): 작성 화면 질문 조회를 승격하고 남은 선을 테스트로 고정한다 (#1874)
Sadturtleman commentedon Sep 7, 2026
완료 확인 (2026-09-07, develop 기준)
이슈가 든 완료 조건 넷을 현재 코드로 대조했고 전부 충족돼 있다. 구현이 여러 PR 에 나눠 들어가면서 이슈만 열린 채 남았다.
1.
MindRecordFailureStage+recordMindRecordFailure신설feature/mindrecord/presentation/.../reporting/MindRecordFailureReporting.kt에 stage 어휘 12종이 서 있고 각각 왜 승격 대상인지 KDoc 이 붙어 있다.2. 승격 지점 — 이슈가 든 후보 전량이 붙어 있다
DiaryWriteViewModelDiaryWriteViewModel.kt:236diary_submitDailyQuestionWriteViewModelDailyQuestionWriteViewModel.kt:345daily_question_submitDraftListViewModelDraftListViewModel.kt:90·:166draft_delete·draft_list_loadDailyQuestionWriteViewModel.kt:90·:133daily_question_loadDiaryListViewModelDiaryListViewModel.kt:154record_list_loadWeeklyReportViewModelWeeklyReportViewModel.kt:204weekly_report_loadReceiverMindRecordViewModel.kt:105receiver_record_load후보 밖에서 추가로 붙은 곳도 같은 판단 기준을 따른다 — 미디어 업로드(
media_upload), 기록 삭제(record_delete, 되돌릴 수 없음), 상세 조회(record_detail_load, 「내가 쓴 기록이 안 열린다」), 추억 공간 전체 실패(memory_space_load).「일괄 치환 금지」도 지켜졌다 — 전달 조건 미충족(
DeliveryNotReadyException)은 정상 상태라 기록하지 않는다고ReceiverMindRecordViewModel에 근거째 적혀 있고, 추억 공간은 합친 결과가 비었고 실패 출처가 있을 때만 올린다.3. Firebase 의존 없음
feature/mindrecord/*/build.gradle.kts에 firebase·crashlytics 문자열이 0건이다. 주입받는 타입은core:common의ErrorReporter추상뿐이다.4. 판정 로직의 단위 테스트
WriteFailureReportingTest— 쓰기 실패 승격ReadFailureReportingTest— 읽기 실패 선별(부분 실패는 안 올리고 전체 실패만 올리는 경계 포함)ReceiverFailureReportingTest— 수신자 경로더 할 일이 남아 있지 않아 닫는다.