설정

Flutter 앱이 플랫폼마다 갖춰야 하는 네이티브 설정(sophonz-config.json, Sophonz-Info.plist), 서비스 식별 방식, Dart에서 지정할 수 있는 모든 옵션을 정리합니다.

데이터를 어디로 보내고 어느 서비스의 데이터로 기록할지는 네이티브 설정이 정합니다. Android는 sophonz-config.json, iOS는 Sophonz-Info.plist이며, 둘 다 Dart에서는 바꿀 수 없습니다. Dart 쪽은 Flutter 계층이 무엇을 수집하고 라우트 시간을 어떻게 잴지만 결정합니다.

한눈에 보기

설정Android (sophonz-config.json)iOS (Sophonz-Info.plist)
수집기 URLsdk_config.ingest.collector_urlCollectorURL
서비스 키sdk_config.ingest.service_keyAppKey
서비스 이름애플리케이션 IDAppName
프로젝트sdk_config.ingest.service_namespaceProject
배포 환경sdk_config.ingest.deployment_environmentDeploymentEnvironment
프레임워크sdk_config.app_framework: "flutter"fromPlist(platform: .flutter)
읽는 시점빌드 시점, Gradle 플러그인이 읽음실행 시점, Sophonz.Options.fromPlist가 읽음

서비스 식별

모든 스팬과 로그에는 어느 서비스의 데이터인지 알려 주는 리소스 속성이 붙습니다.

리소스 속성의미
service.key서비스 키. 수집기는 이 값으로 테넌트와 앱을 찾습니다
service.name앱 이름. Android는 애플리케이션 ID, iOS는 AppName이며 없으면 <번들 ID>:<프로세스 이름>
service.namespace프로젝트
deployment.environment.nameproduction, staging 같은 배포 환경

Android와 iOS 빌드를 하나의 앱으로 보려면 두 플랫폼에 같은 서비스 키와 프로젝트를 넣으세요.

WARNING — 서비스 키가 없으면 데이터도 없습니다

수집기는 서비스 키를 강제합니다. 키가 없거나 등록되지 않은 텔레메트리는 오류 없이 버려집니다. 앱은 정상적으로 시작하고 수집하고 업로드까지 하지만 Sophonz에는 아무것도 나타나지 않습니다. 한 플랫폼의 데이터가 보이지 않으면 먼저 그 플랫폼의 키를 확인하세요.

환경을 나눌 때는 앱마다 서비스 키를 하나로 두고, 아래처럼 빌드마다 deployment_environment / DeploymentEnvironment만 바꿉니다.

Android

sophonz-config.json

앱 모듈에 파일을 만듭니다. Gradle 플러그인이 빌드 시점에 SDK에 넣으므로, 내용을 바꾸면 다시 빌드해야 합니다.

android/app/src/main/sophonz-config.json
{
  "sdk_config": {
    "app_framework": "flutter",
    "ingest": {
      "collector_url": "https://in.sophonz.ai",
      "service_key": "sk_replace_me",
      "service_namespace": "my-project",
      "deployment_environment": "production"
    }
  }
}
키기본값효과
sdk_config.app_framework없음flutter로 지정해 Flutter 앱으로 보고되게 합니다
sdk_config.ingest.collector_urlhttps://in.sophonz.ai수집기 기본 URL. SDK가 /v1/traces, /v1/logs를 붙입니다
sdk_config.ingest.service_key없음service.key로 전송. 없으면 아무것도 받아들여지지 않습니다
sdk_config.ingest.service_namespace없음service.namespace로 전송
sdk_config.ingest.deployment_environment없음deployment.environment.name으로 전송
ndk_enabledtrue네이티브(C/C++) 크래시 수집. Flutter 엔진이 네이티브 코드이므로 켜 두세요
app_id, api_token없음텔레메트리가 아니라 매핑 파일 업로드용 자격 증명입니다. 릴리스 빌드 참고

ANR, WebView, 탭, 네트워크 같은 Android SDK의 수집 옵션도 같은 파일에 넣습니다. 목록은 Android 설정 레퍼런스에 있습니다.

CAUTION — 모르는 키는 빌드를 실패시킵니다

파서는 알 수 없는 키를 거부합니다. 오타 하나로도 빌드가 멈추므로, 이 페이지나 Android 설정 레퍼런스에 있는 키만 쓰세요.

빌드 타입·플레이버별 설정

