React Native 모바일 오류 관측성: 실패 신호를 어떻게 나눠 연결하는가
예상된 제품 결과, 처리한 기술 오류, 처리되지 않은 JavaScript 오류와 플랫폼 crash·무응답 진단을 심각도 하나로 합치는 실패를 재현하고, 개별 신호·사용자 사건 분류·영향·원본·시도 문맥을 나눠 기록한다.
목차
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
관측성은 앱이 밖으로 내보낸 원격 측정 데이터를 연결해 내부에서 무슨 일이 있었는지 질문할 수 있는 성질이다. 로그는 그 데이터 가운데 특정 시각의 사건과 문맥을 남기는 기록이다.
모바일 실패 기록은 개별 신호와 연결한 사용자 사건을 나눠 읽는다. 개별 신호 종류는 수집한 기록 하나가 무엇인지 나타내고, 사용자 사건 분류는 같은 시도의 신호를 연결한 뒤 그 시도에서 무엇이 일어났는지 나타낸다.
개별 신호 종류 product-result(제품 계약 안의 결과) / technical-error(기술 실패)
heartbeat-gap(JavaScript 주기 기록 지연) / platform-diagnostic(운영체제 진단 원본)
사용자 사건 분류 expected-result(예상된 결과) / handled-error(앱이 처리한 기술 오류)
unhandled-js-error(처리되지 않은 JavaScript 오류)
android-anr / ios-crash / symptom-only(플랫폼 판정 없이 증상만 남음)
사용자 영향 none(없음) / degraded(성능 저하) / blocked(완료 차단) / terminated(종료) / unresponsive(무응답)
원본 app-js / android-system / apple-system / server
연결 문맥 시도 식별값 / 요청 경로 식별값 / 실행 산출물 식별값 / 발생·관측 시각일반 모델에는 서버 원본도 포함할 수 있지만, 이 글의 실행 fixture는 app-js, android-system,
apple-system 세 원본만 사용한다.
심각도는 기록의 중요도를 나타내는 별도 축이다. ERROR라고 해서 crash인 것은 아니다. 같은 시도에서
앱 기록과 운영체제 진단이 함께 와도 원본은 둘 다 보존하고, 사용자 사건을 셀 때만 연결한다.
반복해서 기억할 문장은 이것이다.
종류·영향·원본은 나누고, 같은 시도 문맥으로 연결한다.
이 글을 관통하는 상황: 실패 대시보드의 crash가 다섯 건으로 늘었다
결제 화면을 바꾼 뒤 모바일 오류 대시보드의 crash가 갑자기 다섯 건으로 늘었다. 기록을 열어 보니 카드 거절, 앱이 처리한 timeout, 처리되지 않은 JavaScript 오류, Android ANR과 iOS crash가 한 묶음에 있었다. 팀의 집계 규칙은 단순했다.
ERROR 또는 FATAL이면 crash
원본 기록 한 줄이면 사용자 사건 한 건timeout은 정해진 시간 안에 결과를 받지 못한 기술 실패다.
attempt ID는 사용자가 시작한 한 번의 제품 시도를 여러 기록에서 잇는 식별값이다. trace ID는 앱과 서버를 지난 한 요청 경로를 잇는 식별값이며, build ID는 실행된 앱 산출물을 구분하는 값이다.
이 글의 질문은 하나다.
Crash, ANR과 예상 가능한 실패를 어떤 신호로 분류하고 연결하는가?
같은 6개 신호를 잘못된 규칙과 교정 규칙으로 비교한다
24편에서 사용한 React Native 프로젝트의 App.tsx를 아래 코드로 교체한다. 외부 패키지는 필요 없다.
이 fixture는 실제 crash·ANR·hang을 만들지 않고, 고정한 6개 입력의 분류와 집계만 화면에서 반복한다.
fixture는 같은 입력과 결과를 되풀이하기 위한 가상 실험이다.
코드에 들어오는 SignalRecord는 기기에서 막 읽은 원문이 아니다. 앞선 수집 경계가 형식과 허용 값을
검사하고 공통 필드로 바꿨지만, 아직 같은 시도끼리 묶지 않은 정규화된 개별 신호 기록이다.
kind는 개별 신호 종류, impact는 사용자 영향, source는 원본을 뜻한다. platformCategory는 운영체제가
확정한 범주가 있을 때만 채운다.
연결한 사용자 사건(incident)은 같은 시도의 신호 기록을 묶어 사건 분류와 최종 사용자 영향을 계산한
보기다. USE_INCIDENT_MODEL은 이 두 단계 모델을 사용할지 고르는 fixture 스위치다. correlationKey는
이 가상 입력에서 같은 제품 시도를 잇는 비민감 식별값이다. Android·Apple 원본이 이 값을 자동으로
제공한다는 뜻은 아니다. 여기서는 앞선 수집 단계가 원본 식별값·build·앱 실행 단위·시각을 대조해 연결을
확정한 입력이라고 고정한다.
USE_INCIDENT_MODEL = false가 잘못된 기준선이다.
import React, { useState } from 'react'
import { Button, SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native'
const USE_INCIDENT_MODEL = false
type Severity = 'INFO' | 'WARN' | 'ERROR' | 'FATAL'
type SignalKind =
| 'product-result'
| 'technical-error'
| 'heartbeat-gap'
| 'platform-diagnostic'
type UserImpact = 'none' | 'degraded' | 'blocked' | 'terminated' | 'unresponsive'
type SignalSource = 'app-js' | 'android-system' | 'apple-system'
type PlatformCategory = 'android-anr' | 'ios-crash' | null
type SignalRecord = {
id: string
correlationKey: string
eventName: string
severity: Severity
kind: SignalKind
impact: UserImpact
source: SignalSource
handled: boolean
platformCategory: PlatformCategory
buildId: string
occurredAt: string
observedAt: string
}
type IncidentClass =
| 'expected-result'
| 'handled-error'
| 'unhandled-js-error'
| 'android-anr'
| 'ios-crash'
| 'symptom-only'
type Incident = {
correlationKey: string
classification: IncidentClass
impact: UserImpact
sources: SignalSource[]
signalCount: number
}
const BUILD_ID = 'mobile-417'
const SIGNAL_RECORDS: SignalRecord[] = [
{
id: 'signal-1',
correlationKey: 'checkout-101',
eventName: 'payment.card_declined',
severity: 'ERROR',
kind: 'product-result',
impact: 'blocked',
source: 'app-js',
handled: true,
platformCategory: null,
buildId: BUILD_ID,
occurredAt: '2026-08-09T10:00:01.000Z',
observedAt: '2026-08-09T10:00:01.050Z',
},
{
id: 'signal-2',
correlationKey: 'checkout-102',
eventName: 'checkout.request_timeout',
severity: 'ERROR',
kind: 'technical-error',
impact: 'blocked',
source: 'app-js',
handled: true,
platformCategory: null,
buildId: BUILD_ID,
occurredAt: '2026-08-09T10:00:02.000Z',
observedAt: '2026-08-09T10:00:02.030Z',
},
{
id: 'signal-3',
correlationKey: 'receipt-103',
eventName: 'receipt.render_unhandled',
severity: 'ERROR',
kind: 'technical-error',
impact: 'blocked',
source: 'app-js',
handled: false,
platformCategory: null,
buildId: BUILD_ID,
occurredAt: '2026-08-09T10:00:03.000Z',
observedAt: '2026-08-09T10:00:03.010Z',
},
{
id: 'signal-4',
correlationKey: 'checkout-104',
eventName: 'js.heartbeat_gap',
severity: 'WARN',
kind: 'heartbeat-gap',
impact: 'degraded',
source: 'app-js',
handled: true,
platformCategory: null,
buildId: BUILD_ID,
occurredAt: '2026-08-09T10:00:04.000Z',
observedAt: '2026-08-09T10:00:09.300Z',
},
{
id: 'signal-5',
correlationKey: 'checkout-104',
eventName: 'android.anr',
severity: 'ERROR',
kind: 'platform-diagnostic',
impact: 'unresponsive',
source: 'android-system',
handled: false,
platformCategory: 'android-anr',
buildId: BUILD_ID,
occurredAt: '2026-08-09T10:00:04.100Z',
observedAt: '2026-08-09T10:01:00.000Z',
},
{
id: 'signal-6',
correlationKey: 'launch-105',
eventName: 'apple.crash',
severity: 'FATAL',
kind: 'platform-diagnostic',
impact: 'terminated',
source: 'apple-system',
handled: false,
platformCategory: 'ios-crash',
buildId: BUILD_ID,
occurredAt: '2026-08-09T10:00:05.000Z',
observedAt: '2026-08-09T10:02:00.000Z',
},
]
function uniqueSources(signals: SignalRecord[]): SignalSource[] {
return [...new Set(signals.map(signal => signal.source))]
}
function classifyIncident(signals: SignalRecord[]): IncidentClass {
const platformSignal = signals.find(signal => signal.platformCategory !== null)
if (platformSignal?.platformCategory === 'android-anr') return 'android-anr'
if (platformSignal?.platformCategory === 'ios-crash') return 'ios-crash'
if (signals.some(signal => signal.kind === 'product-result')) return 'expected-result'
if (signals.some(signal => signal.kind === 'technical-error' && !signal.handled)) {
return 'unhandled-js-error'
}
if (signals.some(signal => signal.kind === 'technical-error' && signal.handled)) {
return 'handled-error'
}
return 'symptom-only'
}
function strongestImpact(signals: SignalRecord[]): UserImpact {
const order: UserImpact[] = ['none', 'degraded', 'blocked', 'unresponsive', 'terminated']
return signals.reduce<UserImpact>((current, signal) =>
order.indexOf(signal.impact) > order.indexOf(current) ? signal.impact : current
, 'none')
}
function buildIncidents(signals: SignalRecord[]): Incident[] {
const grouped = new Map<string, SignalRecord[]>()
for (const signal of signals) {
const current = grouped.get(signal.correlationKey) ?? []
grouped.set(signal.correlationKey, [...current, signal])
}
return [...grouped.entries()].map(([correlationKey, group]) => ({
correlationKey,
classification: classifyIncident(group),
impact: strongestImpact(group),
sources: uniqueSources(group),
signalCount: group.length,
}))
}
function summarizeBySeverity(signals: SignalRecord[]): string[] {
const crashCount = signals.filter(
signal => signal.severity === 'ERROR' || signal.severity === 'FATAL'
).length
return [
`개별 신호: ${signals.length}`,
`crash: ${crashCount}`,
`사용자 사건: ${signals.length}`,
...signals.map(signal =>
`${signal.correlationKey} → ${signal.severity === 'ERROR' || signal.severity === 'FATAL' ? 'crash' : '기타'}`
),
]
}
function summarizeByDimensions(signals: SignalRecord[]): string[] {
const incidents = buildIncidents(signals)
return [
`개별 신호: ${signals.length}`,
`사용자 사건: ${incidents.length}`,
...incidents.map(incident =>
`${incident.correlationKey} → ${incident.classification} / ${incident.impact} / ${incident.sources.join('+')} / 신호 ${incident.signalCount}`
),
]
}
export default function App() {
const [lines, setLines] = useState<string[]>([])
function runFixture() {
setLines(
USE_INCIDENT_MODEL
? summarizeByDimensions(SIGNAL_RECORDS)
: summarizeBySeverity(SIGNAL_RECORDS)
)
}
return (
<SafeAreaView style={styles.screen}>
<ScrollView contentContainerStyle={styles.content}>
<Text style={styles.title}>모바일 오류 관측성 fixture</Text>
<Text>사건 연결 모델: {USE_INCIDENT_MODEL ? 'on' : 'off'}</Text>
<Button title="6개 신호 분류" onPress={runFixture} />
<View style={styles.results}>
{lines.map((line, index) => (
<Text key={`${index}-${line}`}>{line}</Text>
))}
</View>
</ScrollView>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
screen: { flex: 1 },
content: { padding: 16, gap: 12 },
title: { fontSize: 22, fontWeight: '700' },
results: { borderWidth: 1, borderColor: '#8A8A8A', padding: 12, gap: 6 },
})실행 전에 예측한다.
- 카드 거절과 처리한 timeout도 crash로 셀까?
checkout-104의 앱 heartbeat와 Android ANR은 사용자 사건 몇 건이 될까?ERROR인 처리되지 않은 JavaScript 오류가 iOS crash를 단독으로 증명할까?
개발자 메뉴에서 Reload한 뒤 6개 신호 분류를 누른다.
개별 신호: 6
crash: 5
사용자 사건: 6
checkout-101 → crash
checkout-102 → crash
receipt-103 → crash
checkout-104 → 기타
checkout-104 → crash
launch-105 → crash카드 거절과 처리한 timeout을 crash로 부풀렸다. checkout-104는 같은 시도의 heartbeat와 Android ANR을
사용자 사건 두 건으로 셌다. receipt-103은 처리되지 않은 JavaScript 오류지만 운영체제 crash 원본은
아직 없다.
심각도는 신호·사용자 사건 분류가 아니다
OpenTelemetry는 서로 다른 관측 도구가 이해할 수 있도록 관측 데이터의 공통 형식과 전달 규약을 만드는 공개 표준 프로젝트다. 트레이스 안의 한 작업 구간을 span이라고 한다. 아래 필드 표는 특정 오류 대시보드 제품의 설정이 아니라 그 공통 로그 모델이다.
ERROR는 무언가 잘못된 기록이라는 뜻이고 FATAL은 crash 같은 치명적 오류에 쓸 수 있다. 하지만 이
심각도 값만으로 모바일 사건의 플랫폼 범주가 만들어지지는 않는다.
제품 결과와 기술 오류를 나눈다
payment.card_declined는 이 fixture의 제품 계약에 포함된 거절 결과다. 사용자는 결제를 완료하지 못했으므로
영향은 blocked지만 앱 계약이 깨진 기술 오류는 아니다. 반대로 checkout.request_timeout은 앱이 처리해
재시도 화면을 보여 줬더라도 기술 실패다.
따라서 product-result 신호는 연결 뒤 expected-result가 되고, 앱이 잡은 technical-error는
handled-error가 된다. 플랫폼 판정 없이 heartbeat 지연만 남으면 symptom-only로 보존한다.
“사용자가 성공하지 못했다”와 “앱이 비정상 종료됐다”는 서로 다른 질문이다. 둘을 한 crash 비율에 넣으면 제품 거절률과 기술 회귀율을 모두 왜곡한다. 반대로 처리한 오류를 기록에서 지우면 복구 화면이 자주 뜨는 문제를 놓친다.
같은 시도의 신호는 연결하되 덮어쓰지 않는다
World Wide Web Consortium(W3C, 웹 표준을 만드는 국제 기구)의 Trace Context는 이 요청 경로 문맥을 구성 요소 사이에 전달하는 표준이다.
상관 연결은 여러 기록이 같은 시도나 요청에 속하는지 찾는 일이다. 중복 제거처럼 원본 한 줄을 삭제하는
작업이 아니다. fixture의 checkout-104에는 다음 두 질문이 함께 있다.
app-js / js.heartbeat_gap JavaScript 기록 간격이 왜 길어졌는가?
android-system / android.anr Android가 어느 ANR 범주와 trace를 남겼는가?사용자 사건 보기는 조사할 같은 시도의 신호를 한 화면에 모은 결과다. 이 fixture는 correlationKey 하나로만 묶지만,
운영 환경에서는 식별값의 생성 범위와 충돌 조건을 제품 계약으로 정한다. 요청 경계를 넘는다면 trace ID,
앱 내부 한 시도라면 무작위 attempt ID처럼 목적에 맞는 값을 쓴다. 시간만 가까운 기록을 자동으로 같은
사건이라 단정하지 않는다.
교정 모델은 개별 신호를 연결해 사용자 사건을 계산한다
상수를 바꾼다.
const USE_INCIDENT_MODEL = true개발자 메뉴에서 Reload한 뒤 같은 버튼을 누른다.
개별 신호: 6
사용자 사건: 5
checkout-101 → expected-result / blocked / app-js / 신호 1
checkout-102 → handled-error / blocked / app-js / 신호 1
receipt-103 → unhandled-js-error / blocked / app-js / 신호 1
checkout-104 → android-anr / unresponsive / app-js+android-system / 신호 2
launch-105 → ios-crash / terminated / apple-system / 신호 1정규화된 개별 신호는 여전히 6개다. 카드 거절과 처리한 timeout은 서로 다른 사용자 사건 분류가 되고, Android ANR과
heartbeat는 원본을 둘 다 유지한 사용자 사건 한 건으로 연결된다. receipt-103은 사용자가 영수증 화면을
완료하지 못한 blocked 영향으로 남고, Apple·Android crash가 확인됐다고 승격하지 않는다.
strongestImpact()가 고른 순서는 이 fixture의 제품 정책이다. 모든 앱에 통용되는 표준 우선순위가 아니다.
예를 들어 저장되지 않은 데이터 손실이 있는 blocked가 짧은 unresponsive보다 더 중요할 수 있다면 제품
영향 모델을 다르게 정해야 한다.
분류한 뒤에는 실행 계층에 맞는 산출물로 보낸다
JavaScript frame 실행 bundle과 함께 만든 platform별 최종 source map
Android Java·Kotlin 같은 공개 build의 R8 mapping
Android C·C++ 주소 같은 ABI·build의 제거 전 native symbols
iOS Mach-O 주소 같은 build UUID의 dSYM + 프로세서 구조 + load address같은 incident에 JavaScript와 native stack이 함께 있어도 하나의 도구에 모두 넣지 않는다. 대신 공통
correlationKey, build ID와 발생 시각으로 같은 사건 화면에서 연결한다. 복원된 파일·줄은 조사 입구이며,
그 위치가 사용자 영향의 원인이라는 결론은 재현·주변 실행 흐름과 변경 전후 증거로 닫는다.
발생 시각과 관측 시각을 구분한다
앱 로그는 사건 직후 도착할 수 있지만 Android vitals와 Apple 진단은 나중에 수집될 수 있다. occurredAt은
원본에서 사건이 일어난 시각이고 observedAt은 수집 시스템이 그 기록을 본 시각이다. 늦게 온 플랫폼
진단을 관측 시각만으로 새 사용자 사건으로 만들지 않는다.
시간대·기기 시계와 전송 지연이 다를 수 있으므로 시각 하나만으로 자동 결합하지 않는다. attempt ID나
trace ID, build ID, 플랫폼 원본 식별값과 허용한 시간 범위를 함께 사용한다. 식별값이 없는 기록은
unlinked처럼 연결되지 않은 상태로 보존하고 사람의 조사 없이 임의 사용자 사건에 붙이지 않는다.
식별자와 속성에는 민감 정보를 넣지 않는다
attempt ID와 trace ID는 비밀값이 아니다. 여러 시스템과 저장소를 지날 수 있다. 다음 원문을 ID나 속성으로 복사하지 않는다.
금지 예 인증 비밀값 / 카드 정보 / 사용자 입력 전체
허용 후보 무작위 attempt ID / build ID / 안정된 사건 이름 / 비민감 제품 단계
별도 정책 보존 기간 / 접근 권한 / 삭제·가림 / 수집 실패 감시사건 이름에 서버 응답 원문이나 이메일을 이어 붙이지 않는다. payment.card_declined처럼 값의 종류가
제한된 이름을 쓰고, 꼭 필요한 진단 속성도 허용 목록과 보존 정책 안에서 선택한다.
fixture와 운영 수집의 증명 범위를 나눈다
| 검증 층 | 이 fixture가 확인하는 것 | 실제 통합에서 추가할 증거 |
|---|---|---|
| 사용자 사건 계산 | 정규화 필드가 주어진 뒤의 사건 분류·영향·원본 보존 | 실제 원문을 검증·정규화하는 코드와 필드 누락률 |
| 상관 연결 | checkout-104 원본 2개·사용자 사건 1개 | ID 생성·전달·충돌, 플랫폼 원본에 ID가 없을 때의 미연결 상태·늦은 진단 처리 |
| Android | 가상 android-anr 우선순위 | 같은 원인 후보 ANR 묶음·trace·앱 build 연결 |
| Apple | 가상 ios-crash 우선순위 | crash·hang 보고서·build UUID 연결 |
| stack 복원 | 계층별 산출물 선택표 | 실제 공개 build 산출물로 복원한 출력 |
운영 수집을 검증할 때는 앱 강제 종료·네트워크 끊김·앱이 보이지 않는 상태·다음 실행 업로드도 포함한다. “기록이 없다”는 사실이 “오류가 없었다”는 뜻인지, 종료 전에 보내지 못한 것인지, 수집 대상에서 제외된 것인지 나눠야 한다.
pull request에서 달라져야 할 행동
모바일 오류 계측이나 대시보드 pull request에는 다음 표가 있어야 한다.
두 단계 분류 개별 신호 종류와 연결 뒤 사용자 사건 분류를 구분했는가
사용자 영향 완료 여부와 degraded·blocked·terminated·unresponsive를 별도로 기록하는가
심각도 알림 우선순위이지 crash 판정기가 아님을 보존하는가
원본 app-js·Android·Apple·server 기록을 덮어쓰지 않는가
상관 연결 attempt/trace/build/시각의 생성·전달·충돌 조건이 있는가
중복 집계 개별 신호 수와 사용자 사건 수를 별도 지표로 계산하는가
stack 산출물 JavaScript·Android·iOS 계층별 정확한 공개 build 산출물을 고르는가
민감 정보 식별자·사건 이름·속성의 허용 목록과 보존 정책이 있는가
수집 실패 종료·네트워크 끊김·앱이 보이지 않는 상태·지연 도착을 검증했는가검토자는 다음 질문을 실제 입력과 출력으로 닫는다.
- 제품이 예상한 거절이 기술 오류율이나 crash 비율에 들어가지 않는가?
- 처리한 기술 오류가 “정상”으로 사라지지 않는가?
- 처리되지 않은 JavaScript 오류를 운영체제 crash로 자동 승격하지 않는가?
- 앱 heartbeat와 Android ANR을 원본 두 개·사용자 사건 한 개로 볼 수 있는가?
- iOS hang을 Android ANR 이름으로 저장하지 않는가?
- 늦게 도착한 플랫폼 진단이 새 사용자 사건을 만들지 않는가?
- 같은 사건의 각 stack을 올바른 build 산출물로 복원하는가?
한 장으로 다시 보기
문제 ERROR/FATAL 기록을 모두 crash로 세고 원본 한 줄을 사용자 사건 한 건으로 계산
실패 카드 거절·처리 timeout까지 crash, ANR heartbeat와 플랫폼 진단은 이중 집계
분리 개별 신호 종류 / 사용자 사건 분류 / 영향 / 심각도 / 원본 / 플랫폼 범주
연결 attempt ID / trace ID / build ID / 발생·관측 시각
보존 개별 신호 6개는 그대로, 같은 시도만 사용자 사건 보기에서 연결
교정 결과 expected 1 / handled 1 / unhandled JS 1 / Android ANR 1 / iOS crash 1
복원 JavaScript source map / Android mapping·symbols / iOS dSYM
경계 fixture 분류 성공은 실제 종료·ANR·hang 수집 성공을 증명하지 않음모바일 오류 관측성의 출발점은 더 많은 기록이 아니라 질문마다 다른 축을 보존하는 것이다. 심각도로 알림 우선순위를 정하고, 개별 신호와 사용자 사건 분류로 제품 결과와 기술 실패를 나누며, 플랫폼 원본으로 crash·무응답 범주를 닫는다. 그 뒤 같은 시도 문맥으로 신호를 연결해야 사용자 영향과 원인 코드 위치를 함께 조사할 수 있다.
스스로 확인할 질문
ERROR심각도만으로 모바일 crash를 판정할 수 없는 이유는 무엇인가?- 예상된 카드 거절과 앱이 처리한 timeout은 사용자 영향과 사용자 사건 분류에서 어떻게 다른가?
- 같은 시도의 heartbeat와 Android ANR 기록을 왜 보존하면서 한 사용자 사건으로 연결하는가?
- 발생 시각과 관측 시각을 왜 별도 필드로 저장해야 하는가?
- JavaScript·Android Java/Kotlin·Android C/C++·iOS 주소는 각각 어느 산출물로 복원하는가?
- 이 글의 가상 fixture가 실제 운영 수집 성공을 증명하지 못하는 이유는 무엇인가?
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01ERROR 또는 FATAL 심각도의 모바일 기록을 모두 crash로 세어도 될까?
정답
아니다. 심각도는 기록의 중요도 축이고 개별 신호나 사용자 사건의 종류가 아니다. 예상된 제품 결과, 처리한 기술 오류, 처리되지 않은 오류와 운영체제 crash·무응답 진단을 별도 필드로 분류한다.
관련 설명 다시 읽기왜 그런가
같은 ERROR라도 앱은 계속 실행될 수 있고, crash·ANR·hang은 플랫폼 원본 증거가 필요한 범주다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02카드 거절처럼 제품 계약에 포함된 실패 결과를 기술 오류와 같은 이름으로 저장해야 할까?
정답
아니다. 사용자에게는 완료 실패라는 영향이 있지만, 시스템 계약을 벗어난 기술 오류와는 사용자 사건 분류를 구분한다.
관련 설명 다시 읽기왜 그런가
제품 결과와 기술 오류를 합치면 실제 회귀율과 사용자 결과율을 모두 왜곡한다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03같은 시도에서 앱 heartbeat와 Android ANR 기록이 함께 왔다면 하나를 지워야 중복 집계를 막을 수 있을까?
정답
아니다. 정규화된 개별 신호 두 개는 보존하고 같은 시도 문맥으로 연결해 사용자 사건만 한 번 센다.
관련 설명 다시 읽기왜 그런가
heartbeat는 앱 문맥이고 ANR은 운영체제 판정이므로 조사 질문이 다르다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04모바일 오류 사건에 stack이 있으면 하나의 복원 도구로 모든 frame을 처리해도 될까?
정답
아니다. JavaScript frame은 실행 bundle의 최종 source map, Android Java·Kotlin과 C·C++ frame은 각각 R8 mapping과 native symbols, iOS 주소는 같은 build UUID의 dSYM으로 보낸다.
관련 설명 다시 읽기왜 그런가
사건 분류와 코드 위치 복원은 연결되지만, 실행 계층마다 필요한 산출물이 다르다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- Logs Data ModelOpenTelemetry · 표준 · 확인 2026-08-09
- OpenTelemetry LoggingOpenTelemetry · 표준 · 확인 2026-08-09
- Trace Context Level 1W3C · 표준 · 확인 2026-08-09
- Handling sensitive dataOpenTelemetry · 공식 문서 · 확인 2026-08-09
- CrashesAndroid Developers · 공식 문서 · 확인 2026-08-09
- ANRsAndroid Developers · 공식 문서 · 확인 2026-08-09
- Diagnosing issues using crash reports and device logsApple Developer · 공식 문서 · 확인 2026-08-09
- Improving app responsivenessApple Developer · 공식 문서 · 확인 2026-08-09
- Debugging Release BuildsReact Native · 공식 문서 · 확인 2026-08-09
- R8 retraceAndroid Developers · 공식 문서 · 확인 2026-08-09
- ndk-stackAndroid Developers · 공식 문서 · 확인 2026-08-09
- Adding identifiable symbol names to a crash reportApple Developer · 공식 문서 · 확인 2026-08-09