Skip to Content
Native Appnative 로깅·관측 (observability)

native 로깅·관측 (observability)

native 앱(apps/native-app)도 web 과 동일한 로깅 원칙을 씁니다: 애플리케이션 코드는 언제나 공유 @howmuchhome-web/logger(Logger) 를 통해서만 로깅하고, Datadog·Sentry SDK 를 직접 호출하지 않습니다. web 과 다른 건 그 아래 배관(plumbing) 뿐입니다 — web 은 브라우저 SDK 를, native 는 Metro resolver 로 브리지된 RN SDK 를 씁니다.

로거 API(Logger.log / addAction / startView / captureError)와 web 초기화는 로깅 · @howmuchhome-web/logger 를 보세요. 이 문서는 native 에서 그 API 가 실제로 어디로 흘러가는지(RN Datadog/Sentry 로의 브리지, init 조건, RUM/에러 필터)에만 집중합니다.

한눈에 — 로그는 어디로 가나

애플리케이션 코드가 Logger.* 를 호출하면 native 에서 다음 경로를 탑니다. Logger 자체는 web·native 공용(packages/logger/index.ts)이며, 그 안에서 import 하는 SDK 를 Metro 가 native 구현으로 바꿔치기합니다.

Logger 호출공유 로거가 부르는 SDKnative 에서 실제 도착지
Logger.log(msg, {type, context})datadogLogs.logger.logDatadog Logs (DdLogs.info/warn/error/debug)
Logger.addAction(name, ctx)datadogRum.addActionDatadog RUM 커스텀 액션 (DdRum.addAction)
Logger.startView(name)datadogRum.startViewDatadog RUM 뷰 (DdRum.startView)
Logger.captureError(err, opts)@sentry/nextjs (captureException 등)Sentry RN (@sentry/react-native)
(사용자 컨텍스트)datadogRum.setUser/clearUserDdSdkReactNative.setUserInfo/clearUserInfo

핵심 mental model:

  • RUM(뷰·액션·리소스·터치)과 LogsDatadog (mobile RN SDK).
  • 에러/예외 리포팅Sentry (@sentry/react-native). Datadog 의 native crash 리포팅은 꺼져 있습니다(nativeCrashReportEnabled: false) — crash 는 Sentry 담당.
  • Datadog 미init(또는 네이티브 모듈 부재)이면 RUM/Logs 호출은 전부 no-op — 조용히 버려지고 에러는 안 납니다. (아래 왜 native 에서 로그가 안 잡히나 참고)

브리지 방식 — Metro resolver

공유 packages/logger/index.ts 는 web 을 전제로 아래를 직접 import 합니다:

import {datadogLogs} from '@datadog/browser-logs'; import {datadogRum} from '@datadog/browser-rum'; import * as Sentry from '@sentry/nextjs';

이 셋은 전부 브라우저/Next 전용이라 RN 에서는 그대로 못 씁니다. 공유 패키지를 수정하지 않고, apps/native-app/metro.config.js 의 커스텀 resolveRequest 가 native 번들에서만 이들을 갈아끼웁니다:

import 경로native resolve 결과
@datadog/browser-logssrc/shims/datadogMobile.ts (어댑터)
@datadog/browser-rumsrc/shims/datadogMobile.ts (어댑터)
@sentry/nextjs@sentry/react-native (redirect)

