Sentry
Sentry ↗ error tracking and performance monitoring for Skip apps on both iOS and Android.
On iOS this wraps the Sentry Cocoa SDK ↗ (v9.8+). On Android, the Swift code is transpiled to Kotlin via Skip Lite and wraps the Sentry Android SDK ↗ (v8.37).
Add the dependency to your Package.swift file:
let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-sentry.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipSentry", package: "skip-sentry") ]) ])Sentry Project Setup
Section titled “Sentry Project Setup”Create a Sentry ↗ project and obtain your DSN from Settings > Projects > [Your Project] > Client Keys (DSN).
Android: disable auto-init
Section titled “Android: disable auto-init”SkipSentry initializes manually via SkipSentry.start { … }. The Sentry Android SDK, however, ships a SentryInitProvider that auto-initializes at process start — before your manual start runs. Because the DSN is provided in code (not as manifest metadata), this auto-init aborts the app on launch with java.lang.IllegalArgumentException: DSN is required.
Disable it by adding this to your app’s AndroidManifest.xml, inside <application>:
<meta-data android:name="io.sentry.auto-init" android:value="false" />This must live in the consuming app’s manifest — a meta-data entry in this module’s manifest does not propagate into the merged app manifest. See Sentry’s manual initialization ↗ docs.
Initialization
Section titled “Initialization”Initialize Sentry early in your app’s lifecycle:
import SkipSentry
@main struct MyApp: App { init() { SkipSentry.start( dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", debug: false ) }
var body: some Scene { WindowGroup { ContentView() } }}Advanced Configuration
Section titled “Advanced Configuration”Use the options-based initializer for full control:
SkipSentry.start { options in options.dsn = "https://examplePublicKey@o0.ingest.sentry.io/0" options.debug = false options.environment = "production" options.release = "1.0.0" options.dist = "1" options.sampleRate = 1.0 // 0.0 to 1.0 options.enableAutoSessionTracking = true options.sessionTrackingIntervalMillis = 30000 options.attachStacktrace = true options.enableAppHangTracking = true // iOS only}Filtering Events Before They’re Sent
Section titled “Filtering Events Before They’re Sent”Use beforeSend to inspect each event on-device and decide whether to drop it —
for example, to filter out expected, non-actionable client-side noise (cancelled
in-flight requests, transient offline blips, user-cancelled auth sheets) before it
becomes a Sentry issue. Return the event to send it, or nil to drop it.
let noise = ["network request failed", "request was cancelled"]
SkipSentry.start { options in options.dsn = "https://examplePublicKey@o0.ingest.sentry.io/0" options.beforeSend = { event in var text = (event.message ?? "").lowercased() for value in event.exceptionValues { text += " " + value.lowercased() } return noise.contains(where: { text.contains($0) }) ? nil : event }}The closure receives a lightweight, read-only SkipSentryEvent — a cross-platform
projection of the native event (its message, level, and exceptions, plus the
exceptionValues convenience). The native event types (Sentry.Event on iOS,
io.sentry.SentryEvent on Android) are platform-specific and can’t cross the Skip
bridge, so the hook decides keep-or-drop rather than mutating the event. This mirrors
iOS Options.beforeSend and Android SentryOptions.setBeforeSend.
Capturing Errors
Section titled “Capturing Errors”do { try riskyOperation()} catch { SkipSentry.capture(error: error)}Capturing Messages
Section titled “Capturing Messages”SkipSentry.capture(message: "User completed onboarding")SkipSentry.capture(message: "Payment processing slow", level: .warning)SkipSentry.capture(message: "Database connection lost", level: .error)Breadcrumbs
Section titled “Breadcrumbs”Breadcrumbs provide a trail of events leading up to an error:
SkipSentry.addBreadcrumb(message: "User tapped checkout button")
SkipSentry.addBreadcrumb( message: "Navigated to payment screen", category: "navigation")
SkipSentry.addBreadcrumb( message: "API request failed with 500", category: "http", level: .error)
// Clear all breadcrumbsSkipSentry.clearBreadcrumbs()User Context
Section titled “User Context”Associate events with the current user:
SkipSentry.setUser( id: "user-12345", email: "user@example.com", username: "johndoe")
// Clear user on logoutSkipSentry.clearUser()Tags and Extras
Section titled “Tags and Extras”Tags are indexed and searchable. Extras provide additional context but are not indexed.
// Tags (searchable in Sentry UI)SkipSentry.setTag(key: "page", value: "checkout")SkipSentry.setTag(key: "feature_flag", value: "new_flow")SkipSentry.removeTag(key: "page")
// Extras (additional context)SkipSentry.setExtra(key: "response_time", value: "1200ms")SkipSentry.setExtra(key: "cart_items", value: "3")
// Set the scope levelSkipSentry.setLevel(.warning)Flushing Events
Section titled “Flushing Events”Force-send queued events:
SkipSentry.flush() // Default 5-second timeoutSkipSentry.flush(timeout: 10.0) // Custom timeoutSDK Status
Section titled “SDK Status”if SkipSentry.isEnabled { print("Sentry is active")}
if SkipSentry.crashedLastRun { print("App crashed during the last run")}Closing the SDK
Section titled “Closing the SDK”SkipSentry.close()API Reference
Section titled “API Reference”SkipSentry
Section titled “SkipSentry”All methods are static on the SkipSentry class.
| Method | Description |
|---|---|
start(dsn:debug:) | Initialize with a DSN |
start(configure:) | Initialize with full options |
isEnabled: Bool | Whether the SDK is initialized |
close() | Release SDK resources |
capture(error:) | Capture an error |
capture(message:level:) | Capture a message |
addBreadcrumb(message:) | Add a simple breadcrumb |
addBreadcrumb(message:category:level:) | Add a categorized breadcrumb |
clearBreadcrumbs() | Remove all breadcrumbs |
setUser(id:email:username:) | Set the current user |
clearUser() | Clear the current user |
setTag(key:value:) | Set a searchable tag |
removeTag(key:) | Remove a tag |
setExtra(key:value:) | Set extra context |
setLevel(_:) | Set the scope severity level |
flush(timeout:) | Send queued events |
crashedLastRun: Bool | Whether the app crashed last run |
SkipSentryOptions
Section titled “SkipSentryOptions”| Property | Type | Default | Description |
|---|---|---|---|
dsn | String? | nil | Sentry Data Source Name |
debug | Bool | false | Enable debug logging |
environment | String? | nil | Environment name (e.g. “production”) |
release | String? | nil | Release version string |
dist | String? | nil | Distribution identifier |
sampleRate | Double? | nil | Error sample rate (0.0–1.0) |
enableAutoSessionTracking | Bool | true | Auto-track sessions |
sessionTrackingIntervalMillis | Int? | nil | Session tracking interval (ms) |
attachStacktrace | Bool | true | Attach stack traces to events |
enableAppHangTracking | Bool | true | Detect app hangs (iOS only) |
beforeSend | ((SkipSentryEvent) -> SkipSentryEvent?)? | nil | Inspect each event and drop it (return nil) or send it (return the event) |
SkipSentryLevel
Section titled “SkipSentryLevel”| Case | Description |
|---|---|
.debug | Debug-level message |
.info | Informational message |
.warning | Warning condition |
.error | Error condition |
.fatal | Fatal/critical error |
Limitations
Section titled “Limitations”The following features are not yet available in the Skip cross-platform wrapper:
- Performance monitoring (transactions, spans) — Use the native Sentry SDKs directly via
#if SKIP/#if !SKIPblocks for transaction tracing.- Attachments — File attachments on events are not wrapped.
- User feedback dialog — The Sentry feedback widget is not wrapped.
- Session replay — Available on each platform’s native SDK but not wrapped.
- Profiling — Not wrapped.
- Metrics — The custom metrics API is not wrapped.
- Feature flags — The
addFeatureFlagAPI is not wrapped.
enableAppHangTrackingis only available on iOS. It has no effect on Android.crashedLastRunis deprecated on iOS (uselastRunStatusvia the native SDK for more detail). On Android, returnsnullif unknown, which is treated asfalse.capture(error:)on Android converts the error to a JavaThrowableusing Skip’s standard error bridging. If the error already originates from a Java/Kotlin exception, it is passed directly. Otherwise, it is wrapped in anExceptionwith the error’slocalizedDescription.flush(timeout:)takes seconds on iOS but milliseconds on Android. The wrapper converts automatically.- The
start()methods on Android require an Android context (obtained fromProcessInfo.processInfo.androidContext), so they cannot be called in Robolectric unit tests.
Building
Section titled “Building”This project is a Swift Package Manager module that uses the Skip plugin to build the package for both iOS and Android.
Testing
Section titled “Testing”The module can be tested using the standard swift test command
or by running the test target for the macOS destination in Xcode,
which will run the Swift tests as well as the transpiled
Kotlin JUnit tests in the Robolectric Android simulation environment.
Parity testing can be performed with skip test,
which will output a table of the test results for both platforms.
Releases
Section titled “Releases”The 4 most recent releases of skiptools/skip-sentry ↗:
- 0.1.2 ↗ — 2026-09-20
- Release 0.1.1 ↗ — 2026-05-24
- Release 0.1.0 ↗ — 2026-03-30
- Release 0.0.1 ↗ — 2025-12-17
Full history: github.com/skiptools/skip-sentry/releases ↗ — .atom feed ↗