내비게이션
@sophonz/react-native-navigation으로 expo-router, React Navigation, react-native-navigation의 화면마다 ux.view 스팬을 기록하는 방법 — 설정, 스팬 이름과 속성, 앱 상태 처리, 옵션을 설명합니다.
@sophonz/react-native-navigation은 화면 전환을 스팬으로 바꿉니다. 이미 쓰고 있는 내비게이션 라이브러리를 구독해 라우트가 바뀌면 이전 화면의 스팬을 끝내고 새 라우트의 스팬을 시작하므로, 세션 타임라인에서 어떤 일이 일어났을 때 사용자가 어느 화면에 있었는지 볼 수 있습니다. 패키지는 순수 JavaScript이며 tracer provider를 통해 기록합니다.
설치
npm install @sophonz/react-native-navigation@0.1.0 @sophonz/react-native-tracer-provider@0.1.0| 라이브러리 | 컴포넌트 | 넘길 ref |
|---|---|---|
| expo-router | SophonzNavigationTracker | expo-router의 useNavigationContainerRef() |
@react-navigation/native | SophonzNavigationTracker | useRef(useNavigationContainerRef()) |
| react-native-navigation | SophonzNativeNavigationTracker | useRef(Navigation.events()) |
@react-navigation/native와 react-native-navigation은 선택적 peer dependency입니다. 쓰는 것만 설치하세요.
expo-router
app/_layout.tsx에서 루트 스택을 감쌉니다. 첫 화면의 스팬을 놓치지 않도록 tracer provider가 준비된 뒤에 트래커를 렌더링하세요.
import {Stack, useNavigationContainerRef} from "expo-router";
import {useSophonz} from "@sophonz/react-native";
import {useSophonzNativeTracerProvider} from "@sophonz/react-native-tracer-provider";
import {SophonzNavigationTracker} from "@sophonz/react-native-navigation";
// 설정 플러그인을 쓰는 Expo는 SDK를 네이티브에서 시작하지만, 이 옵션은 여전히 적용됩니다
const SOPHONZ_CONFIG = {trackUnhandledRejections: true};
export default function RootLayout() {
const {isStarted} = useSophonz(SOPHONZ_CONFIG);
const {tracerProvider} = useSophonzNativeTracerProvider(undefined, isStarted);
const navigationRef = useNavigationContainerRef();
if (!tracerProvider) {
return null; // 또는 스플래시 화면
}
return (
<SophonzNavigationTracker
ref={navigationRef}
tracerProvider={tracerProvider}
screenAttributes={{"app.flavor": "production"}}>
<Stack>
<Stack.Screen name="(tabs)" options={{headerShown: false}} />
<Stack.Screen name="+not-found" />
</Stack>
</SophonzNavigationTracker>
);
}React Navigation
@react-navigation/native의 useNavigationContainerRef는 ref가 아니라 내비게이션 객체 자체를 돌려줍니다. 그 객체는 NavigationContainer에, 그 객체를 담은 useRef는 트래커에 넘기세요.
import {useRef} from "react";
import {NavigationContainer, useNavigationContainerRef} from "@react-navigation/native";
import {createNativeStackNavigator} from "@react-navigation/native-stack";
import {useSophonz} from "@sophonz/react-native";
import {useSophonzNativeTracerProvider} from "@sophonz/react-native-tracer-provider";
import {SophonzNavigationTracker} from "@sophonz/react-native-navigation";
const Stack = createNativeStackNavigator();
const SOPHONZ_CONFIG = {trackUnhandledRejections: true};
export default function App() {
const {isStarted} = useSophonz(SOPHONZ_CONFIG);
const {tracerProvider} = useSophonzNativeTracerProvider(undefined, isStarted);
const navigation = useNavigationContainerRef();
const trackerRef = useRef(navigation);
if (!tracerProvider) {
return null;
}
return (
<NavigationContainer ref={navigation}>
<SophonzNavigationTracker ref={trackerRef} tracerProvider={tracerProvider}>
<Stack.Navigator>
<Stack.Screen name="Cart" component={CartScreen} />
<Stack.Screen name="Checkout" component={CheckoutScreen} />
</Stack.Navigator>
</SophonzNavigationTracker>
</NavigationContainer>
);
}react-native-navigation
SophonzNativeNavigationTracker는 Navigation.events()의 componentDidAppear와 componentDidDisappear를 구독합니다. 이 이벤트는 전역이므로 모든 화면이 아니라 계속 등록되어 있는 컴포넌트 하나(보통 첫 화면)만 감싸세요. 여러 곳을 감싸면 감싼 수만큼 리스너가 추가됩니다.
import {useRef} from "react";
import {Navigation} from "react-native-navigation";
import {initialize} from "@sophonz/react-native";
import {SophonzNativeTracerProvider} from "@sophonz/react-native-tracer-provider";
import {SophonzNativeNavigationTracker} from "@sophonz/react-native-navigation";
const start = async () => {
await initialize({sdkConfig: {trackUnhandledRejections: true}});
// provider를 만들기 전에 SDK가 실행 중이어야 합니다
const tracerProvider = new SophonzNativeTracerProvider();
Navigation.registerComponent(
"Home",
() => props => {
const events = useRef(Navigation.events());
return (
<SophonzNativeNavigationTracker ref={events} tracerProvider={tracerProvider}>
<HomeScreen {...props} />
</SophonzNativeNavigationTracker>
);
},
() => HomeScreen,
);
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({root: {stack: {children: [{component: {name: "Home"}}]}}});
});
};
start();기록 내용
화면 방문마다 스팬 하나입니다.
| 필드 | 값 |
|---|---|
| 스팬 이름 | 라우트 이름(getCurrentRoute().name). react-native-navigation은 컴포넌트 이름 |
spz.type | ux.view. screenAttributes에 spz.type을 넣어도 항상 이 값이 우선합니다 |
view.name | 쿼리용으로 한 번 더 넣는 라우트 이름 |
view.launch | 트래커가 마운트된 뒤 첫 화면이면 true, 이후는 false |
view.state.end | 스팬이 끝날 때의 앱 상태. 일반 화면 이동이면 active, 앱이 포그라운드를 떠났다면 background나 inactive |
view.unmount | 트래커가 언마운트되어 끝난 스팬이면 true |
| 사용자 속성 | screenAttributes의 모든 값 |
화면 스팬은 라우트 이름이 바뀔 때 시작합니다. 같은 라우트로 파라미터만 바꿔 이동하거나 다시 렌더링하는 것으로는 새 스팬이 생기지 않습니다.
expo-router에서 라우트 이름은 URL이 아니라 라우터가 알려 주는 파일 기반 이름(index, (tabs) 등)입니다. 동적 라우트를 묶어 보고 싶다면 원하는 값을 screenAttributes에 넣거나 커스텀 스팬을 기록하세요.
앱 상태
트래커는 AppState도 따라갑니다.
- 앱이
background나inactive가 되면 현재 화면의 스팬이 끝나고view.state.end에 그 상태가 들어갑니다. active로 돌아오면 같은 화면의 새 스팬이view.launch: false로 시작합니다.
그래서 다른 앱에 다녀오느라 중간에 끊긴 화면 방문은 스팬 두 개로 보입니다. iOS에서는 제어 센터나 전화 수신 배너 같은 시스템 오버레이 때문에도 잠깐 inactive가 됩니다.
옵션
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
ref | ref | 필수 | 설치의 표 참고 |
tracerProvider | TracerProvider | 전역 provider | 기록에 쓸 provider. Sophonz provider를 넘기거나, 먼저 전역으로 등록하세요 |
screenAttributes | Attributes | {} | 모든 화면 스팬에 추가됩니다 |
tracerOptions | TracerOptions | 없음 | getTracer에 전달됩니다. schemaUrl용 |
debug | boolean | true | 트래커 동작을 [Sophonz] 접두어로 콘솔에 출력합니다. 프로덕션 빌드에서는 false로 두세요 |
children | node | 필수 | 내비게이터 |
tracer 이름은 @sophonz/react-native-navigation, 버전은 0.1.0입니다. iOS에서는 tracer 이름이 기록되지 않습니다.
네이티브 화면 수집
네이티브 SDK는 자체적으로 네이티브 화면을 기록합니다. Android에서는 보통 MainActivity 하나, iOS에서는 내비게이션 라이브러리가 만든 뷰 컨트롤러입니다. 이 패키지를 쓰면 그 스팬이 라우트 스팬과 겹치거나 어긋납니다. 라우트 스팬만 보고 싶다면 네이티브 수집을 끄세요.
{
"sdk_config": {
"view_config": {"enable_automatic_activity_capture": false}
}
}// iOS, JavaScript가 SDK를 시작하는 경우
await initialize({sdkConfig: {ios: {collectorUrl: "https://in.sophonz.ai", appKey: "sk_ios_replace_me", disableAutomaticViewCapture: true}}});iOS에서 SDK를 네이티브로 시작한다면 SophonzInitializer.swift에서 서비스를 제거합니다.
let services = CaptureServicesOptionsBuilder()
.addDefaults()
.remove(ofType: ViewCaptureService.self)
.build()
try Sophonz.start(
options: .withCollector(
url: "https://in.sophonz.ai",
appKey: "sk_ios_replace_me",
platform: .reactNative,
captureServices: services
)
)화면이 기록되지 않을 때
debug를 켜 두면 콘솔에 이유가 나옵니다.
Navigation ref is not available— 트래커가 렌더링될 때ref에 값이 없었습니다. React Navigation에서는 내비게이션 객체 자체가 아니라useRef(useNavigationContainerRef())를 넘기세요.No TracerProvider found. Using global tracer instead.—tracerProvider속성도 없고trace.setGlobalTracerProvider로 등록한 것도 없습니다. 스팬은 no-op tracer로 갑니다.no tracer available, not creating a span— provider가 생기기 전에 트래커가 렌더링됐습니다.tracerProvider가 준비된 뒤에만 렌더링하세요.