Sentry redirect 가 성립하는 이유: 로거가 쓰는 Sentry.setUser / withScope / captureException 등은 @sentry/nextjs@sentry/react-native 양쪽 공통 API 라 시그니처가 같습니다. 모노레포의 @sentry/* JS SDK 는 web·native 모두 v10 으로 통일돼 있어 버전 스큐도 없습니다. (metro 는 @sentry/core 등의 exports 서브패스를 해석 못 해서, require.resolve 로 실제 파일을 찾아주는 별도 보정이 같은 파일에 있습니다 — 로깅 동작과는 무관한 번들링 이슈.)

Datadog 어댑터 (src/shims/datadogMobile.ts)

이 shim 이 브라우저 datadogLogs/datadogRum 인터페이스를 @datadog/mobile-react-nativeDdLogs/DdRum/DdSdkReactNative 로 매핑합니다. 매 호출마다 isDatadogReady() 게이트를 먼저 확인하고, false 면 즉시 return(no-op) 합니다.

// src/shims/datadogMobile.ts (요지) export const datadogRum = { init: () => {}, // 실제 init 은 lib/datadog 의 DatadogProvider 가 담당 addAction: (name, context) => { if (!isDatadogReady()) return; void DdRum.addAction(RumActionType.CUSTOM, name, context ?? {}); }, startView: (name) => { if (!isDatadogReady()) return; void DdRum.startView(name, name, {}); }, // setUser → DdSdkReactNative.setUserInfo, clearUser → clearUserInfo ... };

어댑터의 init 은 의도적으로 빈 함수입니다. web 은 datadogRum.init() 으로 초기화하지만, native 의 실제 init 은 <DatadogProvider>(아래)가 담당하기 때문입니다.

초기화

web 은 LoggerProvider(RUM init + 사용자 컨텍스트)와 initSentry() 로 초기화합니다. native 는 LoggerProvider 를 쓰지 않고, apps/native-app/src/app/_layout.tsx 에서 직접 두 SDK 를 부트합니다.

Datadog — DatadogProvider

src/lib/datadog.tsbuildDatadogConfiguration() 이 RUM/Logs/Trace 설정을 만들고, _layout.tsx 가 앱 트리를 <DatadogProvider> 로 감쌉니다.

// src/app/_layout.tsx (요지) const datadogConfiguration = useMemo(() => buildDatadogConfiguration(), []); // ... <DatadogProvider configuration={datadogConfiguration} onInitialization={onDatadogInitialized}> {/* 앱 */} </DatadogProvider>
  • 자격증명: CLIENT_TOKEN·RUM_APPLICATION_ID 는 공개 클라이언트 식별자(앱 바이너리에 구워지도록 설계된 값 — web 의 NEXT_PUBLIC_DATADOG_*, Sentry DSN 과 같은 성격)라 lib/datadog.ts 에 인라인돼 있습니다. 이 값이 없으면 buildDatadogConfiguration()null 을 반환하고 provider 가 미적용 → 미계측.
  • service: 'howmuchhome-native-app'. RUM application 은 web RUM 앱과 별개입니다(별도 모바일 RUM 애플리케이션).
  • env: APP_ENV(local/development/qa/production/canary). site 는 US1(datadoghq.com, web 과 동일).
  • onInitializationonDatadogInitialized(): RUM init 이 끝나면 호출됩니다. 여기서 ready = true 로 표시(→ isDatadogReady() 가 true 가 되어 어댑터 호출이 통과)하고, Session Replay 를 enable 합니다.
  • 트래킹 옵션: trackInteractions(터치)·trackResources(네트워크)·trackErrors(에러) 모두 on, sessionSampleRate: 100.
  • nativeCrashReportEnabled: false: crash 는 Sentry 가 담당하므로 Datadog native crash 는 끕니다.

⚠️ @datadog/mobile-react-native 계열은 네이티브 모듈이라 EAS/dev client 재빌드가 선행돼야 합니다. 재빌드 없이 JS 만 바뀐 상태에서는 네이티브 모듈이 없어 SessionReplay.enable·startTrackingViews 가 throw 하지만, 각각 try/catch 로 감싸 무시하므로 앱은 죽지 않고 리플레이/뷰추적만 비활성됩니다.

Session Replay 마스킹

본인인증·신분증 촬영·개인정보를 다루므로 web RUM(mask-user-input)과 동일하게 보수적으로 마스킹합니다: 모든 입력(MASK_ALL_INPUTS), 비번들 이미지(신분증·매물 사진 등, MASK_NON_BUNDLED_ONLY) 마스킹. 터치는 표시(TouchPrivacyLevel.SHOW, PII 아님).

Sentry — initSentry

src/lib/sentry.tsinitSentry()_layout.tsx 모듈 평가 시점(컴포넌트 렌더 이전)에 호출합니다.

// src/lib/sentry.ts (요지) Sentry.init({ dsn: DSN, environment: APP_ENV, enabled: !isDebugBuild, // ⚠️ 디버그 빌드에서는 Sentry 비활성 release: `${appVersion}+${appBuildCode}`, tracesSampleRate: APP_ENV === 'production' ? 0.1 : 1.0, sendDefaultPii: false, enableLogs: false, ignoreErrors: ['not attached to window manager'], });

주의할 조건:

  • enabled: !isDebugBuildisDebugBuild === __DEV__ 입니다(src/config/env.ts). 즉 Metro dev(개발) 빌드에서는 Sentry 가 아예 비활성입니다. 로컬에서 에러를 던져도 Sentry 대시보드에 안 뜨는 게 정상 — 확인하려면 release/preview 빌드가 필요합니다.
  • DSN 은 v2 전용 프로젝트(howmuchhome-native-app)의 것으로, 보안 비밀이 아닌 프로젝트 식별자라 코드에 인라인돼 있습니다.
  • release${appVersion}+${appBuildCode}(네이티브 앱 버전 + 빌드코드, src/lib/appInfo.ts)로 세팅됩니다.
  • ignoreErrors: ['not attached to window manager'] — RN LogBox(개발용 에러 오버레이)가 액티비티 destroy/재생성 중 자기 다이얼로그를 dismiss 하다 내는 양성 노이즈로, dev support 빌드에서만 발생(release 엔 LogBox 부재)합니다. production Sentry 오염 방지용 드롭.

RUM 뷰·액션 추적

화면 전환 (뷰)

두 경로로 RUM 뷰가 잡힙니다:

  1. 자동startDatadogViewTracking(navigationRef)(lib/datadog.ts)가 DdRumReactNavigationTracking.startTrackingViews 로 expo-router(=React Navigation) 화면 전환을 RUM 뷰로 추적합니다. _layout.tsx 는 navigation 컨테이너가 ready 된 뒤에 이걸 등록합니다(navigationRef.isReady() 확인 또는 'state' 리스너 대기).
  2. 수동/보완 — Funnel 이 단계 전환마다 Logger.startView(viewName) 를 자동 호출합니다(packages/funnel/src/Funnel.native.tsx). 다단계 플로우의 논리적 단계를 뷰로 남기는 보완 경로입니다.

