API 레퍼런스
Sophonz Flutter SDK의 모든 공개 Dart API — 수명 주기, 오류, 로그, 스팬, 네트워크 요청, 뷰, 사용자, 세션, 익스포터, OpenTelemetry API — 를 시그니처와 Android·iOS 지원 여부와 함께 정리합니다.
API 대부분은 Sophonz.instance에 있습니다. 호출은 메서드 채널을 통해 네이티브 SDK로 전달되므로 대부분 곧바로 반환되고 실제 작업은 네이티브에서 이루어집니다. void를 반환하는 메서드는 예외를 던지지 않습니다. start() 전의 호출을 포함해 실패한 호출은 버려지며, SDK가 시작될 때까지 쌓아 두지 않습니다.
Import
| import | 제공하는 것 |
|---|---|
package:sophonz/sophonz.dart | Sophonz, SophonzNavigationObserver, SophonzScreenLoadConfig, SophonzRouteSettingsExtractor, SophonzHttpClient, HttpMethod, W3cTraceContext |
package:sophonz/sophonz_api.dart | SophonzSpan, SophonzSpanEvent, SophonzNetworkRequest, Severity |
package:sophonz_platform_interface/sophonz_platform_interface.dart | ErrorCode |
package:sophonz_platform_interface/last_run_end_state.dart | LastRunEndState |
package:sophonz/sophonz_samples.dart | 테스트용 트리거 SophonzSamples |
package:sophonz_dio/sophonz_dio.dart | SophonzInterceptor |
package:sophonz_go_router/sophonz_go_router.dart | SophonzGoRouterObserver |
sophonz.dart는 나머지 행의 타입을 다시 내보내지 않습니다. sophonz_platform_interface를 직접 import하려면 pubspec.yaml의 dependencies와 dependency_overrides 양쪽에 추가해야 합니다(설치 참고).
NOTE — Severity가 두 곳에 있습니다
sophonz_api.dart와 dartastic_opentelemetry_api가 모두 Severity를 선언합니다. 둘을 함께 import하는 파일에서는 하나를 숨기세요. 예: import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart' hide Severity;
플랫폼 지원
| API | Android | iOS |
|---|---|---|
| 수명 주기, 오류, 로그, 브레드크럼 | 지원 | 지원 |
스팬(startSpan, recordCompletedSpan, SophonzSpan) | 지원 | 지원 |
recordNetworkRequest | 지원. 표준 메서드만 | 지원 |
startView, endView | 지원 | 지원 |
setUserIdentifier, 페르소나, 결제 사용자 | 지원 | 지원. 페르소나는 프로세스 수명 |
setUserName, setUserEmail | 지원 | 무시 |
세션 속성, endSession | 지원 | 지원. endSession은 5초에 한 번까지 |
logPushNotification | 지원 | 지원. title과 body 필요 |
addSpanExporter, addLogRecordExporter | 지원. start 전에 호출 | 무시 |
| OpenTelemetry API 스팬 | 일부, 아래 참고 | 일부 |
수명 주기
| 시그니처 | 설명 |
|---|---|
Future<void> start({FutureOr<void> Function()? action}) | 네이티브 SDK에 연결하고 Dart 쪽 수집을 시작합니다. action이 있으면 오류 핸들러를 설치하고 action을 실행한 뒤 첫 프레임까지의 시간을 기록합니다 |
Future<void> installErrorHandlers(FutureOr<void> Function() action) | action 없이 start를 호출한 경우 오류 핸들러를 설치하고 action을 실행합니다 |
void disable() | Dart와 네이티브 SDK의 수집을 멈춥니다. 같은 프로세스에서는 다시 시작할 수 없습니다 |
Future<LastRunEndState> getLastRunEndState() | crash, cleanExit, 알 수 없거나 SDK가 실행 중이 아니면 invalid |
Future<String?> getDeviceId() | 네이티브 SDK가 부여한 기기 식별자 또는 null |
start는 enableIntegrationTesting도 받지만 사용 중단되었으며 아무 효과가 없습니다.
import 'package:sophonz_platform_interface/last_run_end_state.dart';
final lastRun = await Sophonz.instance.getLastRunEndState();
if (lastRun == LastRunEndState.crash) {
showCrashRecoveryHint();
}start는 한 번만 호출하세요. 두 번째 호출은 다시 연결하지는 않지만 프레임·멈춤·수명 주기 모니터와 오류 핸들러를 한 번 더 설치합니다.
오류
start(action: ...)이나 installErrorHandlers를 실행하면 처리되지 않은 오류가 수집됩니다. 직접 잡은 오류는 다음과 같이 기록합니다.
try {
await checkout();
} catch (error, stack) {
Sophonz.instance.logHandledDartError(error, stack);
}| 시그니처 | 설명 |
|---|---|
void logDartError(Object error, StackTrace stack) | 처리되지 않은 오류로 기록 |
void logHandledDartError(Object error, StackTrace stack) | 처리된 오류로 기록. 오류 없는 세션 비율 계산에 포함되지 않습니다 |
오류는 exception.type, exception.message, exception.stacktrace, spz.exception_handling을 담은 error 수준 로그로 전송되며, 스택은 Dart 스택입니다. 계측을 참고하세요.
로그와 브레드크럼
Sophonz.instance.logInfo('Checkout started');
Sophonz.instance.logError('Payment declined', properties: {'order.id': 'A-1042'});
Sophonz.instance.addBreadcrumb('Tapped pay');| 시그니처 | 설명 |
|---|---|
void logInfo(String message, {Map<String, String>? properties}) | info 수준 로그 |
void logWarning(String message, {Map<String, String>? properties}) | warning 수준 로그 |
void logError(String message, {Map<String, String>? properties}) | error 수준 로그 |
void logMessage(String message, Severity severity, {Map<String, String>? properties}) | Severity.info, warning, error 중 지정한 수준의 로그 |
void addBreadcrumb(String message) | 세션 타임라인에 남는 가벼운 이벤트 |
properties는 로그 속성이 됩니다. 개인정보를 넣지 마세요.
푸시 알림
void logPushNotification(
String? title,
String? body, {
String? subtitle,
int? badge,
String? category,
String? from,
String? messageId,
int? priority,
bool hasNotification = false,
bool hasData = false,
})| 파라미터 | 플랫폼 |
|---|---|
title, body | 공통. iOS는 둘 다 null이 아니어야 기록합니다 |
subtitle, badge, category | iOS |
from, messageId, priority, hasNotification, hasData | Android |
iOS에서 UNUserNotificationCenter로 받는 알림은 시작할 때 PushNotificationCaptureService를 추가해 네이티브로 수집할 수도 있습니다(설정). 같은 알림을 두 방식으로 모두 기록하지는 마세요.
스팬
import 'package:sophonz/sophonz.dart';
import 'package:sophonz_platform_interface/sophonz_platform_interface.dart' show ErrorCode;
final span = await Sophonz.instance.startSpan('load-catalog');
try {
final items = await loadCatalog();
await span?.addAttribute('item.count', '${items.length}');
await span?.stop();
} catch (_) {
await span?.stop(errorCode: ErrorCode.failure);
rethrow;
}| 시그니처 | 설명 |
|---|---|
Future<SophonzSpan?> startSpan(String name, {SophonzSpan? parent, int? startTimeMs}) | 스팬을 시작합니다. SDK가 실행 중이 아니거나 스팬을 만들지 못하면 null |
Future<bool> recordCompletedSpan(String name, int startTimeMs, int endTimeMs, {ErrorCode? errorCode, SophonzSpan? parent, Map<String, String>? attributes, List<SophonzSpanEvent>? events}) | 이미 끝난 작업을 스팬으로 기록합니다. 시간은 epoch 밀리초 |
Future<T> recordSpan<T>(String name, {SophonzSpan? parent, Map<String, String>? attributes, List<SophonzSpanEvent>? events, required Future<T> Function() code}) | code를 시작과 끝 사이에서 실행합니다 |
parent가 없는 스팬은 새 트레이스를 시작합니다.
SophonzSpan
| 멤버 | 설명 |
|---|---|
String id | 스팬 ID |
Future<String?> traceId | 트레이스 ID |
Future<bool> stop({ErrorCode? errorCode, int? endTimeMs}) | 스팬을 끝냅니다. ErrorCode.failure, abandon, unknown을 넘기면 실패로 표시됩니다 |
Future<bool> addEvent(String name, {int? timestampMs, Map<String, String>? attributes}) | 스팬 이벤트 추가. timestampMs를 생략하면 현재 시각 |
Future<bool> addAttribute(String key, String value) | 문자열 속성 추가 |
반환되는 bool은 이미 멈춘 스팬처럼 스팬을 찾지 못하면 false입니다.
SophonzSpanEvent({required String name, required int timestampMs, required Map<String, String> attributes})는 recordCompletedSpan에 넘길 이벤트입니다.
final start = DateTime.now().millisecondsSinceEpoch;
final image = await decodeImage(bytes);
await Sophonz.instance.recordCompletedSpan(
'decode-image',
start,
DateTime.now().millisecondsSinceEpoch,
attributes: {'image.bytes': '${bytes.length}'},
);CAUTION — recordSpan은 실패를 삼키고 속성을 무시합니다
SDK가 실행 중이 아니면 recordSpan은 code를 아예 실행하지 않습니다. code가 예외를 던지면 다시 던지지 않고, 스팬은 열린 채로 남으며, 기본값(null, false, 0, 0.0, 빈 문자열)을 돌려줍니다. attributes와 events는 받기만 하고 적용하지 않습니다. 실패할 수 있는 작업에는 위 예시처럼 startSpan과 stop(errorCode:)을 쓰세요.
NOTE — 스팬은 네이티브 스팬 한도를 함께 씁니다
Dart에서 만든 스팬은 네이티브 SDK 입장에서 커스텀 스팬입니다. 화면 로드 스팬, iOS에서 Dart가 기록한 네트워크 스팬을 포함해 다른 모든 커스텀 스팬과 세션당 한도를 나눠 씁니다. 스팬 이름의 종류는 적게 유지하고 ID는 속성에 넣으세요.
시작만 하고 멈추지 않은 스팬은 프로세스가 끝날 때까지 네이티브 플러그인의 메모리에 남습니다.
OpenTelemetry API
SDK는 dartastic_opentelemetry_api(1.0.0-rc.3)의 트레이서·로거 프로바이더로 등록되므로, OpenTelemetry Dart API로 작성된 코드도 Sophonz에 기록됩니다. import하려면 이 버전으로 pubspec.yaml에 추가하세요.
import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart' hide Severity;
final tracer = OTelAPI.tracerProvider().getTracer('checkout');
final span = tracer.startSpan('validate-cart');
span.setIntAttribute('cart.size', cart.items.length);
try {
await tracer.withSpanAsync(span, validateCart);
} catch (error, stack) {
span.recordException(error, stackTrace: stack);
rethrow;
} finally {
span.end();
}트레이서 이름과 관계없이 같은 트레이서가 반환됩니다. 0.1.0에서 브리지가 지원하는 범위는 다음과 같습니다.
| OpenTelemetry 호출 | 결과 |
|---|---|
startSpan, createSpan, end | 네이티브 스팬을 시작하고 끝냅니다. createSpan(startTime:)과 end(endTime:)이 반영됩니다 |
setStringAttribute, setIntAttribute 등 setter, addAttributes | 문자열 속성으로 전송. 리스트는 쉼표로 이어 붙입니다 |
addEvent, addEventNow, addEvents | 스팬 이벤트 |
recordException | exception.type, exception.message, exception.stacktrace를 담은 exception 이벤트 |
withSpan, withSpanAsync | 스팬을 현재 스팬으로 만듭니다. SophonzHttpClient, SophonzInterceptor가 traceparent에 사용합니다 |
setStatus, end(spanStatus:), updateName, addLink | 네이티브 플러그인에 구현되어 있지 않습니다. 호출이 비동기로 실패하며 처리되지 않은 Dart 오류 로그로 나타날 수 있습니다 |
부모 스팬(parentSpan: 또는 현재 스팬) | 유지되지 않습니다. 네이티브에서는 각 스팬이 새 트레이스이며, Dart에서 보이는 ID는 네이티브 ID가 아닙니다 |
OTelAPI.loggerProvider().getLogger(...).emit | severity number에 따라 info, warning, error 로그. attributes, eventName, 타임스탬프는 버려집니다 |
스팬 계층이 중요하면 Sophonz.instance.startSpan(parent:)을 쓰세요.
네트워크 요청
SophonzHttpClient와 SophonzInterceptor는 요청을 자동으로 기록합니다. 둘 다 네트워크에서 설명합니다.
| 시그니처 | 설명 |
|---|---|
SophonzHttpClient({http.Client? internalClient}) | 모든 요청을 기록하고 traceparent를 붙이는 http.BaseClient |
void recordNetworkRequest(SophonzNetworkRequest request) | 다른 방식으로 보낸 요청 기록 |
SophonzNetworkRequest.fromCompletedRequest({required String url, required HttpMethod httpMethod, required int startTime, required int endTime, required int bytesSent, required int bytesReceived, required int statusCode, String? traceId, String? w3cTraceparent}) | 응답을 받은 요청 |
SophonzNetworkRequest.fromIncompleteRequest({required String url, required HttpMethod httpMethod, required int startTime, required int endTime, required String errorDetails, String? traceId, String? w3cTraceparent}) | 응답을 받지 못한 요청 |
Future<String?> generateW3cTraceparent(String? traceId, String? spanId) | traceparent 값. Android는 두 인자를 무시하고 새로 만듭니다. iOS는 넘긴 ID로 만들고, 하나라도 null이면 무작위 ID를 씁니다 |
HttpMethod는 get, post, put, delete, patch, other입니다. Android에서 other로 기록한 요청은 버려집니다.
W3cTraceContext
| 시그니처 | 설명 |
|---|---|
static String? fromSpanContext(SpanContext spanContext) | OpenTelemetry 스팬 컨텍스트로 traceparent를 만듭니다. 유효하지 않으면 null |
static SpanContext? extract(String? header) | traceparent를 원격 스팬 컨텍스트로 파싱합니다. 형식이 틀리면 null |
static Future<void> injectCurrent(Map<String, String> headers) | 현재 OpenTelemetry 스팬의 traceparent를 추가합니다. 네이티브 SDK에 먼저 요청합니다 |
static void injectCurrentSync(Map<String, dynamic> headers) | 현재 OpenTelemetry 스팬의 traceparent를 Dart에서 만들어 추가합니다 |
현재 스팬이 없으면 inject 메서드는 아무것도 하지 않습니다.
뷰
내비게이션 옵저버가 대신 호출합니다. 탭처럼 라우트가 아닌 화면에는 직접 호출하세요. 내비게이션을 참고하세요.
| 시그니처 | 설명 |
|---|---|
void startView(String name) | 뷰 시작 |
void endView(String name) | 같은 이름으로 시작한 뷰 종료 |
| 시그니처 | 설명 |
|---|---|
SophonzNavigationObserver({SophonzRouteSettingsExtractor? routeSettingsExtractor, SophonzScreenLoadConfig screenLoadConfig}) | Navigator와 MaterialApp용 NavigatorObserver |
SophonzGoRouterObserver({GoRouter? router, SophonzRouteSettingsExtractor? routeSettingsExtractor, SophonzScreenLoadConfig screenLoadConfig}) | go_router 옵저버. router를 넘겼다면 다 쓴 뒤 dispose() 호출 |
const SophonzScreenLoadConfig({Duration recencyThreshold = const Duration(seconds: 1)}) | 화면 로드 스팬 시간 설정 |
typedef SophonzRouteSettingsExtractor = RouteSettings? Function(Route<dynamic> route) | 기록할 설정을 돌려주거나, null로 건너뜁니다 |
사용자
Sophonz.instance.setUserIdentifier('agent-10482');
Sophonz.instance.addUserPersona('beta-tester');| 시그니처 | Android | iOS |
|---|---|---|
void setUserIdentifier(String id), void clearUserIdentifier() | 지원 | 지원 |
void addUserPersona(String persona), void clearUserPersona(String persona), void clearAllUserPersonas() | 지원 | 지원(프로세스 수명) |
void setUserAsPayer(), void clearUserAsPayer() | 지원 | 지원 |
void setUserName(String name), void clearUserName() | 지원 | 무시 |
void setUserEmail(String email), void clearUserEmail() | 지원 | 무시 |
setUserAsPayer는 payer 페르소나를 추가합니다. iOS에서는 프로세스가 끝나면 페르소나가 사라지므로, 로그인 상태를 복원한 직후처럼 실행할 때마다 다시 설정하세요.
이름이나 이메일보다 불투명한 식별자를 쓰세요. iOS는 이름과 이메일을 아예 기록하지 않습니다.
세션
| 시그니처 | 설명 |
|---|---|
Future<String?> getCurrentSessionId() | 현재 세션 ID. 소문자 16진수 32자이며, 세션이 아직 없으면 null |
void addSessionProperty(String key, String value, {bool permanent = false}) | 현재 세션의 속성. permanent가 true면 이후 모든 세션에 붙습니다 |
void removeSessionProperty(String key) | 수명과 관계없이 속성을 제거 |
void endSession({bool clearUserInfo = true}) | 세션을 끝내고 새 세션을 시작합니다. clearUserInfo면 먼저 식별자와 페르소나를, Android에서는 이름과 이메일도 지웁니다 |
NOTE — iOS에서 세션 종료는 횟수가 제한됩니다
Apple SDK는 5초에 한 번만 endSession을 받아들이고 나머지는 무시합니다. 키오스크처럼 오래 포그라운드에 머무는 앱에 쓰고, 화면마다 호출하지 마세요.
세션 ID를 백엔드 헤더 등 다른 곳으로 넘길 때는 반환된 값을 그대로 쓰세요. 백엔드는 session.id가 소문자 16진수 32자가 아닌 배치를 통째로 거부합니다.
익스포터
| 시그니처 | 설명 |
|---|---|
void addSpanExporter({required String endpoint, List<Map<String, String>>? headers, int? timeoutSeconds}) | OTLP/HTTP 스팬 익스포터 추가. Android 전용이며 start 전에 호출 |
void addLogRecordExporter({required String endpoint, List<Map<String, String>>? headers, int? timeoutSeconds}) | OTLP/HTTP 로그 익스포터 추가. Android 전용이며 start 전에 호출 |
SophonzTracerProvider get tracerProvider | 등록된 트레이서 프로바이더. start나 익스포터 호출 전에는 StateError |
SophonzLoggerProvider get loggerProvider | 등록된 로거 프로바이더. 조건은 같습니다 |
Sophonz로 보내는 것에 더해 추가로 내보냅니다. 호출 시점 제약은 설정을 참고하세요.
테스트
위젯·단위 테스트에서는 @visibleForTesting으로 표시된 debugSophonzOverride로 Sophonz.instance를 바꿉니다.
import 'package:mocktail/mocktail.dart';
import 'package:sophonz/sophonz.dart';
class MockSophonz extends Mock implements Sophonz {}
void main() {
late MockSophonz sophonz;
setUp(() {
sophonz = MockSophonz();
debugSophonzOverride = sophonz;
});
tearDown(() => debugSophonzOverride = null);
testWidgets('logs a breadcrumb on pay', (tester) async {
await tester.pumpWidget(const PayButton());
await tester.tap(find.text('Pay'));
verify(() => sophonz.addBreadcrumb('Tapped pay')).called(1);
});
}오버라이드하지 않으면 연결된 네이티브 SDK가 없으므로 테스트 중의 호출은 버려집니다.
package:sophonz/sophonz_samples.dart의 SophonzSamples는 빌드를 시험하기 위해 오류, 네이티브 크래시, ANR(Android), 시그널(iOS)을 일으킵니다. 계측을 참고하세요.