iOS SDK
Native Swift SDK for iOS, macOS, and tvOS with crash detection, ANR detection, URLSession tracking, and performance monitoring.
Built with Swift, supports iOS 12+, macOS 10.14+, tvOS 12+ (tvOS via Swift Package Manager only)
Catches uncaught exceptions, plus native signal crashes (SIGSEGV, SIGABRT, etc.) and Mach exceptions via PLCrashReporter
Detect Application Not Responding events (main thread blocked)
Track user journey before errors
Automatic HTTP performance spans and breadcrumbs
Send debug, info, warning, and error messages
Track API latency with URLSession and Alamofire
Automatic device, OS, and app info collection
Installation#
Swift Package Manager (Recommended)
In Xcode: File → Add Packages → enter the Abrova Center address:
https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git
Select version 0.2.4 or later.
Or add to your Package.swift:
dependencies: [
.package(url: "https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git", from: "0.2.4")
]CocoaPods
Add to your Podfile:
pod 'AbrovaTrace', :git => 'https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git', :tag => '0.2.4'
Then run:
pod install
Releases are installed from Abrova Center, which works inside Iran. The source code is on GitHub (Abrova-co/abrova-trace-ios-sdk) with the same version tags, so https://github.com/Abrova-co/abrova-trace-ios-sdk.git also works where GitHub is reachable.
Quick Start#
UIKit Initialization
Initialize AbrovaTrace in your AppDelegate:
import UIKit
import AbrovaTrace
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY")
.environment("production")
.release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
.debug(false)
.captureUncaughtExceptions(true)
.captureSignalCrashes(true)
.captureAnr(true)
.anrTimeoutMs(5000)
.build()
AbrovaTrace.shared.initialize(config: config)
return true
}
func applicationWillTerminate(_ application: UIApplication) {
AbrovaTrace.shared.close() // Optional: the SDK also does this on terminate
}
}SwiftUI Initialization
import SwiftUI
import AbrovaTrace
@main
struct MyApp: App {
init() {
let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY")
.environment("production")
.release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
.debug(false)
.captureUncaughtExceptions(true)
.captureSignalCrashes(true)
.build()
AbrovaTrace.shared.initialize(config: config)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}Capture Errors#
Capture Swift Errors
do {
try riskyOperation()
} catch {
AbrovaTrace.shared.captureError(error)
}Use captureError(_:) for Swift Error values and captureException(_:) for an NSException.
Capture with Context
AbrovaTrace.shared.captureError(
error,
message: "Failed to load user profile",
extra: ["user_id": userId, "screen": "ProfileView"],
tags: ["feature": "user_profile"]
)Capture Messages
// Info message
AbrovaTrace.shared.captureMessage("User completed onboarding", level: .info)
// Warning message
AbrovaTrace.shared.captureMessage("API rate limit approaching", level: .warning)
// Error message with extra data
AbrovaTrace.shared.captureMessage(
"Payment failed",
level: .error,
extra: ["amount": 99.99]
)Messages appear in the Console under Abrova Trace → Messages, with their level, platform, extra data and breadcrumbs. They never create an issue. Levels: .debug, .info (default), .warning, .error. .fatal is accepted and stored as error. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs.
Breadcrumbs#
// User action
AbrovaTrace.shared.addUserBreadcrumb("Added item to cart", data: ["product_id": "abc"])
// Navigation
AbrovaTrace.shared.addNavigationBreadcrumb(from: "HomeView", to: "ProductDetailView")
// HTTP request
AbrovaTrace.shared.addHttpBreadcrumb(
method: "POST",
url: "https://api.example.com/orders",
statusCode: 201,
duration: 0.342 // seconds (TimeInterval)
)
// Custom breadcrumb
AbrovaTrace.shared.addBreadcrumb("Order placed", type: .info, data: [
"order_id": "12345",
"total": 99.99
])Automatic HTTP Tracking#
Global HTTP Tracking
Enable automatic tracking for URLSession requests. Each request is reported as a performance span and added as an HTTP breadcrumb:
// Registers AbrovaTraceURLProtocol globally (URLSession.shared and default-configured sessions) AbrovaTrace.shared.enablePerformanceTracking() // Requests are now tracked automatically let (data, response) = try await URLSession.shared.data(from: url)
Session-Based Tracking
// Create a tracked URLSession
let config = AbrovaTrace.shared.performanceSessionConfiguration()
// Equivalent to:
// config.protocolClasses = [AbrovaTraceURLProtocol.self] + (config.protocolClasses ?? [])
let session = URLSession(configuration: config)
// Requests through this session are tracked
let task = session.dataTask(with: url) { data, response, error in
// Handle response
}
task.resume()Performance spans from enablePerformanceTracking() record the full request URL, including query strings, so avoid putting secrets in URLs or exclude those hosts with setPerformanceExcludedHosts(_:). The separate AbrovaTraceHttpBreadcrumbProtocol.enable() adds HTTP breadcrumbs with sensitive query parameters (token, api_key, apikey, key, secret, password, pwd, auth, authorization, access_token, refresh_token, session) redacted, and reports 5xx responses and failed requests as messages.
Crash Detection#
AbrovaTrace captures crashes with a vendored copy of PLCrashReporter:
- Uncaught NSExceptions — Sent immediately, before the app exits (
captureUncaughtExceptions) - ANR Events — Main thread blocked longer than
anrTimeoutMs(captureAnr) - Native Crashes — POSIX signals (SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE, SIGTRAP) and Mach exceptions (
captureSignalCrashes)
- On initialization, AbrovaTrace installs PLCrashReporter's async-signal-safe handlers
- When the app crashes, a crash report is written to disk inside the app sandbox
- On the next launch,
initializereads the pending report and sends it to Abrova Trace in the background, so app launch never waits for the network. The report is removed only after it was delivered; if the server or the network fails it is kept and sent on a later launch. Requires version 0.2.3 or newer.
Native crashes are reported on the next launch, so they only arrive once the user opens the app again. The SDK calls AbrovaTrace.shared.close() itself when it receives the app's will-terminate notification; calling it yourself is optional and flushes buffered logs.
Performance Monitoring (APM)#
Track API latency and HTTP request performance.
Automatic HTTP Tracking with URLSession
// Enable performance tracking globally AbrovaTrace.shared.enablePerformanceTracking() // All URLSession requests are now tracked with timing data let (data, response) = try await URLSession.shared.data(from: url) // Method, URL, status code, and duration are automatically reported
Using with Alamofire
import Alamofire
// Create a session with AbrovaTrace tracking
let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [AbrovaTraceURLProtocol.self] + (configuration.protocolClasses ?? [])
let session = Session(configuration: configuration)
// All requests through this session are tracked
session.request("https://api.example.com/users").response { response in
// Handle response
}Track Custom Operations
// Async (iOS 13+); use PerformanceTracker.track(_:) for synchronous work
let result = try await PerformanceTracker.trackAsync("load_user_profile") {
let user = try await fetchUser()
try await cacheUser(user)
return user
}
// Or time it manually
let tracker = PerformanceTracker(operation: "image_processing")
// ... do work
tracker.finish(status: .ok)Manual Span Reporting
// HTTP request span
AbrovaTrace.shared.reportPerformanceSpan(
PerformanceSpan.fromHttpRequest(
method: "GET",
url: "https://api.example.com/products",
statusCode: 200,
durationMs: 245,
startTime: Date().addingTimeInterval(-0.245)
)
)
// Database query span
AbrovaTrace.shared.reportPerformanceSpan(
PerformanceSpan.forDbQuery(
queryType: "SELECT",
tableName: "products",
durationMs: 15,
startTime: Date().addingTimeInterval(-0.015),
rowsAffected: 50
)
)
// Custom operation span
AbrovaTrace.shared.reportPerformanceSpan(
PerformanceSpan.custom(
operation: "image_processing",
durationMs: 1250,
startTime: Date().addingTimeInterval(-1.25),
tags: ["format": "png"]
)
)Logging#
Structured log ingestion with automatic batching and retry. Enable it once, then use the level helpers from anywhere in your app:
// Enable logging after initialization
AbrovaTrace.shared.enableLogging(sourceId: "my-ios-app")
AbrovaTrace.shared.info("User login successful", metadata: ["user_id": "user_123"])See iOS SDK Logging for the full pipeline — batching, log levels, lifecycle handling, manual flush, and configuration.
Configuration Options#
| Option | Type | Required | Description |
|---|---|---|---|
| apiKey | String | Yes | Your project API key (must start with ab_live_) |
| environment | String | No | Environment name (default: "production") |
| release | String? | No | App version identifier (default: CFBundleShortVersionString) |
| debug | Bool | No | Enable debug logging (default: false) |
| enabled | Bool | No | Enable/disable SDK (default: true) |
| sampleRate | Double | No | Error capture sample rate 0.0-1.0 (default: 1.0) |
| maxBreadcrumbs | Int | No | Max breadcrumbs to keep (default: 20) |
| httpTimeout | TimeInterval | No | HTTP request timeout in seconds (default: 30) |
| enableOfflineStorage | Bool | No | Keep errors that could not be sent because the device is offline on disk (up to 100, oldest dropped first) and send them on the next launch or as soon as another error is sent successfully. Errors only: messages, logs and performance data are not stored. Set to false to keep nothing on disk (default: true) |
| captureUncaughtExceptions | Bool | No | Catch uncaught exceptions (default: true) |
| captureSignalCrashes | Bool | No | Detect signal crashes (default: true) |
| captureAnr | Bool | No | Detect main thread hangs (default: true) |
| anrTimeoutMs | Int64 | No | ANR detection timeout in ms (default: 5000) |
| apiUrl | String | No | API base URL, for self-hosted deployments (default: "https://trace.abrova.ir") |
Upload Debug Symbols (dSYM)#
To see file names and line numbers in your iOS stack traces, upload your app's dSYM files. Each build produces unique dSYMs — upload a new one for every release version.
1. Find your dSYM files
After building your app in Xcode, dSYMs are located in your build products:
# For debug builds (simulator) ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/YourApp.app.dSYM # For release/archive builds ~/Library/Developer/Xcode/Archives/*.xcarchive/dSYMs/
2. Zip the dSYM bundle
# Zip the .dSYM bundle cd ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/ zip -r YourApp.dSYM.zip YourApp.app.dSYM
3. Upload via Dashboard
In the Abrova Console, go to Abrova Trace → Source maps and open the iOS dSYMs tab. Drop the .zip file, optionally enter the release version, and click Upload. Uploading needs the member role or higher; deleting uploaded dSYMs needs admin or owner — see Roles & Permissions.
The dSYM upload endpoint accepts only a Console login session (no API key or personal access token), and @abrova-trace/cli uploads source maps only, so upload dSYMs from the Console.
Each time you rebuild your app, the dSYM UUIDs change. If stack traces show function names but no file/line info, you need to upload the dSYM matching that build.
Platform Support#
- iOS — 12.0+
- macOS — 10.14+
- tvOS — 12.0+ (Swift Package Manager only; the CocoaPods spec covers iOS and macOS)
watchOS is not supported. Building the SDK requires Swift 5.10 or later (Xcode 15.3+).
Context & Tags#
Attach persistent metadata to every error and message captured by the SDK (native crash and ANR reports, logs and performance spans do not carry them). Useful for filtering by team, region, feature flag, or experiment cohort in the Abrova Console.
Global extras
Extras are arbitrary key/value pairs (string keys, any serializable value) automatically merged into every event:
// Set individual extras
AbrovaTrace.shared.setExtra("organizationId", value: "org-123")
AbrovaTrace.shared.setExtra("feature", value: "checkout-v2")
// Set multiple at once
AbrovaTrace.shared.setExtras([
"organizationId": "org-123",
"feature": "checkout-v2",
"experiment": "new-flow"
])
// Clear when no longer needed (e.g. on logout)
AbrovaTrace.shared.clearExtras()Tags
Tags are string-only key/value pairs for categorizing events:
AbrovaTrace.shared.setTag("team", value: "payments")
AbrovaTrace.shared.setTag("region", value: "us-east")
AbrovaTrace.shared.setTags(["team": "payments", "version": "2.1"])
AbrovaTrace.shared.clearTags()Use tags for low-cardinality categorical filters (team, region, build flavor). Use extras for richer per-event context (user IDs, request bodies, computed values).
SwiftUI Integration#
The SDK works seamlessly with the SwiftUI @main app entry point. Initialize from the App struct's init() so errors are captured from the moment your view hierarchy is constructed:
import SwiftUI
import AbrovaTrace
@main
struct MyApp: App {
init() {
let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_your_api_key")
.environment("production")
.build()
AbrovaTrace.shared.initialize(config: config)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}For UIKit apps, initialize from application(_:didFinishLaunchingWithOptions:) in your AppDelegate — see the Complete Example below.
API Reference#
Full method surface of AbrovaTrace.shared and PerformanceSpan. Selectors use the Swift convention; optional parameters end with ? in the description.
AbrovaTrace.shared — Initialization & lifecycle
| Option | Type | Required | Description |
|---|---|---|---|
| initialize(config:) | - | - | Initialize SDK with a full AbrovaTraceConfig |
| initialize(apiKey:) | - | - | Initialize SDK with just an API key (uses defaults) |
| isReady | - | - | True once the SDK is initialized |
| close() | - | - | Cleanup SDK, flush pending data |
Error capture
| Option | Type | Required | Description |
|---|---|---|---|
| captureError(_:message:extra:tags:completion:) | - | - | Capture a Swift Error |
| captureException(_:message:extra:tags:completion:) | - | - | Capture an NSException |
| captureMessage(_:level:extra:tags:completion:) | - | - | Send a message to Abrova Trace → Messages (not an issue) |
User & session
| Option | Type | Required | Description |
|---|---|---|---|
| setUserId(_:) | - | - | Set the current user identifier |
| getUserId() | - | - | Get the current user identifier |
Global context (extras)
| Option | Type | Required | Description |
|---|---|---|---|
| setExtra(_:value:) | - | - | Set a single extra |
| setExtras(_:) | - | - | Set multiple extras at once |
| clearExtras() | - | - | Clear all extras |
Tags
| Option | Type | Required | Description |
|---|---|---|---|
| setTag(_:value:) | - | - | Set a single tag |
| setTags(_:) | - | - | Set multiple tags at once |
| clearTags() | - | - | Clear all tags |
Breadcrumbs
| Option | Type | Required | Description |
|---|---|---|---|
| addBreadcrumb(_:type:data:) | - | - | Add a generic breadcrumb |
| addNavigationBreadcrumb(from:to:) | - | - | Add a navigation breadcrumb |
| addUserBreadcrumb(_:data:) | - | - | Add a user-action breadcrumb |
| addHttpBreadcrumb(method:url:statusCode:duration:) | - | - | Add an HTTP breadcrumb |
| clearBreadcrumbs() | - | - | Clear all breadcrumbs |
Performance monitoring
| Option | Type | Required | Description |
|---|---|---|---|
| reportPerformanceSpan(_:completion:) | - | - | Report a constructed PerformanceSpan |
| reportPerformanceSpanBatch(_:completion:) | - | - | Report multiple spans at once |
| trackHttpRequest(request:response:startTime:error:) | - | - | Track a one-off HTTP request |
| enablePerformanceTracking() | - | - | Turn on automatic HTTP tracking |
| disablePerformanceTracking() | - | - | Turn off automatic HTTP tracking |
| performanceSessionConfiguration(baseConfiguration:) | - | - | Return a URLSessionConfiguration with tracking attached |
| setPerformanceExcludedHosts(_:) | - | - | Exclude hosts from automatic tracking |
| setPerformanceMinDuration(_:) | - | - | Set minimum span duration threshold |
Logging
| Option | Type | Required | Description |
|---|---|---|---|
| enableLogging(sourceId:sourceName:batchSize:flushInterval:) | - | - | Turn on the log pipeline |
| log(_:level:metadata:) | - | - | Log at an explicit level |
| trace(_:metadata:) | - | - | Log at trace level |
| logDebugMessage(_:metadata:) | - | - | Log at debug level |
| info(_:metadata:) | - | - | Log at info level |
| warn(_:metadata:) | - | - | Log at warn level |
| logErrorMessage(_:metadata:) | - | - | Log at error level |
| fatal(_:metadata:) | - | - | Log at fatal level |
| flushLogs(completion:) | - | - | Flush buffered logs immediately |
| pendingLogCount | - | - | Number of logs still in the buffer |
PerformanceSpan factories
| Option | Type | Required | Description |
|---|---|---|---|
| PerformanceSpan.fromHttpRequest(...) | - | - | Construct a span from an HTTP request/response |
| PerformanceSpan.forDbQuery(...) | - | - | Construct a span for a database query |
| PerformanceSpan.custom(...) | - | - | Construct a fully custom span |
| PerformanceSpan.generateTraceId() | - | - | Generate a random trace ID |
| PerformanceSpan.generateSpanId() | - | - | Generate a random span ID |
Complete Example#
import UIKit
import AbrovaTrace
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY")
.environment("production")
.release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
.debug(false)
.captureUncaughtExceptions(true)
.captureSignalCrashes(true)
.captureAnr(true)
.build()
AbrovaTrace.shared.initialize(config: config)
AbrovaTrace.shared.enablePerformanceTracking()
return true
}
func applicationWillTerminate(_ application: UIApplication) {
AbrovaTrace.shared.close()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
AbrovaTrace.shared.setUserId("user_123")
AbrovaTrace.shared.addNavigationBreadcrumb(from: "Launch", to: "HomeView")
}
func handlePurchase() {
AbrovaTrace.shared.addUserBreadcrumb("Initiated purchase", data: ["product": "Premium"])
do {
try processPurchase()
AbrovaTrace.shared.captureMessage("Purchase successful", level: .info, extra: [
"amount": 99.99
])
} catch {
AbrovaTrace.shared.captureError(
error,
message: "Purchase failed",
extra: ["user_id": "user_123", "screen": "HomeView"]
)
}
}
func processPurchase() throws { /* ... */ }
}Best Practices#
Initialize AbrovaTrace as early as possible to catch all errors from app start
The SDK calls close() on the will-terminate notification to flush logs; call it yourself only for custom shutdown paths
Call enablePerformanceTracking() for automatic HTTP breadcrumbs and performance monitoring
Include user IDs, screen names, and relevant data in the extra parameter
Separate production, staging, and development errors using the environment config
Set debug: true during development to see SDK logs and verify integration
Call enableLogging() right after initialization to capture all application events