커스텀 액션

버튼 클릭·플로우 시작 등은 Logger.addAction(name, context) 로 남깁니다. native 코드에서 실제 사용 예:

Logger.addAction('vote_normal_entry', {/* ... */}); Logger.startView('tax-chat'); Logger.addAction('tax-chat-session-start', {/* ... */});

에러 캡처·필터

애플리케이션 에러

Logger.captureError(err, opts) → (redirect 된) @sentry/react-native 로 전송됩니다. API 요청 에러는 web 과 마찬가지로 @howmuchhome-web/api fetcher 가 자동 Sentry 전송하므로 별도 호출이 불필요합니다. Logger.captureError 는 API 외 비즈니스 로직·외부 SDK 예외 등에 씁니다.

RUM 에러 노이즈 필터 — errorEventMapper

cold start 의 “navigation not initialized” 노이즈를 RUM 에서 제외하는 필터가 buildDatadogConfiguration()rumConfiguration.errorEventMapper 에 있습니다.

errorEventMapper: event => event.message.includes("hasn't been initialized yet") ? null : event,

배경: React Navigation 은 컨테이너가 ready 되기 전에 navigation 이 접근되면(예: Datadog 의 nav 뷰 추적이 state 변경마다 호출하는 getCurrentRoute, cold start 푸시/딥링크 라우팅 중 transient) "The 'navigation' object hasn't been initialized yet"console.error 로 남깁니다. 이 호출은 undefined 를 반환하고 Datadog 가 graceful 하게 처리하므로 기능상 무해하지만, trackErrors 가 이 console.error 를 RUM 에러로 캡처해 버립니다. errorEventMapper 로 이 양성 메시지만 RUM 에서 드롭합니다. (router 자체는 큐 안전 — 원인이 아님.)

소스맵 · debug ID

Sentry RN 이벤트를 심볼리케이트하려면 소스맵이 필요합니다. native 는 debug ID 방식으로 처리합니다:

  • metro.config.jsgetSentryExpoConfigSentry serializer 를 붙여, expo export 시 번들과 소스맵에 debug ID 를 주입합니다. 이 debug ID 로 OTA(R2)로 배포된 JS 의 Sentry 이벤트가 release/dist 매칭 없이 심볼리케이트됩니다.
  • 소스맵 업로드는 빌드/OTA 파이프라인에서 sentry-expo-upload-sourcemaps dist 로 수행합니다(OTA 릴리즈는 scripts/release-update.mjs 계열).

빌드·OTA·릴리즈 파이프라인 전반은 이 문서 범위 밖입니다. apps/native-app/README.md, apps/native-app/package.json 의 빌드/ota:release 스크립트, apps/native-app/scripts/release-update.mjs 를 참고하세요. (환경/빌드 개요)

왜 native 에서 로그가 안 잡히나

디버깅 시 자주 겪는 케이스와 원인:

증상원인
로컬(Metro dev)에서 던진 에러가 Sentry 에 안 뜸enabled: !isDebugBuild — dev 빌드에서 Sentry 비활성. release/preview 빌드로 확인.
Logger.addAction/startView/logDatadog 에 안 뜸init 전(또는 네이티브 모듈 부재) → isDatadogReady() 가 false → 어댑터 no-op. onDatadogInitialized 가 불렸는지 확인.
Datadog 자격증명 미설정 시 계측이 아예 없음buildDatadogConfiguration()null → provider 미적용.
Datadog 네이티브 모듈 관련 기능(Session Replay/뷰추적) 만 안 됨JS 만 바뀌고 재빌드 안 함 → 네이티브 모듈 부재. try/catch 로 무시되어 RUM/Logs 는 계속 동작하지만 리플레이/뷰추적은 비활성. EAS/dev client 재빌드 필요.
RUM 에러에 "...hasn't been initialized yet"안 보임의도된 것 — errorEventMapper 로 드롭됨.
crash 가 Datadog 엔 없고 Sentry 에만 있음의도된 것 — nativeCrashReportEnabled: false, crash 는 Sentry 담당.

관련 파일

파일역할
apps/native-app/src/lib/datadog.tsDatadogProvider 설정·init 콜백·RUM 뷰추적·isDatadogReady
apps/native-app/src/shims/datadogMobile.ts브라우저 datadogLogs/datadogRum → RN DdLogs/DdRum 어댑터
apps/native-app/src/lib/sentry.tsinitSentry (RN Sentry init)
apps/native-app/metro.config.jsdatadog SDK → 어댑터 shim, @sentry/nextjs@sentry/react-native redirect, Sentry serializer(debug ID)
apps/native-app/src/app/_layout.tsxinitSentry() 호출·<DatadogProvider> 마운트·뷰추적 등록
apps/native-app/src/config/env.tsAPP_ENV·isDebugBuild
packages/logger/index.ts공유 Logger API (web·native 공용)
Last updated on