파일은 src/<variant>/, 플레이버 조합, 개별 플레이버, src/<build type>/, src/main/ 순으로 찾으며 처음 찾은 파일을 씁니다. 그래서 디버그 빌드는 다른 환경으로 보고하게 할 수 있습니다.

android/app/src/debug/sophonz-config.json
{
  "sdk_config": {
    "app_framework": "flutter",
    "ingest": {
      "service_key": "sk_replace_me",
      "service_namespace": "my-project",
      "deployment_environment": "development"
    }
  }
}

flutter build apk --flavor staging으로 빌드하면 android/app/src/staging/의 파일을 씁니다.

키를 저장소에 커밋하지 않기

collector_url과 service_key는 빌드 시점의 환경 변수로 넘길 수 있습니다.

SOPHONZ_SERVICE_KEY=sk_... flutter build appbundle --release
변수대체하는 키
SOPHONZ_SERVICE_KEYsdk_config.ingest.service_key
SOPHONZ_COLLECTOR_URLsdk_config.ingest.collector_url

파일에 값이 있으면 환경 변수보다 파일이 우선하므로, 환경 변수를 쓸 때는 파일에서 키를 빼 두세요.

iOS

Sophonz-Info.plist

ios/Sophonz-Info.plist를 만들고 Xcode에서 Runner 타깃에 추가합니다(File > Add Files to "Runner", Runner 타깃 체크). 앱 번들에 리소스로 들어가야 합니다.

ios/Sophonz-Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CollectorURL</key>
  <string>https://in.sophonz.ai</string>
  <key>AppKey</key>
  <string>sk_replace_me</string>
  <key>Project</key>
  <string>my-project</string>
  <key>DeploymentEnvironment</key>
  <string>production</string>
</dict>
</plist>
키필수효과
CollectorURL예수집기 기본 URL. /v1/...을 붙이지 마세요
AppKey예서비스 키. service.key로 전송
AppName아니요service.name으로 전송. 없으면 <번들 ID>:<프로세스 이름>
Project아니요service.namespace로 전송
DeploymentEnvironment아니요deployment.environment.name으로 전송. 기본값 production

CAUTION — 설정이 비어 있으면 SDK가 시작되지 않습니다

Sophonz.Options.fromPlist는 CollectorURL이나 AppKey가 없거나 비어 있으면 nil을 돌려줍니다. 값이 $(SOMETHING) 형태로 남아 있어도 없는 것으로 봅니다. Xcode는 타깃의 Info.plist에서만 빌드 설정을 치환하고, 리소스로 복사되는 plist는 건드리지 않기 때문입니다. 이 파일에는 실제 값을 넣거나, Copy Bundle Resources 전에 빌드 단계에서 파일을 생성하세요.

NOTE — AppName은 최신 Apple SDK가 필요합니다

AppName은 Apple SDK 1.0.0 이후 빌드부터 읽습니다. 1.0.0에서는 무시되고 번들에서 만든 기본 이름이 서비스 이름이 됩니다. 포털은 프로젝트와 service.name으로 앱을 찾으므로, 나중에 이름을 바꾸면 기존 앱 페이지와 데이터가 분리됩니다.

시작 옵션

fromPlist는 나머지 시작 옵션을 인자로 받습니다.

ios/Runner/AppDelegate.swift
let captureServices = CaptureServicesOptionsBuilder()
  .addDefaults()
  .addPushNotificationCaptureService(withOptions: .init())
  .build()
 
if let options = Sophonz.Options.fromPlist(
  platform: .flutter,
  captureServices: captureServices,
  crashReporter: .sophonz,
  logLevel: .default
) {
  try? Sophonz.start(options: options)
}
인자기본값설명
platform.native.flutter를 넘깁니다
captureServices.default()푸시 알림 수집은 기본값에 없으므로 위처럼 추가합니다. 전체 목록은 iOS 계측 참고
crashReporter.sophonz크래시 수집을 끄려면 .none
logLevel.defaultSDK 자체의 콘솔 로그 수준
otelnil직접 만든 익스포터를 담은 Sophonz.OTelOptions. iOS에서 다른 곳으로 내보내는 유일한 방법입니다

plist 대신 코드로 설정하려면 Sophonz.Options.withCollector(url:appKey:project:deploymentEnvironment:platform:)를 사용합니다. $(...)가 치환되는 타깃의 Info.plist에서 값을 읽는 식으로, plist를 생성하지 않고도 빌드 구성마다 값을 바꿀 수 있습니다.

