클라이언트 앱 모니터링 가이드
작성일: 2026-04-06 Last Updated: 2026-04-06 대상: Browser Extension, Tauri Desktop, Flutter Mobile
목차
1. 크래시 리포트
1.1 Sentry 기반 통합 구성
| 플랫폼 | SDK | 패키지 |
|---|---|---|
| Browser Extension | @sentry/browser | npm install @sentry/browser |
| Tauri (Rust) | sentry crate | cargo add sentry |
| Tauri (Frontend) | @sentry/browser | npm install @sentry/browser |
| Flutter | sentry_flutter | flutter pub add sentry_flutter |
1.2 초기화 코드
Browser Extension
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: CONFIG.SENTRY_DSN,
environment: CONFIG.ENV, // dev, staging, prod
release: `browser-ext@${CONFIG.VERSION}`,
integrations: [
Sentry.breadcrumbsIntegration({
console: true,
dom: true,
fetch: true,
xhr: true,
}),
],
// 프로덕션에서만 100% 샘플링, 개발 중에는 비활성화
sampleRate: CONFIG.ENV === 'prod' ? 1.0 : 0,
// PII 전송 방지
beforeSend(event) {
// 이메일, IP 등 개인정보 제거
if (event.user) {
delete event.user.email;
delete event.user.ip_address;
}
return event;
},
});
Tauri (Rust 백엔드)
fn main() {
let _guard = sentry::init((
env!("SENTRY_DSN"),
sentry::ClientOptions {
release: sentry::release_name!(),
environment: Some(env!("APP_ENV").into()),
sample_rate: 1.0,
..Default::default()
},
));
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Flutter
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = Config.sentryDsn;
options.environment = Config.env;
options.release = 'flutter@${Config.version}';
options.tracesSampleRate = 0.2;
options.attachScreenshot = true;
options.attachViewHierarchy = true;
// PII 제거
options.beforeSend = (event, hint) {
event = event.copyWith(
user: event.user?.copyWith(email: null, ipAddress: null),
);
return event;
};
},
appRunner: () => runApp(const MyApp()),
);
}
1.3 소스맵/디버그 심볼 업로드
| 플랫폼 | 도구 | CI 명령 |
|---|---|---|
| Browser Extension | @sentry/cli | sentry-cli sourcemaps upload --release=X.Y.Z dist/ |
| Tauri (Rust) | sentry-cli | sentry-cli debug-files upload target/release/ |
| Flutter (Android) | Sentry Gradle plugin | 자동 (build.gradle 설정) |
| Flutter (iOS) | sentry-cli | sentry-cli debug-files upload build/ios/ |
# GitHub Actions - 소스맵 업로드
- name: Upload source maps to Sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
run: |
npx sentry-cli releases new "$VERSION"
npx sentry-cli sourcemaps upload --release="$VERSION" dist/
npx sentry-cli releases finalize "$VERSION"
2. 에러 트래킹
2.1 에러 분류 체계
| 레벨 | 설명 | 예시 | 알림 |
|---|---|---|---|
| Fatal | 앱 크래시, 복구 불가 | 미처리 예외, OOM | 즉시 |
| Error | 기능 실패, 사용자 영향 | API 실패, 결제 오류 | 5분 |
| Warning | 잠재적 문제 | 재시도 성공, 느린 응답 | 1시간 |
| Info | 참고 정보 | 기능 사용 패턴 | 일간 |
2.2 구조화된 에러 컨텍스트
// 에러에 컨텍스트 추가
Sentry.withScope((scope) => {
scope.setTag('feature', 'sync');
scope.setTag('client_type', 'browser_ext');
scope.setContext('sync_state', {
lastSyncAt: state.lastSyncAt,
pendingItems: state.pendingQueue.length,
retryCount: state.retryCount,
});
Sentry.captureException(error);
});
2.3 플랫폼별 에러 수집 범위
| 에러 유형 | Extension | Tauri | Flutter |
|---|---|---|---|
| 미처리 JS 예외 | O | O (WebView) | - |
| Rust panic | - | O | - |
| Dart 미처리 예외 | - | - | O |
| 네트워크 오류 | O | O | O |
| WebView 렌더 오류 | - | O | - |
| ANR (Application Not Responding) | - | - | O (Android) |
| 네이티브 크래시 | - | O | O |
2.4 에러 그룹핑 규칙
| 그룹 기준 | 설명 |
|---|---|
| 에러 타입 + 스택트레이스 | 기본 그룹핑 |
| fingerprint 커스텀 | 특정 에러의 그룹핑 오버라이드 |
| 릴리즈별 분리 | 버전별 에러 추적 |
| 환경별 분리 | staging vs production |
3. 사용 분석 (Analytics)
3.1 분석 도구 선택
| 도 구 | 장점 | 단점 | 적합 |
|---|---|---|---|
| PostHog (자체호스팅) | 데이터 소유권, GDPR 친화 | 운영 부담 | 프라이버시 중시 |
| Mixpanel | 강력한 퍼널/리텐션 | 비용 | B2C |
| Amplitude | 프로덕트 분석 | 비용 | B2B SaaS |
| Google Analytics | 무료, 익숙함 | 프라이버시 우려 | 가벼운 분석 |
| 자체 구축 | 완전한 통제 | 개발 비용 | 최소 수집만 필요 시 |
3.2 추적 이벤트 설계
공통 이벤트
| 이벤트 | 속성 | 비고 |
|---|---|---|
app_launched | version, platform, os_version | 앱 시작 |
feature_used | feature_name, duration_ms | 기능 사용 |
error_occurred | error_type, feature | 에러 발생 |
subscription_changed | plan, action (upgrade/downgrade/cancel) | 구독 변경 |
settings_changed | setting_key, new_value | 설정 변경 |
플랫폼별 이벤트
| 이벤트 | Extension | Tauri | Flutter |
|---|---|---|---|
extension_installed | O | - | - |
extension_updated | O | - | - |
window_resized | - | O | - |
system_tray_clicked | - | O | - |
push_notification_received | - | - | O |
deep_link_opened | - | O | O |
3.3 이벤트 구현 패턴
// 분석 추상화 레이어
interface AnalyticsProvider {
track(event: string, properties?: Record<string, unknown>): void;
identify(userId: string, traits?: Record<string, unknown>): void;
reset(): void;
}
class Analytics {
private providers: AnalyticsProvider[] = [];
private enabled = false;
init(providers: AnalyticsProvider[]) {
this.providers = providers;
// 사용자 옵트인 확인 후 활성화
this.enabled = UserPreferences.analyticsOptedIn();
}
track(event: string, properties?: Record<string, unknown>) {
if (!this.enabled) return;
const enriched = {
...properties,
app_version: APP_VERSION,
platform: PLATFORM,
timestamp: Date.now(),
};
this.providers.forEach((p) => p.track(event, enriched));
}
}
4. 성능 모니터링
4.1 핵심 메트릭
| 메트릭 | 측정 방법 | 임계값 |
|---|---|---|
| 앱 시작 시간 (Cold Start) | 프로세스 시작 ~ 첫 렌더 | < 2초 |
| 화면 전환 시간 | 네비게이션 시작 ~ 렌더 완료 | < 300ms |
| API 응답 시간 (P95) | 요청 시작 ~ 응답 수신 | < 1초 |
| 메모리 사용량 (피크) | 주기적 측정 | < 150MB (모바일), < 300MB (데스크톱) |
| 프레임 드롭 | 16ms 이상 걸린 프레임 수 | < 1% |
| 번들/바이너리 크기 | 빌드 출력 | 증가 추세 모니터링 |
4.2 Sentry Performance (트랜잭션)
// 화면 전환 성능 측정
const transaction = Sentry.startTransaction({
name: 'screen.settings',
op: 'navigation',
});
// 하위 작업 측정
const span = transaction.startChild({
op: 'db.query',
description: 'Load user preferences',
});
await loadPreferences();
span.finish();
transaction.finish();
// Flutter 성능 모니터링
final transaction = Sentry.startTransaction('loadDashboard', 'ui.load');
try {
final span = transaction.startChild('http.client', description: 'Fetch data');
await api.fetchDashboardData();
span.finish(status: const SpanStatus.ok());
} finally {
await transaction.finish();
}
4.3 플랫폼별 성능 도구
| 플랫폼 | 내장 도구 | 외부 도구 |
|---|---|---|
| Browser Extension | Chrome DevTools Performance | Lighthouse CI |
| Tauri | Chrome DevTools (WebView) + perf (Linux) | Instruments (macOS) |
| Flutter | Flutter DevTools | Firebase Performance |
4.4 성능 알림 규칙
| 조건 | 알림 레벨 | 채널 |
|---|---|---|
| Cold Start P95 > 3초 | Warning | Slack |
| API P95 > 2초 | Warning | Slack |
| 크래시율 > 1% | Critical | PagerDuty + Slack |
| 메모리 사용 > 500MB | Warning | Slack |
| 프레임 드롭 > 5% | Warning | 대시보드 |
5. 사용자 피드백 수집
5.1 인앱 피드백 채널
| 채널 | 구현 | 용도 |
|---|---|---|
| 피드백 폼 | 앱 내 모달/시트 | 일반 피드백 |
| 크래시 후 보고 | Sentry User Feedback | 크래시 재현 정보 |
| 평점 요청 | 인앱 리뷰 프롬프트 | 스토어 평점 유도 |
| 기능 요청 | 외부 보드 링크 (Canny, Nolt) | 로드맵 반영 |
5.2 크래시 후 사용자 피드백
// Sentry User Feedback
Sentry.showReportDialog({
eventId: Sentry.lastEventId(),
title: '문제가 발생했습니다',
subtitle: '어떤 작업을 하고 있었는지 알려주세요.',
labelSubmit: '보내기',
labelClose: '닫기',
labelName: '이름',
labelEmail: '이메일',
labelComments: '무슨 일이 있었나요?',
});
5.3 인앱 리뷰 요청 타이밍
| 조건 | 설명 |
|---|---|
| 최소 사용 횟수 | 앱 실행 10회 이상 |
| 최소 사용 기간 | 설치 후 7일 이상 |
| 성공적 작업 완료 후 | 핵심 기능 성공 시 |
| 최근 요청 이력 | 마지막 요청 후 90일 이상 |
| 부정 경험 배제 | 에러 발생 세션에서는 요청 금지 |
플랫폼별 인앱 리뷰 API
| 플랫폼 | API | 비고 |
|---|---|---|
| Android | com.google.android.play:review | 연간 요청 횟수 제한 |
| iOS | SKStoreReviewController.requestReview() | 연 3회 표시 |
| Chrome Extension | 스토어 페이지 링크 | 네이티브 API 없음 |
| Tauri | 해당 없음 | GitHub Star 또는 자체 시스템 |
6. 프라이버시 준수
6.1 GDPR / 개인정보보호법 체크리스트
| 항목 | 요구사항 | 구현 |
|---|---|---|
| 사전 동의 | 데이터 수집 전 명시적 동의 | 옵트인 UI (첫 실 행 시) |
| 동의 철회 | 언제든 수집 중단 가능 | 설정 > 개인정보 > 분석 끄기 |
| 데이터 접근권 | 사용자 데이터 내보내기 | API 엔드포인트 제공 |
| 삭제권 | 사용자 데이터 완전 삭제 | 계정 삭제 + Sentry 데이터 삭제 |
| 최소 수집 | 필요한 최소한의 데이터만 | PII 자동 제거 필터 |
| 투명성 | 수집 항목 공개 | 개인정보처리방침 + 앱 내 설명 |
6.2 옵트인/옵트아웃 구현
// 데이터 수집 동의 관리
interface ConsentState {
analytics: boolean; // 사용 분석
crashReports: boolean; // 크래시 리포트
performance: boolean; // 성능 모니터링
consentVersion: string; // 동의 버전 (약관 변경 시 재동의)
consentDate: string; // 동의 일시
}
class ConsentManager {
async updateConsent(consent: Partial<ConsentState>) {
const state = { ...await this.getConsent(), ...consent };
await storage.set('consent', state);
// 동의 상태에 따라 SDK 활성화/비활성화
if (!state.crashReports) {
Sentry.close();
}
if (!state.analytics) {
analytics.disable();
}
}
isConsentValid(): boolean {
const state = this.getConsent();
return state.consentVersion === CURRENT_CONSENT_VERSION;
}
}
6.3 수집 데이터 분류
| 카테고리 | 수집 항목 | 동의 필요 | 비고 |
|---|---|---|---|
| 필수 (기능용) | 인증 토큰, 설정 | 서비스 약관 | 수집 중단 불가 |
| 크래시 리포트 | 스택트레이스, OS 정보, 앱 버전 | 옵트인 | PII 제거 |
| 사용 분석 | 이벤트, 화면 조회, 기능 사용 | 옵트인 | 익명화 |
| 성능 데이터 | 로딩 시간, 메모리, 프레임 | 옵트인 | 집계만 |
6.4 Apple App Privacy (iOS 필수)
| Privacy Nutrition Label | 해당 여부 확인 |
|---|---|
| Data Used to Track You | 사용 시 ATT 필수 |
| Data Linked to You | 사용자 ID 연결 데이터 |
| Data Not Linked to You | 익명 분석 데이터 |
| Data Not Collected | 해당 카테고리 없음 |
6.5 Google Play 데이터 안전 섹션
| 항목 | 선언 필요 |
|---|---|
| 수집하는 데이터 유형 | O |
| 데이터 공유 여부 | O |
| 보안 관행 (암호화, 삭제 요청) | O |
| 독립 보안 검토 여부 | 선택 |
7. 대시보드 구성
7.1 Sentry 대시보드 레이아웃
Overview 대시보드
| 위젯 | 표시 내용 | 기간 |
|---|---|---|
| 크래시율 추이 | 일별 크래시 비율 (크래시 세션/전체 세션) | 30일 |
| 미해결 이슈 Top 10 | 가장 빈번한 에러 | 7일 |
| 릴리즈 안정성 | 버전별 크래시율 비교 | 최근 3개 릴리즈 |
| 영향받은 사용자 수 | 유니크 유저 기준 | 24시간 |
| 플랫폼별 에러 분포 | Extension / Tauri / Flutter 비율 | 7일 |
Performance 대시보드
| 위젯 | 표시 내용 |
|---|---|
| P50 / P75 / P95 / P99 응답 시간 | API 엔드포인트별 |
| 앱 시작 시간 추이 | 버전별 비교 |
| 가장 느린 트랜잭션 Top 5 | 개선 대상 식별 |
| Web Vitals (Tauri/Extension) | LCP, FID, CLS |
7.2 Analytics 대시보드
| 카테고리 | 주요 지표 |
|---|---|
| 활성 사용자 | DAU, WAU, MAU, DAU/MAU 비율 |
| 리텐션 | D1, D7, D30 리텐션율 |
| 기능 사용 | 기능별 사용 빈도, 활성 사용자 비율 |
| 전환 퍼널 | 설치 > 가입 > 활성화 > 구독 |
| 이탈 분석 | 이탈 시점, 마지막 사용 기능 |
7.3 운영 대시보드
| 카테고리 | 모니터링 항목 |
|---|---|
| 배포 현황 | 최신 버전 도달률, 롤아웃 진행률 |
| 스토어 리뷰 | 평점 추이, 부정 리뷰 알림 |
| 라이선스 | 활성 구독 수, 만료 임박 |
| 인프라 | 업데이트 서버 상태, API 헬스체크 |
8. 알림 설정
8.1 알림 채널 구성
| 채널 | 용도 | 도구 |
|---|---|---|
| Slack #app-alerts | 실시간 운영 알림 | Sentry → Slack 연동 |
| Slack #app-metrics | 일간/주간 리포트 | 스케줄 봇 |
| 주간 요약 리포트 | Sentry Digest | |
| PagerDuty | 긴급 에스컬레이션 (P0) | Sentry → PagerDuty |
8.2 알림 규칙
| 조건 | 심각도 | 알림 방법 | 대응 시간 |
|---|---|---|---|
| 크래시율 > 2% (1시간 내) | P0 | PagerDuty + Slack | 15분 |
| 새 Fatal 에러 (10건 이상) | P0 | Slack 즉시 | 30분 |
| 크래시율 > 0.5% (24시간) | P1 | Slack | 4시간 |
| 새 Error (50건 이상) | P1 | Slack | 4시간 |
| API P95 > 3초 (30분간) | P2 | Slack | 24시간 |
| 스토어 평점 1점 리뷰 | P2 | Slack | 24시간 |
| 주간 크래시율 0.1% 초과 | P3 | 다음 스프린트 |
8.3 Sentry 알림 규칙 설정 예시
# sentry alert rules (웹 UI 또는 API로 설정)
rules:
- name: "High crash rate"
conditions:
- type: event_frequency
value: 100
interval: 1h
actions:
- type: slack
channel: "#app-alerts"
- type: pagerduty
severity: critical
- name: "New issue in production"
conditions:
- type: first_seen_event
environment: production
filters:
- type: level
value: [fatal, error]
actions:
- type: slack
channel: "#app-alerts"
8.4 알림 피로 방지
| 전략 | 설명 |
|---|---|
| 디바운싱 | 동일 이슈 알림은 최소 1시간 간격 |
| 에스컬레이션 | 미처리 시 30분 후 재알림 + 상위 에스컬레이션 |
| 근무 시간 | P2 이하는 근무 시간에만 알림 |
| 묶음 알림 | Warning은 1시간 단위 묶음 발송 |
| 자동 해결 | 14일간 재발하지 않으면 자동 Resolved |
부록: 모니터링 설정 체크리스트
초기 설정
| 항목 | 상태 |
|---|---|
| Sentry 프로젝트 생성 (플랫폼별) | [ ] |
| Sentry DSN 환경 변수 등록 | [ ] |
| 소스맵/디버그 심볼 업로드 CI 설정 | [ ] |
| 릴리즈 추적 설정 (version tagging) | [ ] |
| 환경 분리 (staging, production) | [ ] |
| 옵트인 동의 UI 구현 | [ ] |
| 개인정보처리방침 업데이트 | [ ] |
Analytics 설정
| 항목 | 상태 |
|---|---|
| Analytics 도구 선택 및 프로젝트 생성 | [ ] |
| 추적 이벤트 목록 정의 | [ ] |
| 이벤트 추상화 레이어 구현 | [ ] |
| 옵트인/옵트아웃 연동 | [ ] |
| 핵심 대시보드 구성 | [ ] |
| 전환 퍼널 정의 | [ ] |
알림 설정
| 항목 | 상태 |
|---|---|
| Slack 연동 설정 | [ ] |
| P0 ~ P3 알림 규칙 생성 | [ ] |
| PagerDuty 에스컬레이션 정책 | [ ] |
| 주간 리포트 스케줄 | [ ] |
| 알림 담당자/온콜 로테이션 | [ ] |