Dart

Dart에는 수집기나 키 설정이 없습니다. 존재하는 옵션은 다음이 전부입니다.

옵션위치기본값
actionSophonz.instance.start없음
routeSettingsExtractorSophonzNavigationObserver, SophonzGoRouterObserverroute.settings
screenLoadConfig.recencyThreshold두 옵저버, SophonzScreenLoadConfig로 지정1초
routerSophonzGoRouterObserver없음
internalClientSophonzHttpClient새 http.Client()
endpoint, headers, timeoutSecondsaddSpanExporter, addLogRecordExporter네이티브 SDK 타임아웃

느린 프레임, 멈춘 프레임, 아이솔레이트 멈춤의 기준값은 0.1.0에서 고정되어 있어 바꿀 수 없습니다. 값은 계측에 있습니다.

start

await Sophonz.instance.start(action: () => runApp(const MyApp()));

action을 넘기면 start는 FlutterError.onError를 감싸고(기존 핸들러는 유지하고 기록 후 호출합니다), PlatformDispatcher.instance.onError를 설정한 뒤 action을 실행합니다. action이 없으면 오류 핸들러를 설치하지 않으므로 나중에 직접 설치합니다.

await Sophonz.instance.start();
await Sophonz.instance.installErrorHandlers(() => runApp(const MyApp()));

CAUTION — PlatformDispatcher.onError는 이어 붙지 않고 교체됩니다

FlutterError.onError는 기존 핸들러를 유지하지만 PlatformDispatcher.instance.onError는 덮어씁니다. start 전에 설정한 핸들러는 더 이상 실행되지 않고, start 후에 설정하면 Sophonz가 그 오류를 받지 못합니다. 둘 다 필요하면 start 후에 직접 핸들러를 설정하고 그 안에서 Sophonz.instance.logDartError(error, stack)를 호출하세요.

내비게이션

SophonzNavigationObserver(
  routeSettingsExtractor: (route) => route.settings,
  screenLoadConfig: const SophonzScreenLoadConfig(
    recencyThreshold: Duration(milliseconds: 500),
  ),
)
옵션효과
routeSettingsExtractor이름을 기록할 RouteSettings를 돌려줍니다. null을 돌려주면 그 라우트는 추적하지 않습니다
screenLoadConfig.recencyThreshold화면 로드의 시작으로 볼 터치가 얼마나 최근이어야 하는지. 더 오래된 터치면 push 시점을 시작으로 씁니다

라우트 이름, go_router, 옵저버별 기록 내용은 내비게이션에서 설명합니다.

추가 익스포터 (Android 전용)

Sophonz.instance.addSpanExporter(
  endpoint: 'https://otlp.example.com/v1/traces',
  headers: [{'x-api-key': 'token'}],
  timeoutSeconds: 10,
);
Sophonz.instance.addLogRecordExporter(
  endpoint: 'https://otlp.example.com/v1/logs',
);
await Sophonz.instance.start(action: () => runApp(const MyApp()));
파라미터설명
endpoint/v1/traces 또는 /v1/logs까지 포함한 전체 OTLP/HTTP URL. 빈 문자열은 무시됩니다
headers헤더마다 항목 하나짜리 맵을 담은 리스트
timeoutSeconds내보내기 타임아웃. 생략하면 네이티브 기본값

Sophonz로 보내는 것에 더해 추가로 내보내는 것이며, 대체하지 않습니다.

CAUTION — 익스포터는 네이티브 SDK가 시작되기 전에 전달돼야 합니다

익스포터는 Dart에 쌓였다가 start()가 실행될 때 네이티브로 넘어갑니다. start() 이후의 호출은 무시됩니다. Application.onCreate()에서 이미 Android SDK를 시작했다면 전달된 익스포터도 너무 늦게 도착해 무시되므로, 둘을 함께 쓰려면 앱 시작 시간 측정을 포기하거나 Kotlin에서 Sophonz.start(this) 전에 익스포터를 등록해야 합니다. iOS에는 아예 전달되지 않으므로 시작 옵션의 otel:을 사용하세요.

다음 단계

  • 계측 — 계층별로 자동 수집되는 항목
  • 릴리스 빌드 — 읽을 수 있는 스택 트레이스를 위한 매핑 파일과 dSYM