
Sentry Cocoa Sdk
- 2.2k installs
- 243 repo stars
- Updated July 27, 2026
- getsentry/sentry-for-ai
sentry-cocoa-sdk is an agent skill that Full Sentry SDK setup for Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). Use when asked to "add Sentry to iOS", .
About
All Skills SKILL_TREE md SDK Setup sentry sdk setup SKILL md Cocoa SDK Opinionated wizard that scans your Apple project and guides you through complete Sentry setup User asks to add Sentry to iOS macOS tvOS or set up Sentry in an Apple app User wants error monitoring tracing profiling session replay or logging in Swift ObjC or metrics in Swift User mentions sentry cocoa SentrySDK or the Apple iOS Sentry SDK User wants to monitor crashes app hangs watchdog terminations or performance Note SDK versions and APIs below reflect Sentry docs at time of writing sentry cocoa 9 15 0 Always verify against docs sentry io platforms apple https docs sentry io platforms apple before implementing Run these commands to understand the project before making any recommendations The sentry cocoa sdk agent skill provides documented workflows prerequisites triggers and safety guidance from its SKILL md source Agents load it when user requests match the description and follow step by step instructions without inventing capabilities It integrates with standard agent
- description: Full Sentry SDK setup for Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). Use when asked to "add Sen
- > [All Skills](../../SKILL_TREE.md) > [SDK Setup](../sentry-sdk-setup/SKILL.md) > Cocoa SDK
- Opinionated wizard that scans your Apple project and guides you through complete Sentry setup.
- Follow sentry-cocoa-sdk SKILL.md steps and documented constraints.
- Follow sentry-cocoa-sdk SKILL.md steps and documented constraints.
Sentry Cocoa Sdk by the numbers
- 2,182 all-time installs (skills.sh)
- +58 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #83 of 1,453 DevOps & CI/CD skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
sentry-cocoa-sdk capabilities & compatibility
- Capabilities
- description: full sentry sdk setup for apple pla · > [all skills](../../skill_tree.md) > [sdk setup · opinionated wizard that scans your apple project · follow sentry cocoa sdk skill.md steps and docum
- Use cases
- orchestration
What sentry-cocoa-sdk says it does
description: Full Sentry SDK setup for Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). Use when asked to "add Sentry to iOS", "add Sentry to Swift", "install sentry-cocoa", or configure error m
> [All Skills](../../SKILL_TREE.md) > [SDK Setup](../sentry-sdk-setup/SKILL.md) > Cocoa SDK
Opinionated wizard that scans your Apple project and guides you through complete Sentry setup.
npx skills add https://github.com/getsentry/sentry-for-ai --skill sentry-cocoa-sdkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.2k |
|---|---|
| repo stars | ★ 243 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | getsentry/sentry-for-ai ↗ |
When should an agent use sentry-cocoa-sdk and what problem does it solve?
Full Sentry SDK setup for Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). Use when asked to "add Sentry to iOS", "add Sentry to Swift", "install sentry-cocoa", or configure error monitoring, tr
Who is it for?
Developers invoking sentry-cocoa-sdk as documented in the skill source.
Skip if: Skip when requirements fall outside sentry-cocoa-sdk documented scope.
When should I use this skill?
Full Sentry SDK setup for Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). Use when asked to "add Sentry to iOS", "add Sentry to Swift", "install sentry-cocoa", or configure error monitoring, tr
What you get
Outputs aligned with the sentry-cocoa-sdk SKILL.md workflow and stated deliverables.
- Sentry SDK initialization code
- Configured crash and HTTP error capture
By the numbers
- Requires sentry-cocoa v7.0.0+ minimum SDK
- Default maxBreadcrumbs is 100 per captured event
- attachAllThreads stack traces require sentry-cocoa SDK 9.9+
Files
All Skills > SDK Setup > Cocoa SDK
Sentry Cocoa SDK
Opinionated wizard that scans your Apple project and guides you through complete Sentry setup.
Invoke This Skill When
- User asks to "add Sentry to iOS/macOS/tvOS" or "set up Sentry" in an Apple app
- User wants error monitoring, tracing, profiling, session replay, or logging in Swift/ObjC, or metrics in Swift
- User mentions
sentry-cocoa,SentrySDK, or the Apple/iOS Sentry SDK - User wants to monitor crashes, app hangs, watchdog terminations, or performance
Note: SDK versions and APIs below reflect Sentry docs at time of writing (sentry-cocoa 9.15.0).
Always verify against docs.sentry.io/platforms/apple/ before implementing.
---
Phase 1: Detect
Run these commands to understand the project before making any recommendations:
# Check existing Sentry dependency
grep -rEi "sentry|sentry-cocoa|SentrySPM|SentrySwiftUI" \
--include="Package.swift" --include="Podfile" --include="Cartfile" \
--include="Package.resolved" --include="project.pbxproj" . 2>/dev/null | head -20
# Detect UI framework (SwiftUI vs UIKit)
grep -rE "@main|struct .*: App" --include="*.swift" . 2>/dev/null | head -5
grep -rE "AppDelegate|UIApplicationMain|@UIApplicationDelegateAdaptor" --include="*.swift" . 2>/dev/null | head -5
# Detect platform and deployment targets
grep -rE "platforms:|\\.iOS|\\.macOS|\\.tvOS|\\.watchOS|\\.visionOS|IPHONEOS_DEPLOYMENT_TARGET|MACOSX_DEPLOYMENT_TARGET|TVOS_DEPLOYMENT_TARGET|WATCHOS_DEPLOYMENT_TARGET|XROS_DEPLOYMENT_TARGET" \
--include="Package.swift" --include="project.pbxproj" . 2>/dev/null | head -20
grep -E "platform :ios|platform :osx|platform :tvos|platform :watchos" Podfile 2>/dev/null
# Detect logging
grep -rE "import OSLog|import os\\.log|Logger\\(|CocoaLumberjack|DDLog" --include="*.swift" . 2>/dev/null | head -5
# Detect companion backend
ls ../backend ../server ../api 2>/dev/null
ls ../go.mod ../requirements.txt ../Gemfile ../package.json 2>/dev/nullWhat to note:
- Is
sentry-cocoaalready inPackage.swiftorPodfile? If yes, skip to Phase 2 (configure features). - SwiftUI (
@main Appstruct) or UIKit (AppDelegate)? Determines init pattern. - Which Apple platforms? (Affects which features are available — see Platform Support Matrix.)
- Existing logging library? (Enables structured log capture.)
- SwiftUI tracing import/product?
SentrySwiftUIstill exists but is deprecated in SDK 9.4.1+; prefer the mainSentrymodule for released binary products. - Companion backend? (Triggers Phase 4 cross-link for distributed tracing.)
---
Phase 2: Recommend
Based on what you found, present a concrete recommendation. Don't ask open-ended questions — lead with a proposal:
Recommended (core coverage):
- Error Monitoring — always; crash reporting, app hangs, watchdog terminations, NSError/Swift errors
- Tracing — always for apps; auto-instruments app launch, network, UIViewController, file I/O, Core Data
- Profiling — production iOS/macOS apps; UI profiling via
configureProfiling
Optional (enhanced observability):
- Session Replay — user-facing iOS apps; verify masking on iOS 26+ / Liquid Glass builds
- Logging — when structured log capture is needed
- Metrics — Swift apps needing aggregate counters, gauges, or distributions
- User Feedback — apps that want crash/error feedback forms from users
Not available for Cocoa:
- Crons — backend only
- AI Monitoring — JS/Python only
Recommendation logic:
| Feature | Recommend when... |
|---|---|
| Error Monitoring | Always — non-negotiable baseline |
| Tracing | Always for apps — rich auto-instrumentation out of the box |
| Profiling | iOS/macOS production apps where performance matters (not tvOS/watchOS/visionOS) |
| Session Replay | User-facing iOS apps; tvOS may work but is not officially supported |
| Logging | Existing os.log / CocoaLumberjack usage, or structured logs needed |
| Metrics | Aggregate product or health signals that should not create issues; Swift only, SDK 9.12+ |
| User Feedback | Apps wanting in-app bug reports with screenshots |
Propose: "I recommend Error Monitoring + Tracing + Profiling. Want me to also add Session Replay and Logging?"
---
Phase 3: Guide
Install
Option 1 — Sentry Wizard (recommended):
You need to run this yourself — the wizard opens a browser for login and requires interactive input that the agent can't handle. Copy-paste into your terminal:
>
```
brew install getsentry/tools/sentry-wizard && sentry-wizard -i ios
```
>
It handles login, org/project selection, auth token setup, SDK installation, AppDelegate updates, and dSYM/debug symbol upload build phases.
>
Once it finishes, come back and skip to [Verification](#verification).
If the user skips the wizard, proceed with Option 2 (SPM/CocoaPods) and manual setup below.
Option 2 — Swift Package Manager: File → Add Packages → enter:
https://github.com/getsentry/sentry-cocoa.gitOr in Package.swift:
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.15.0"),SPM Products — choose exactly one per target:
| Product | Use Case |
|---|---|
Sentry | Recommended — static framework, fast app start; includes SwiftUI APIs in SDK 9.4.1+ |
Sentry-Dynamic | Dynamic framework alternative |
SentrySwiftUI | Legacy/deprecated re-export for SwiftUI APIs; use only when maintaining older setup |
Sentry-WithoutUIKitOrAppKit | watchOS, app extensions, CLI tools (Swift < 6.1) |
SentrySPM + NoUIFramework trait | Source build without UIKit/AppKit for CLI/headless targets (SDK 9.7+ / Swift 6.1+ / Xcode 26.4+ for Xcode UI) |
Warning: Xcode allows selecting multiple products — choose only one.
>
If usingSentrySPMfrom source, current source-build projects may importSentrySwiftinstead ofSentry; verify the module name in the target. Released binary products useimport Sentry.
Swift 6.1+ trait-based opt-out of UIKit/AppKit (requires Package@swift-6.1.swift manifest):
// Package.swift (Swift 6.1+)
.package(
url: "https://github.com/getsentry/sentry-cocoa",
from: "9.15.0",
traits: ["NoUIFramework"]
),
// In your target's dependencies:
.product(name: "SentrySPM", package: "sentry-cocoa")This is the preferred opt-out path for command-line/headless targets on Swift 6.1+. It compiles the SDK from source so the trait can remove UIKit/AppKit/SwiftUI linkage. For Swift < 6.1 continue using Sentry-WithoutUIKitOrAppKit.
Note: Package traits are visible in the Xcode UI starting with Xcode 26.4+. On older Xcode versions, traits still work when declared in Package.swift but won't appear in the GUI.Option 3 — CocoaPods (deprecated; prefer SPM):
platform :ios, '15.0'
use_frameworks!
target 'YourApp' do
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '9.15.0'
endSentry plans to stop publishing CocoaPods releases at the end of June 2026; use this only for existing CocoaPods projects.
Known issue (Xcode 14+): Sandboxrsync.sambaerror → Target Settings → "Enable User Script Sandbox" →NO.
Option 4 — SentryObjC (for pure Objective-C/C++ projects):
For pure Objective-C or Objective-C++ projects that cannot enable Clang modules (e.g., -fmodules=NO), use the SentryObjC wrapper SDK. It provides the same functionality as the main SDK but with pure Objective-C headers that don't require Swift module imports.
SPM:
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.17.1"),
// In your target's dependencies:
.product(name: "SentryObjC", package: "sentry-cocoa")Or download SentryObjC-Dynamic.xcframework.zip from the releases page.
Migration from regular Sentry to SentryObjC:
- Change
#import <Sentry/Sentry.h>to#import <SentryObjC/SentryObjC.h> - Rename
Sentry-prefixed types toSentryObjC(e.g.,SentrySDK→SentryObjCSDK,SentryOptions→SentryObjCOptions) - The API surface is otherwise identical
Most users should use the standard Sentry product (Option 2). Only use SentryObjC if you have a specific requirement preventing Clang modules.
---
Quick Start — Recommended Init
Full iOS app config enabling the most common features with sensible defaults. Add before any other code at app startup.
For macOS, watchOS, app extensions, or NoUIFramework builds, omit options that are unavailable for that platform (sessionReplay, screenshots/view hierarchy, user-feedback UI, UIKit tracing, and profiling on tvOS/watchOS/visionOS). Keep the core dsn, environment, error monitoring, tracing, logs, and metrics settings that compile for the detected target.
SwiftUI — App entry point:
import SwiftUI
import Sentry
@main
struct MyApp: App {
init() {
SentrySDK.start { options in
options.dsn = ProcessInfo.processInfo.environment["SENTRY_DSN"]
?? "https://examplePublicKey@o0.ingest.sentry.io/0"
options.environment = ProcessInfo.processInfo.environment["SENTRY_ENVIRONMENT"]
?? "production"
// releaseName defaults to "<bundle id>@<version>+<build>"; set only if you need a custom release.
// Error monitoring (on by default — explicit for clarity)
options.enableCrashHandler = true
options.enableAppHangTracking = true
options.enableReportNonFullyBlockingAppHangs = true
options.enableWatchdogTerminationTracking = true
options.attachScreenshot = true
options.attachViewHierarchy = true
options.sendDefaultPii = true
// Tracing
options.tracesSampleRate = 1.0 // lower to 0.2 in high-traffic production
// Profiling (SDK 9.0.0+ API)
options.configureProfiling = {
$0.sessionSampleRate = 1.0
$0.lifecycle = .trace
}
// Session Replay. Keep production sampling conservative and verify masking on iOS 26+.
options.sessionReplay.sessionSampleRate = 0.1
options.sessionReplay.onErrorSampleRate = 1.0
// Logging (SDK 9.0.0+ top-level; use options.experimental.enableLogs in 8.x)
options.enableLogs = true
// Metrics are enabled by default in SDK 9.12+. Set false only to opt out.
options.enableMetrics = true
}
}
var body: some Scene {
WindowGroup { ContentView() }
}
}UIKit — AppDelegate:
import UIKit
import Sentry
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
SentrySDK.start { options in
options.dsn = ProcessInfo.processInfo.environment["SENTRY_DSN"]
?? "https://examplePublicKey@o0.ingest.sentry.io/0"
options.environment = ProcessInfo.processInfo.environment["SENTRY_ENVIRONMENT"]
?? "production"
// releaseName defaults to "<bundle id>@<version>+<build>"; set only if you need a custom release.
options.enableCrashHandler = true
options.enableAppHangTracking = true
options.enableReportNonFullyBlockingAppHangs = true
options.enableWatchdogTerminationTracking = true
options.attachScreenshot = true
options.attachViewHierarchy = true
options.sendDefaultPii = true
options.tracesSampleRate = 1.0
options.configureProfiling = {
$0.sessionSampleRate = 1.0
$0.lifecycle = .trace
}
options.sessionReplay.sessionSampleRate = 0.1
options.sessionReplay.onErrorSampleRate = 1.0
// Logging (SDK 9.0.0+ top-level; use options.experimental.enableLogs in 8.x)
options.enableLogs = true
// Metrics are enabled by default in SDK 9.12+. Set false only to opt out.
options.enableMetrics = true
}
return true
}
}Warning: SDK initialization must occur on the main thread.
---
For Each Agreed Feature
Walk through features one at a time. Load the reference file for each, follow its steps, and verify before moving to the next:
| Feature | Reference file | Load when... |
|---|---|---|
| Error Monitoring | ${SKILL_ROOT}/references/error-monitoring.md | Always (baseline) |
| Tracing | ${SKILL_ROOT}/references/tracing.md | App launch, network, UIViewController perf |
| Profiling | ${SKILL_ROOT}/references/profiling.md | Production perf-sensitive apps |
| Session Replay | ${SKILL_ROOT}/references/session-replay.md | User-facing iOS apps; tvOS only with caveat |
| Logging | ${SKILL_ROOT}/references/logging.md | Structured log capture needed |
| Metrics | ${SKILL_ROOT}/references/metrics.md | Aggregate counters, gauges, distributions |
| User Feedback | ${SKILL_ROOT}/references/user-feedback.md | In-app bug reporting wanted |
For each feature: Read ${SKILL_ROOT}/references/<feature>.md, follow steps exactly, verify it works.
---
Configuration Reference
Key SentryOptions Fields
| Option | Type | Default | Purpose |
|---|---|---|---|
dsn | String? | nil | SDK disabled if empty; macOS can read SENTRY_DSN, other Apple platforms must set explicitly |
environment | String | "production" | e.g., "production" |
releaseName | String? | bundle-derived | Defaults to <bundle id>@<version>+<build> |
debug | Bool | false | Verbose SDK output — disable in production |
sendDefaultPii | Bool | false | Include IP, user info from active integrations |
enableCrashHandler | Bool | true | Master switch for crash reporting |
enableAppHangTracking | Bool | true | Master switch for app hang tracking |
enableReportNonFullyBlockingAppHangs | Bool | true | Report non-fully-blocking hangs on supported UI platforms |
appHangTimeoutInterval | Double | 2.0 | Seconds before classifying as hang |
enableWatchdogTerminationTracking | Bool | true | Track watchdog kills (iOS, tvOS, Mac Catalyst) |
attachScreenshot | Bool | false | Capture screenshot on error |
attachViewHierarchy | Bool | false | Capture view hierarchy on error |
tracesSampleRate | NSNumber? | nil | Transaction sample rate (nil = tracing disabled); Swift auto-boxes Double literals (e.g. 1.0 → NSNumber) |
tracesSampler | Closure | nil | Dynamic per-transaction sampling (overrides rate) |
enableAutoPerformanceTracing | Bool | true | Master switch for auto-instrumentation |
tracePropagationTargets | [Any] | all requests | Strings or NSRegularExpression values that receive distributed trace headers |
enableCaptureFailedRequests | Bool | true | Auto-capture HTTP 5xx errors as events |
enableNetworkBreadcrumbs | Bool | true | Breadcrumbs for outgoing HTTP requests |
add(inAppInclude:) | Method | bundle executable | Add module prefixes treated as "in-app" code |
maxBreadcrumbs | Int | 100 | Max breadcrumbs per event |
sampleRate | Float | 1.0 | Error event sample rate |
beforeSend | Closure | nil | Hook to mutate/drop error events |
onLastRunStatusDetermined | Closure | nil | Called after SDK determines previous launch crash status |
strictTraceContinuation | Bool | false | Reject incoming traces from other orgs; validates sentry-org_id in baggage headers (sentry-cocoa ≥9.10.0) |
orgId | String? | nil | Organization ID for strict trace validation; auto-parsed from DSN host (e.g. o123.ingest.sentry.io → "123") if not set explicitly |
enableLogs | Bool | false | Enable structured logs |
enableMetrics | Bool | true | Enable Swift Metrics API (SDK 9.12+) |
Environment Variables
| Variable | Maps to | Purpose |
|---|---|---|
SENTRY_DSN | dsn | macOS fallback only; set explicitly on iOS/tvOS/watchOS/visionOS |
SENTRY_RELEASE | releaseName | Do not assume automatic Cocoa fallback; set explicitly if needed |
SENTRY_ENVIRONMENT | environment | Do not assume automatic Cocoa fallback; set explicitly if needed |
Platform Feature Support Matrix
| Feature | iOS | tvOS | macOS | watchOS | visionOS |
|---|---|---|---|---|---|
| Crash Reporting | Yes | Yes | Yes | No | Yes |
| App Hangs | Yes | Yes | Yes | No | Yes |
| Watchdog Termination | Yes | Yes | No | No | Yes |
| App Start Tracing | Yes | Yes | No | No | Yes |
| UIViewController Tracing | Yes | Yes | No | No | Yes |
| SwiftUI Tracing | Yes | Yes | Yes | No | Yes |
| Network Tracking | Yes | Yes | Yes | No | Yes |
| Profiling | Yes | No | Yes | No | No |
| Session Replay | Yes | Unofficial | No | No | No |
| MetricKit | Yes (15+) | No | Yes (12+) | No | No |
| Metrics API | Yes | Yes | Yes | Verify | Yes |
Production Settings
Lower sample rates for production to control volume and cost:
options.tracesSampleRate = 0.2 // 20% of transactions
options.configureProfiling = {
$0.sessionSampleRate = 0.1 // 10% of sessions
$0.lifecycle = .trace
}
options.sessionReplay.sessionSampleRate = 0.1 // 10% continuous
options.sessionReplay.onErrorSampleRate = 1.0 // 100% on error (keep high)
options.enableLogs = true
options.enableMetrics = true // default true in SDK 9.12+
options.debug = false // never in production---
Verification
Test that Sentry is receiving events:
// Trigger a test error event:
SentrySDK.capture(message: "Sentry Cocoa SDK test")
// Or test crash reporting (without debugger — crashes are intercepted by debugger):
// SentrySDK.crash() // uncomment, run without debugger, relaunch to see crash reportCheck the Sentry dashboard within a few seconds. If nothing appears: 1. Set options.debug = true — prints SDK internals to Xcode console 2. Verify the DSN is correct and the project exists 3. Ensure initialization is on the main thread
---
Phase 4: Cross-Link
After completing Apple setup, check for a companion backend missing Sentry coverage:
# Detect companion backend
ls ../backend ../server ../api 2>/dev/null
cat ../go.mod 2>/dev/null | head -5
cat ../requirements.txt ../Pipfile 2>/dev/null | head -5
cat ../Gemfile 2>/dev/null | head -5
cat ../package.json 2>/dev/null | grep -E '"name"|"dependencies"' | head -5If a backend is found, configure tracePropagationTargets to enable distributed tracing end-to-end, and suggest the matching skill:
| Backend detected | Suggest skill | Trace header support |
|---|---|---|
Go (go.mod) | sentry-go-sdk | Automatic |
Python (requirements.txt) | sentry-python-sdk | Automatic |
Ruby (Gemfile) | sentry-ruby-sdk | Automatic |
Node.js backend (package.json) | sentry-node-sdk (or sentry-express-sdk) | Automatic |
---
Troubleshooting
| Issue | Solution |
|---|---|
| Events not appearing | Set debug: true, verify DSN format, ensure init is on main thread |
| Crashes not captured | Run without debugger attached — debugger intercepts signals |
| App hangs not reported | Auto-disabled when debugger attached; check appHangTimeoutInterval |
| Session Replay not recording | Verify sessionSampleRate > 0 or onErrorSampleRate > 0; on iOS 26+ verify masking and any manual Liquid Glass gating |
| Tracing data missing | Confirm tracesSampleRate > 0; check enableAutoPerformanceTracing = true |
| Profiling data missing | Verify sessionSampleRate > 0 in configureProfiling; for .trace lifecycle, tracing must be enabled |
rsync.samba build error (CocoaPods) | Target Settings → "Enable User Script Sandbox" → NO |
| Multiple SPM products selected | Choose only one of Sentry, Sentry-Dynamic, SentrySwiftUI, Sentry-WithoutUIKitOrAppKit, or SentrySPM (with NoUIFramework trait on Swift 6.1+) |
inAppExclude compile error | Removed in SDK 9.0.0 — use options.add(inAppInclude:) |
enableAppHangTrackingV2 compile error | Removed in SDK 9.0.0 — use enableAppHangTracking; V2 behavior is default where supported |
| Watchdog termination not tracked | Requires enableCrashHandler = true (it is by default) |
| Network breadcrumbs missing | Requires enableSwizzling = true (it is by default) |
profilesSampleRate compile error | Removed in SDK 9.0.0 — use configureProfiling closure instead |
Error Monitoring — Sentry Cocoa SDK
Minimum SDK: sentry-cocoa v7.0.0+Swift Error improvements: v8.7.0+
HTTP client error capture: v8.0.0+
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
enableCrashHandler | Bool | true | Master switch for crash reporting (signal handlers, Mach exceptions, C++) |
sampleRate | Float (0.0–1.0) | 1.0 | Percentage of error events sent |
attachStacktrace | Bool | true | Attach stack traces to all captured messages |
attachAllThreads | Bool | false | Attach full stack traces for all threads (SDK 9.9+) |
maxBreadcrumbs | Int | 100 | Max breadcrumbs per event |
enableAppHangTracking | Bool | true | Detect main thread unresponsiveness |
appHangTimeoutInterval | Double | 2.0 | Seconds before a hang is reported |
enableReportNonFullyBlockingAppHangs | Bool | true | Include non-fully-blocking hangs where V2 is supported |
enableWatchdogTerminationTracking | Bool | true | Track OS watchdog kills via heuristics |
enableCaptureFailedRequests | Bool | true | Auto-capture HTTP client errors as Sentry events |
failedRequestStatusCodes | [HttpStatusCodeRange] | [500–599] | Status code ranges that trigger error capture |
failedRequestTargets | [String] | [".*"] | Hosts/regex patterns to monitor for HTTP errors |
attachScreenshot | Bool | false | Capture screenshot when an error event fires |
attachViewHierarchy | Bool | false | Capture view hierarchy when an error event fires |
sendDefaultPii | Bool | false | Include PII (IP address, username) in events |
Code Examples
SDK initialization
import Sentry
SentrySDK.start { options in
options.dsn = "https://examplePublicKey@o0.ingest.sentry.io/0"
options.environment = "production"
options.releaseName = "my-app@2.0.0+123"
options.enableCrashHandler = true // default; explicit for clarity
options.attachScreenshot = true
options.attachViewHierarchy = true
}Capture a message
SentrySDK.capture(message: "Something noteworthy happened")Capture a Swift Error / NSError
do {
try riskyOperation()
} catch {
SentrySDK.capture(error: error)
}Capture a custom SentryEvent
let event = Event(level: .warning)
event.message = SentryMessage(formatted: "Checkout flow aborted")
event.tags = ["feature": "checkout"]
event.extra = ["cart_items": 3]
SentrySDK.capture(event: event)Swift Error enum — human-readable titles (v8.7.0+)
By default, Swift error enum cases appear as LoginError - Code: 1. To get readable titles, conform to CustomNSError:
enum LoginError: Error {
case wrongUser(id: String)
case wrongPassword
}
extension LoginError: CustomNSError {
var errorUserInfo: [String: Any] {
[NSDebugDescriptionErrorKey: debugDescription]
}
private var debugDescription: String {
switch self {
case .wrongUser(let id): return "Wrong user (id: \(id))"
case .wrongPassword: return "Wrong password"
}
}
}
// Captures "LoginError - Wrong user (id: 12345)" as the issue title
SentrySDK.capture(error: LoginError.wrongUser(id: "12345"))UseNSDebugDescriptionErrorKey, NOTNSLocalizedDescriptionKey. Localized strings vary by device locale and create duplicate issues.
Capture with per-event scope
The scope callback receives an isolated copy — changes don't affect global state:
SentrySDK.capture(error: error) { scope in
scope.setTag(value: "checkout", key: "feature")
scope.setContext(value: ["amount": 99.99, "currency": "USD"], key: "payment")
}
SentrySDK.capture(message: "Payment declined") { scope in
scope.setLevel(.fatal)
scope.setTag(value: "stripe", key: "payment_provider")
}App hang detection
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.enableAppHangTracking = true
options.appHangTimeoutInterval = 2.0 // default; avoid values < 0.1
// V2 is the default in v9+ where supported; this option controls whether
// less-actionable non-fully-blocking hangs are reported.
options.enableReportNonFullyBlockingAppHangs = true
}
// Pause tracking during expected blocking operations (e.g., permission dialogs)
SentrySDK.pauseAppHangTracking()
// ... system dialog ...
SentrySDK.resumeAppHangTracking()V2 exception types:
| Type | Meaning |
|---|---|
App Hang Fully Blocked | Main thread completely frozen |
App Hang Non Fully Blocked | App stuck but still renders some frames |
Fatal App Hang Fully Blocked | Force-quit / watchdog kill during full block |
Fatal App Hang Non Fully Blocked | Force-quit / watchdog kill during partial block |
HTTP client error capture
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.enableCaptureFailedRequests = true
// Capture 4xx and 5xx
options.failedRequestStatusCodes = [
HttpStatusCodeRange(min: 400, max: 599)
]
// Only monitor your own backend
options.failedRequestTargets = [
"api.myapp.com",
".*\\.myapp\\.com" // regex supported
]
}Scope management
// Global scope — persists across all events
SentrySDK.configureScope { scope in
scope.setTag(value: "premium", key: "subscription")
scope.setExtra(value: 42, key: "retry_count")
let user = User()
user.email = "user@example.com"
user.userId = "abc123"
scope.setUser(user)
scope.setContext(value: [
"version": "2.1",
"platform": "ios"
], key: "app_info")
}
// Clear a specific value
SentrySDK.configureScope { scope in
scope.removeTag(key: "subscription")
scope.setUser(nil) // clear user on logout
}
// Clear everything
SentrySDK.configureScope { $0.clear() }Set user identity
let user = User()
user.userId = "user-abc-123"
user.email = "john.doe@example.com"
user.username = "johndoe"
user.data = ["plan": "premium"]
SentrySDK.setUser(user)
// On logout
SentrySDK.setUser(nil)Breadcrumbs
let crumb = Breadcrumb()
crumb.level = .info
crumb.category = "auth"
crumb.type = "user"
crumb.message = "User logged in"
crumb.data = ["method": "oauth", "provider": "google"]
SentrySDK.addBreadcrumb(crumb)Filter breadcrumbs via beforeBreadcrumb:
SentrySDK.start { options in
options.beforeBreadcrumb = { crumb in
if crumb.message?.contains("password") == true { return nil }
return crumb
}
}beforeSend hook — filter and modify events
SentrySDK.start { options in
options.beforeSend = { event in
// Drop events from internal testers
if event.user?.email?.hasSuffix("@mycompany.com") == true {
return nil
}
// Suppress app hang events
// V1/macOS may use "App Hanging"; V2 uses the more specific values
// listed above. Inspect event.exceptions?.first?.type in beforeSend.
if event.exceptions?.first?.type?.contains("App Hang") == true {
return nil
}
// Scrub sensitive data
event.request?.cookies = nil
// Add global tag
event.tags?["processed_by"] = "beforeSend"
return event
}
}Screenshot and view hierarchy attachments
SentrySDK.start { options in
options.attachScreenshot = true
options.screenshot.maskAllText = true // default: true
options.screenshot.maskAllImages = true // default: true
options.screenshot.maskedViewClasses = [MySecretView.self]
options.screenshot.unmaskedViewClasses = [MyLogoView.self]
options.attachViewHierarchy = true
options.reportAccessibilityIdentifier = true // disable if identifiers contain PII
// Conditional capture
options.beforeCaptureScreenshot = { event in event.level == .fatal }
options.beforeCaptureViewHierarchy = { _ in true }
}Fingerprinting and custom grouping
// Per-event fingerprint via scope
SentrySDK.capture(error: error) { scope in
scope.fingerprint = ["payment-service-timeout", "stripe"]
}
// Pattern-based in beforeSend — extend default grouping
SentrySDK.start { options in
options.beforeSend = { event in
if let error = event.error as NSError?,
error.domain == NSURLErrorDomain,
let url = error.userInfo[NSURLErrorFailingURLErrorKey] as? String {
event.fingerprint = ["{{ default }}", url, String(error.code)]
}
return event
}
}
// Aggressive grouping — all SQLite errors → one issue
SentrySDK.start { options in
options.beforeSend = { event in
if let error = event.error as NSError?,
error.domain == NSSQLiteErrorDomain {
event.fingerprint = ["database-connection-error"]
}
return event
}
}"{{ default }}" substitutes Sentry's standard hash, allowing you to extend rather than fully replace default grouping.
onLastRunStatusDetermined callback
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.onLastRunStatusDetermined = { status, crashEvent in
// Called once after init when the previous run's crash status is determined.
// Keep this minimal — complex logic can cascade into another crash.
if status == .didCrash {
UserDefaults.standard.set(true, forKey: "didCrashLastRun")
}
}
}Automatic Crash Reporting
When enableCrashHandler = true (default), the SDK installs:
- Signal handlers — SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP
- Mach exception handlers — low-level kernel exceptions
- C++ exception handlers —
std::terminateinterception - Objective-C uncaught exception handler —
NSSetUncaughtExceptionHandler
Warning: Always test crash reporting without a debugger attached. The debugger intercepts signals and prevents the SDK from capturing crashes.
macOS — uncaught NSException reporting
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.enableUncaughtNSExceptionReporting = true
}SIGTERM reporting (v8.27.0+)
options.enableSigtermReporting = true // report background task timeoutsScope API Quick Reference
SentrySDK.configureScope { scope in
scope.setTag(value: "v2", key: "api_version")
scope.removeTag(key: "api_version")
scope.setExtra(value: someObject, key: "debug_info")
scope.removeExtra(key: "debug_info")
scope.setContext(value: ["key": "value"], key: "my_context")
scope.removeContext(key: "my_context")
scope.setUser(User(userId: "12345"))
scope.setUser(nil)
scope.setLevel(.error)
scope.fingerprint = ["my-group-key"]
scope.addBreadcrumb(crumb)
scope.clear()
}Best Practices
- Set
releaseNameto a consistent value (e.g.,CFBundleShortVersionString + "+" + CFBundleVersion) for regression tracking between deployments - Use
NSDebugDescriptionErrorKey— notNSLocalizedDescriptionKey— for error user info to avoid locale-based duplicate issues - Use
beforeSendto strip PII (event.request?.cookies = nil) whensendDefaultPii = false - Use
onLastRunStatusDeterminedonly for lightweight operations (flag writes); heavy logic risks a cascading crash - Disable app hang tracking for Widgets and Live Activities to avoid false positives
- Use
initialScopeto set global context before the first event fires
Troubleshooting
| Issue | Solution |
|---|---|
| Crashes not appearing in Sentry | Test without debugger attached; debugger intercepts signals |
| Swift errors show "Code: 1" | Conform to CustomNSError and provide NSDebugDescriptionErrorKey in errorUserInfo |
| Duplicate issues from localization | Use NSDebugDescriptionErrorKey, not NSLocalizedDescriptionKey |
| App hang events too noisy | Raise appHangTimeoutInterval; or filter in beforeSend by exception type |
| HTTP errors not captured | Verify enableCaptureFailedRequests = true and failedRequestStatusCodes covers the status code |
| Screenshots contain PII | Enable screenshot.maskAllText = true and screenshot.maskAllImages = true (both default) |
Events missing from beforeSend for transactions | beforeSend is for error/message events only; use beforeSendSpan for spans |
onLastRunStatusDetermined not firing | SDK must be initialized on main thread; check enableCrashHandler = true |
Logging — Sentry Cocoa SDK
Minimum SDK (experimental): sentry-cocoa v8.55.0+Minimum SDK (stable): sentry-cocoa v9.0.0+Configuration
| Option | Type | Default | Description |
|---|---|---|---|
enableLogs | Bool | false | Enable structured logging (v9.0.0+, stable) |
experimental.enableLogs | Bool | false | Enable structured logging (v8.55.0–8.x, experimental) |
beforeSendLog | ((SentryLog) -> SentryLog?)? | nil | Filter or modify logs before sending; return nil to drop |
Code Examples
Enable logging
SDK v9.0.0+ (stable, recommended):
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.enableLogs = true
}SDK v8.55.0–8.x (experimental):
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.experimental.enableLogs = true
}All log levels
import Sentry
let logger = SentrySDK.logger
// Without attributes
logger.trace("Starting database connection")
logger.debug("Cache miss for user")
logger.info("Profile updated successfully")
logger.warn("Rate limit nearly reached")
logger.error("Failed to process payment")
logger.fatal("Database connection pool exhausted")
// With structured attributes
logger.trace("Starting DB connection", attributes: ["database": "users"])
logger.debug("Cache miss for user", attributes: ["userId": 123])
logger.info("Profile updated", attributes: ["profileId": 345])
logger.warn("Rate limit reached", attributes: ["endpoint": "/api/results/"])
logger.error("Payment failed", attributes: ["amount": 99.99])
logger.fatal("Connection pool exhausted", attributes: ["activeConnections": 100])Supported Swift attribute value types include String, Bool, Int, Double, Float, arrays, and sets; other values are converted to strings.
Log levels (severity order)
| Level | Method | Typical Use |
|---|---|---|
| 1 — Trace | logger.trace(...) | Very fine-grained diagnostic events |
| 2 — Debug | logger.debug(...) | Debugging information |
| 3 — Info | logger.info(...) | General informational messages |
| 4 — Warn | logger.warn(...) | Potentially harmful situations |
| 5 — Error | logger.error(...) | Error events; app may continue |
| 6 — Fatal | logger.fatal(...) | Severe errors; likely app abort |
Swift string interpolation as structured attributes
When you use Swift string interpolation in the message, the SDK automatically extracts the interpolated values as named attributes using the key pattern sentry.message.parameter.{index}:
let userId = "user_123"
let orderCount = 5
logger.info("User \(userId) placed \(orderCount) orders")
// Sentry receives:
// message template: "User %s placed %d orders"
// sentry.message.parameter.0 = "user_123"
// sentry.message.parameter.1 = 5This preserves the ability to search and filter by the template while retaining the individual values as queryable attributes.
beforeSendLog filter hook
SentrySDK.start { options in
options.enableLogs = true
options.beforeSendLog = { log in
// Drop trace-level logs
if log.level == .trace { return nil }
// Drop debug logs in production
if log.level == .debug && options.environment == "production" { return nil }
// Enrich all logs with app version
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
log.attributes["app.version"] = SentryAttribute(string: version)
}
return log
}
}Available on SentryLog:
log.level—SentryLog.Level(.trace,.debug,.info,.warn,.error,.fatal)log.body—Stringlog.timestamp—Datelog.attributes—[String: SentryAttribute]
Automatic default attributes
The SDK automatically attaches the following to every log entry:
environmentandrelease- SDK name and version
- User ID, name, email (if set via
SentrySDK.setUser(...)) - Message template and
sentry.message.parameter.*interpolated values - Integration origin marker
Using alongside Apple os.log
SentrySDK.logger is a standalone Sentry telemetry system — it is not a bridge to os.log / Logger. To write to both:
import OSLog
import Sentry
private let osLog = Logger(subsystem: "com.myapp", category: "network")
func fetchData() {
osLog.info("Fetching data") // → system log / Console.app
SentrySDK.logger.info("Fetching data", // → Sentry Logs
attributes: ["subsystem": "network"])
}There is no built-in bridge to automatically forward OSLog entries to Sentry.
Full initialization example with logging
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.environment = "production"
options.enableLogs = true // v9.0.0+
options.beforeSendLog = { log in
// Drop trace and debug in production
guard log.level != .trace && log.level != .debug else { return nil }
return log
}
}
// Anywhere in your app:
SentrySDK.logger.info("User signed in",
attributes: ["userId": currentUser.id, "method": "oauth"])Known Limitations
- Logs can be lost in crash scenarios if the SDK cannot flush the buffer before the app terminates — this is a known limitation of the current implementation
- Logs are a separate pipeline from error events — they are not attached to breadcrumbs or spans automatically
- Attribute values are limited to
String,Int,Double, andBool— other types must be converted
Best Practices
- Prefer
logger.error(...)orlogger.fatal(...)overSentrySDK.capture(message:)for application-level log lines — structured logs are easier to search and filter in Sentry - Use structured attributes instead of embedding values in the message string directly; attributes are indexed and queryable
- Use Swift string interpolation to let the SDK extract attribute values automatically
- Set
beforeSendLogto droptraceanddebugin production to reduce noise and volume - Set the user via
SentrySDK.setUser(...)before logging to automatically correlate logs with user identities
Troubleshooting
| Issue | Solution |
|---|---|
| Logs not appearing in Sentry | Verify options.enableLogs = true (v9+) or options.experimental.enableLogs = true (v8.55+) |
| Logs only partially appearing | Logs may be lost during crashes; this is a known SDK limitation |
SentrySDK.logger not found | Requires v8.55.0+; check SPM/CocoaPods version |
| Attributes not queryable | Prefer String, Bool, Int, Double, Float, arrays, or sets; convert complex objects to stable strings |
beforeSendLog not called | Ensure you set it before SentrySDK.start completes and enableLogs = true |
| Too many logs overwhelming Sentry | Use beforeSendLog to filter by level; set minimum level for production |
| Logs missing user context | Call SentrySDK.setUser(...) before logging to attach user identity automatically |
Metrics — Sentry Cocoa SDK
Minimum SDK: experimental in v9.4.0+, generally available in v9.12.0+
Swift only; Objective-C metrics API is not currently available.
Metrics are enabled by default in v9.12.0+.
Use metrics for aggregate counters, gauges, and distributions that should not create Sentry issues. Do not duplicate automatic tracing, app hangs, MetricKit diagnostics, or error events.
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
enableMetrics | Bool | true | Enable or disable metrics |
beforeSendMetric | ((SentryMetric) -> SentryMetric?)? | nil | Filter or mutate metrics before send; return nil to drop |
For SDK 9.4.0-9.11.x, metrics used options.experimental.enableMetrics and options.experimental.beforeSendMetric. Those experimental options were removed in 9.12.0; use the top-level options above.
Code Examples
Basic setup
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.enableMetrics = true // default in SDK 9.12+
}Counter
Counters track discrete occurrences. count does not accept a unit.
SentrySDK.metrics.count(key: "button.click")
SentrySDK.metrics.count(
key: "link.created",
value: 1,
attributes: [
"source": "share_extension",
"is_favorite": false
]
)Gauge
Gauges track current state.
SentrySDK.metrics.gauge(
key: "queue.depth",
value: 42,
attributes: ["queue": "sync"]
)Distribution
Distributions track measured values where percentiles are useful.
SentrySDK.metrics.distribution(
key: "qr.render.duration",
value: 187.5,
unit: .millisecond,
attributes: [
"cache_hit": true,
"image_size": "1024"
]
)beforeSendMetric
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.beforeSendMetric = { metric in
var metric = metric
if case let .boolean(drop)? = metric.attributes["drop_me"], drop {
return nil
}
metric.attributes["processed"] = .boolean(true)
metric.attributes["app_area"] = .string("links")
return metric
}
}Attribute Types
Metric attributes use SentryAttributeValue at the capture API and SentryAttributeContent inside beforeSendMetric.
Supported capture values:
- Scalar:
String,Bool,Int,Double,Float - Arrays:
[String],[Bool],[Int],[Double],[Float] - Sets:
Set<String>,Set<Bool>,Set<Int>,Set<Double>,Set<Float>(SDK 9.13+)
Use stable, low-cardinality attributes. Avoid URLs, IDs, user-entered names, or unbounded strings unless they are intentionally scrubbed and useful for grouping.
Best Practices
- Use metrics for aggregate product and app-health signals, not exceptions or stack traces.
- Keep metric names lowercase and dot-delimited, such as
link.createdornetwork.reachability.changed. - Prefer distributions for durations and sizes, gauges for current state, and counters for occurrences.
- Avoid metrics that duplicate automatic spans, failed request events, app hangs, watchdog terminations, or MetricKit diagnostics.
- Keep
enableMetrics = trueunless the app has a policy or volume reason to disable metrics.
Troubleshooting
| Issue | Solution |
|---|---|
enableMetrics compile error | Requires SDK 9.12+; on 9.4-9.11 use experimental.enableMetrics or upgrade |
| Metric not sent | Verify SentrySDK.start ran and enableMetrics is true |
Count with unit fails | count has no unit parameter; use gauge or distribution if units are needed |
| Objective-C compile issue | Metrics are Swift-only |
| Too many unique series | Reduce high-cardinality attributes and metric names |
Profiling — Sentry Cocoa SDK
Minimum SDK for UI Profiling (configureProfiling):sentry-cocoav8.49.0+
Minimum SDK for stable configureProfiling API: v9.0.0+Supported platforms: iOS and macOS. Not supported on tvOS, watchOS, or visionOS.
All legacy profiling APIs (`profilesSampleRate`, `enableAppLaunchProfiling`, continuous beta) were removed in v9.0.0.
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
configureProfiling | ((SentryProfileOptions) -> Void)? | nil | Closure to configure UI Profiling (v8.49.0+) |
sessionSampleRate | Float (0.0–1.0) | 0 | Fraction of user sessions to profile; evaluated once per session |
lifecycle | SentryProfileLifecycle | .manual | .trace (auto) or .manual (explicit start/stop) |
profileAppStarts | Bool | false | Profile from the earliest possible lifecycle stage on next launch |
Code Examples
Basic setup — trace lifecycle (recommended)
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
// Tracing must be enabled for .trace lifecycle
options.tracesSampleRate = 1.0
options.configureProfiling = {
$0.sessionSampleRate = 1.0 // 100% of sessions; lower for production
$0.lifecycle = .trace // profiler runs while a root span is active
}
}Manual lifecycle — explicit start/stop
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.configureProfiling = {
$0.sessionSampleRate = 1.0
$0.lifecycle = .manual // default if omitted
}
}
// Profile a specific operation
@IBAction func onSyncTapped() {
SentrySDK.startProfiler()
URLSession.shared.dataTask(with: syncRequest) { data, _, _ in
self.processData(data)
DispatchQueue.main.async {
self.tableView.performBatchUpdates({
// update cells
}) { _ in
SentrySDK.stopProfiler()
}
}
}.resume()
}App launch profiling (trace lifecycle)
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 1.0
options.configureProfiling = {
$0.sessionSampleRate = 1.0
$0.lifecycle = .trace
$0.profileAppStarts = true // profile from earliest lifecycle stage
}
}Launch profile attaches to a special app.launch transaction (shown as "launch" in the Sentry UI). The profiler stops automatically when: 1. SentrySDK.startWithOptions is called, OR 2. TTID/TTFD is reached (if TTID/TTFD tracking is enabled)
Manual lifecycle — app launch profiling
With .manual lifecycle, a launch profile starts on the next app launch and continues until you explicitly call SentrySDK.stopProfiler().
Compound sampling example
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 0.5 // 50% of transactions traced
options.configureProfiling = {
$0.sessionSampleRate = 0.5 // 50% of those sessions profiled
$0.lifecycle = .trace
// Result: ~25% of root span creations will produce profile data (0.5 × 0.5)
}
}sessionSampleRate is evaluated once per session, not per span. The same decision applies to all profiler start attempts for the duration of that session.
SentryProfileLifecycle Values
| Value | Behaviour |
|---|---|
.manual | Profiler runs only between SentrySDK.startProfiler() and SentrySDK.stopProfiler() |
.trace | Profiler starts automatically when a new root span is created; stops when no root spans remain |
Manual Profiler Control
SentrySDK.startProfiler() // begin profiling (manual lifecycle)
SentrySDK.stopProfiler() // end profiling and flush data to SentrydSYM Upload Requirement
Profiling data in Sentry shows symbolicated stack frames. Without dSYM files, frames appear as memory addresses.
Upload dSYMs via the Sentry Wizard build phase (added automatically during wizard setup):
# Verify the build phase exists in Xcode:
# Target → Build Phases → "Upload Debug Symbols to Sentry"
# or manually:
sentry-cli --auth-token YOUR_TOKEN debug-files upload \
--org YOUR_ORG \
--project YOUR_PROJECT \
path/to/dSYMs/For CI/CD, set SENTRY_AUTH_TOKEN as an environment variable.
API History / Migration
| API | Introduced | Removed | Notes |
|---|---|---|---|
profilesSampleRate | 8.12.0 | 9.0.0 | Transaction-based profiling |
profilesSampler | 8.12.0 | 9.0.0 | Dynamic transaction-based profiling |
enableAppLaunchProfiling | 8.21.0 | 9.0.0 | Launch profiling (old) |
| Continuous profiling beta | 8.36.0 | 9.0.0 | Standalone startProfiler/stopProfiler (old) |
configureProfiling (UI Profiling) | 8.49.0 | — | Current API |
Migrating from legacy profilesSampleRate
// BEFORE (removed in v9.0.0)
SentrySDK.start { options in
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0 // Removed: no longer exists
}
// AFTER (v9.0.0+)
SentrySDK.start { options in
options.tracesSampleRate = 1.0
options.configureProfiling = {
$0.sessionSampleRate = 1.0
$0.lifecycle = .trace // Equivalent behavior
}
}MetricKit App Hang Reports
MetricKit delivers hang diagnostic payloads on iOS 15+ and macOS 12+. Starting with sentry-cocoa v9.x (merged via PR #7185), the SDK captures the full call-tree flamegraph from a MetricKit hang report instead of a single stack sample.
- No configuration required — the improvement is automatic for all apps with MetricKit-compatible OS versions
- In the Sentry UI, MetricKit hang issues now display a flamegraph showing all samples collected during the hang, improving both diagnosis and issue grouping accuracy
- MetricKit reports are delivered by the OS after the hang ends (typically on next launch); they complement — but do not replace — real-time app hang detection (automatic in SDK v9+)
| Platform | MetricKit Availability |
|---|---|
| iOS | 15+ |
| macOS | 12+ |
| tvOS | No |
| watchOS | No |
| visionOS | No |
Best Practices
- Always set
sessionSampleRate > 0— it defaults to0, so no profiling data is collected unless you explicitly set it - Use
.tracelifecycle in production: the profiler only runs during active transactions, minimising overhead - Use
.manuallifecycle to profile targeted operations (e.g., a specific button tap, a batch import) - Lower
sessionSampleRatein production (e.g.,0.1) — profiling adds CPU overhead on older devices - Upload dSYMs for every build; without them, profile data shows raw addresses
profileAppStarts = trueis most valuable for identifying slow+[AppDelegate application:didFinishLaunchingWithOptions:]work- Do not combine the old
profilesSampleRatewithconfigureProfiling— the old APIs are removed in v9.0.0
Troubleshooting
| Issue | Solution |
|---|---|
| No profiling data in Sentry | Verify sessionSampleRate > 0; it defaults to 0 |
Profiles missing for .trace lifecycle | Verify tracesSampleRate > 0; profiles only appear when transactions are sent |
| Stack frames show memory addresses | Upload dSYMs; verify build phase runs in both Debug and Release |
| Profiling not starting on app launch | Use profileAppStarts = true; SDK must be initialised with SentrySDK.startWithOptions |
configureProfiling not available | Requires v8.49.0+; check your SPM/CocoaPods version |
Old profilesSampleRate not compiling | Removed in v9.0.0; migrate to configureProfiling |
| Manual profiler never stops | Ensure SentrySDK.stopProfiler() is called on all code paths, including error branches |
| Excessive CPU overhead | Lower sessionSampleRate; switch to .trace lifecycle; avoid .manual with long-running sessions |
Session Replay — Sentry Cocoa SDK
Minimum SDK: sentry-cocoa v8.31.1+View Renderer V2 (default): v8.50.0+
Official support: iOS 16+ with UIKit and SwiftUI. tvOS 16+ may work but is not officially supported.
SDK 9.12.0+ runs on iOS 26 Liquid Glass; verify masking for your app.
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
sessionReplay.sessionSampleRate | Float (0.0–1.0) | 0 | Continuous recording sample rate |
sessionReplay.onErrorSampleRate | Float (0.0–1.0) | 0 | Buffered recording sample rate (uploads on error) |
sessionReplay.maskAllText | Bool | true | Mask all text content |
sessionReplay.maskAllImages | Bool | true | Mask all images |
sessionReplay.maskedViewClasses | [AnyClass] | [] | Additional view classes to always mask |
sessionReplay.unmaskedViewClasses | [AnyClass] | [] | View classes to always unmask |
sessionReplay.quality | SentryReplayQuality | .medium | Video quality (bitrate and resolution) |
sessionReplay.enableViewRendererV2 | Bool | true | Faster renderer (default since v8.50.0) |
sessionReplay.enableFastViewRendering | Bool | false | Experimental CALayer renderer (faster, less accurate) |
sessionReplay.frameRate | UInt | 1 | Frames per second |
sessionReplay.errorReplayDuration | TimeInterval | 30 | Seconds of buffer kept before an error |
sessionReplay.sessionSegmentDuration | TimeInterval | 5 | Seconds per upload segment |
sessionReplay.maximumDuration | TimeInterval | 3600 | Maximum session duration (60 min) |
experimental.enableReplayNetworkDetailsCapturing | Bool | false | Capture request/response details in replays (SDK 9.12+) |
sessionReplay.networkDetailAllowUrls | [SentryUrlMatchable] | [] | URL allowlist for replay network details |
sessionReplay.networkDetailDenyUrls | [SentryUrlMatchable] | [] | URL denylist for replay network details |
sessionReplay.networkCaptureBodies | Bool | true | Capture bodies for allowed URLs when network details are enabled |
sessionReplay.networkRequestHeaders | [String] | default safe headers | Request headers to capture for allowed URLs |
sessionReplay.networkResponseHeaders | [String] | default safe headers | Response headers to capture for allowed URLs |
Code Examples
Basic setup
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
// Continuously record 10% of sessions
options.sessionReplay.sessionSampleRate = 0.1
// Buffer and upload on error for all other sessions
options.sessionReplay.onErrorSampleRate = 1.0
}Sampling logic: sessionSampleRate is evaluated first. If not selected for continuous recording, the SDK switches to buffered mode and evaluates onErrorSampleRate — keeping a rolling buffer that is uploaded only when an error fires.
Session lifecycle
- Starts: SDK init or app foreground
- Ends: 30+ seconds in background, or 60-minute maximum
- Buffer mode: Keeps a rolling 30-second window; uploaded on error capture
- Segments: Chunked into 5-second segments for upload
- Resumes: Within 30 seconds of foreground using the same
replay_id
Privacy masking defaults
What is masked by default:
- Masked: all text content (
maskAllText = true) - Masked: all images (
maskAllImages = true) - Masked: user input fields (always masked, regardless of settings)
- Masked: video players
- Masked: WebViews
- Not masked by default: bundled image assets (considered low PII risk; shown in replay)
SwiftUI view modifiers
import Sentry
// UNMASK a specific view (show in replay despite global maskAllText/maskAllImages)
Text("Public promotion text")
.sentryReplayUnmask()
// MASK a specific view (hide in replay even if global masking is off)
Text("\(user.creditCardNumber)")
.sentryReplayMask()
// Visualize masking overlay in DEBUG builds / Xcode Previews
ContentView()
.sentryReplayPreviewMask()UIKit view instance masking
// Mask a single UIView instance
myView.sentryReplayMask()
// equivalent:
SentrySDK.replay.maskView(view: myView)
// Unmask a single UIView instance
myLabel.sentryReplayUnmask()
// equivalent:
SentrySDK.replay.unmaskView(view: myLabel)Note: Masking targetsUIViewsubclasses only. You cannot targetUIViewControllertypes directly.
Class-level masking (all instances of a class)
SentrySDK.start { options in
options.sessionReplay.maskedViewClasses = [MySecretView.self, CreditCardField.self]
options.sessionReplay.unmaskedViewClasses = [MyPublicBanner.self]
}Debug — visualize the masking overlay live
#if DEBUG
SentrySDK.replay.showMaskPreview() // full opacity
SentrySDK.replay.showMaskPreview(0.5) // 50% opacity
#endifNetwork details in replays (SDK 9.12+)
Network details are opt-in and require both the experimental flag and an allowlist:
SentrySDK.start { options in
options.experimental.enableReplayNetworkDetailsCapturing = true
options.sessionReplay.networkDetailAllowUrls = [
"api.myapp.com",
".*\\.myapp\\.com"
]
options.sessionReplay.networkDetailDenyUrls = [
"api.myapp.com/oauth",
"api.myapp.com/payment"
]
options.sessionReplay.networkCaptureBodies = false
options.sessionReplay.networkRequestHeaders = ["Content-Type", "X-Request-ID"]
options.sessionReplay.networkResponseHeaders = ["Content-Type", "X-Request-ID"]
}Keep request and response bodies disabled unless you have explicitly reviewed them for sensitive data.
Exclude views from subtree traversal
For views that cause crashes or performance issues during replay capture:
options.sessionReplay.excludeViewTypeFromSubtreeTraversal("MyProblematicView")
// Force-include a system view normally excluded:
options.sessionReplay.includeViewTypeInSubtreeTraversal("CameraUI.ChromeSwiftUIView")Reducing performance overhead
SentrySDK.start { options in
options.sessionReplay.quality = .low // lower bitrate/resolution
options.sessionReplay.enableFastViewRendering = true // CALayer renderer (faster, less accurate)
}
// Disable entirely on low-power / low-end devices:
if ProcessInfo.processInfo.isLowPowerModeEnabled {
options.sessionReplay.sessionSampleRate = 0.0
options.sessionReplay.onErrorSampleRate = 0.0
}Quality enum values
| Value | Bit Rate | Resolution |
|---|---|---|
.low | ~50 kbps | Reduced |
.medium | Default | Default |
.high | Higher | Full |
---
iOS 26 / Xcode 26 / Liquid Glass
There is version-specific behavior here:
- SDK 8.57.0 through 9.11.x auto-disabled Session Replay on iOS 26 Liquid Glass builds to avoid masking risks.
- SDK 9.12.0+ removed that safeguard after redaction fixes; Session Replay records on iOS 26 again.
- If your app needs to keep replay disabled for Liquid Glass, gate
sessionSampleRateandonErrorSampleRateyourself.
Example manual gate:
var sessionRate: Float = 0.1
var errorRate: Float = 1.0
if #available(iOS 26.0, *) {
let compatibilityMode = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
let xcodeVersion = Int(Bundle.main.object(forInfoDictionaryKey: "DTXcode") as? String ?? "") ?? 0
let liquidGlassActive = xcodeVersion >= 2600 && !compatibilityMode
if liquidGlassActive {
sessionRate = 0
errorRate = 0
}
}
options.sessionReplay.sessionSampleRate = sessionRate
options.sessionReplay.onErrorSampleRate = errorRate---
Performance Overhead (iPhone 14 Pro benchmarks)
| Metric | Without Replay | With Replay |
|---|---|---|
| FPS | 55 | 53 |
| Memory | 102 MB | 121 MB |
| CPU | 4% | 13% |
| Main thread per capture | — | ~25 ms |
| Network bandwidth | — | ~10 KB/s |
iPhone 8 and older: The ~25 ms capture time exceeds the 16.7 ms frame budget, causing scrolling jank. View Renderer V2 (default since v8.50.0) improved from ~155 ms to ~25 ms per capture.
---
Best Practices
- Set
maskAllText = trueandmaskAllImages = true(both default) — only unmask content that is explicitly safe to show - Use
.sentryReplayUnmask()sparingly on known-safe content rather than globally disabling masking - Start with
onErrorSampleRate = 1.0andsessionSampleRate = 0to capture replays only on errors (lowest overhead) - Test masking on real devices — use
SentrySDK.replay.showMaskPreview()in DEBUG builds to verify - Re-test masking on iOS 26+ Liquid Glass after every SDK or Xcode upgrade
Troubleshooting
| Issue | Solution |
|---|---|
| No replays appearing | Verify sessionSampleRate > 0 or onErrorSampleRate > 0; both default to 0 |
| Replay disabled on iOS 26 | SDK 8.57.0–9.11.x auto-disabled Liquid Glass builds; SDK 9.12+ records again unless you gate sample rates yourself |
| PII visible in replay | Verify maskAllText = true and maskAllImages = true; check .sentryReplayUnmask() isn't applied too broadly |
| Scrolling jank during replay | Enable enableFastViewRendering = true; switch to quality = .low; consider disabling on low-end devices |
| Replay stops after 60 minutes | Expected — maximumDuration = 3600 seconds is the default cap |
| Error buffer not uploading | Verify onErrorSampleRate > 0; buffer is only uploaded when SentrySDK.capture(error:) is called |
| App crash during replay capture | Use excludeViewTypeFromSubtreeTraversal for the problematic view type |
| Texture/AsyncDisplayKit views not masked | Access .view on the node: SentrySDK.replay.maskView(view: myNode.view) |
Tracing — Sentry Cocoa SDK
Minimum SDK: sentry-cocoa v7.0.0+SwiftUI instrumentation stable: v8.17.0+
File I/O manual tracing extensions: v8.48.0+
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
tracesSampleRate | Double (0.0–1.0) | nil | Uniform sample rate; mutually exclusive with tracesSampler |
tracesSampler | (SentrySamplingContext) -> NSNumber | nil | Dynamic per-transaction sampling; overrides tracesSampleRate |
enableAutoPerformanceTracing | Bool | true | Master switch for all automatic instrumentation |
enableUIViewControllerTracing | Bool | true | UIViewController lifecycle spans |
enableUserInteractionTracing | Bool | true | Transactions for UIControl tap/click events |
enableNetworkTracking | Bool | true | URLSession HTTP request spans |
enableFileIOTracing | Bool | true | NSData / FileManager file I/O spans |
enableCoreDataTracing | Bool | true | Core Data fetch/save spans |
enableTimeToFullDisplayTracing | Bool | false | TTFD span; requires SentrySDK.reportFullyDisplayed() |
enablePreWarmedAppStartTracing | Bool | true | Prewarmed cold/warm start tracing (iOS 15+) |
enableDataSwizzling | Bool | true | NSData swizzling for automatic file I/O tracing |
enableFileManagerSwizzling | Bool | false | NSFileManager swizzling (experimental; needed for iOS 18+) |
tracePropagationTargets | [Any] | all requests | Strings or NSRegularExpression values for outgoing distributed trace headers |
enableSwizzling | Bool | true | Master switch for method swizzling (required by several auto-instrumentation features) |
strictTraceContinuation | Bool | false | Only continue an incoming trace when orgId matches; prevents cross-org trace continuation (SDK 9.x+) |
orgId | String? | nil | Organization ID for strict trace validation; auto-parsed from DSN host (e.g. o123.ingest.sentry.io → "123") if not set explicitly |
Code Examples
Basic tracing setup
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 1.0 // 100% in dev; lower for production (e.g., 0.2)
}Dynamic sampling with tracesSampler
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampler = { context in
// Never sample next-launch transactions
if context.isForNextAppLaunch { return 0 }
// Always sample checkout
if context.customSamplingContext?["flow"] as? String == "checkout" { return 1.0 }
// Default: 25%
return 0.25
}
}Custom transaction with child spans
import Sentry
func performCheckout() {
let transaction = SentrySDK.startTransaction(
name: "checkout",
operation: "perform-checkout",
bindToScope: true // makes it accessible via SentrySDK.span
)
let validationSpan = transaction.startChild(
operation: "validation",
description: "validating shopping cart"
)
validateShoppingCart()
validationSpan.finish()
let processSpan = transaction.startChild(
operation: "process",
description: "processing payment"
)
do {
try processPayment()
processSpan.finish()
transaction.finish()
} catch {
SentrySDK.capture(error: error)
processSpan.finish(status: .internalError)
transaction.finish(status: .internalError)
}
}Accessing the scope-bound span from a called function
func processPayment() {
// Grab the transaction bound to scope (or start a standalone one)
let span = SentrySDK.span ?? SentrySDK.startTransaction(
name: "processPayment",
operation: "task"
)
let child = span.startChild(operation: "payment.gateway")
defer { child.finish() }
// payment logic...
}Setting data attributes on transactions and spans
let transaction = SentrySDK.startTransaction(name: "sync", operation: "task")
transaction.setData(value: "incremental", key: "sync.type")
transaction.setData(value: 42, key: "sync.item_count")
transaction.setData(value: true, key: "sync.force")
transaction.setData(value: ["a", "b"], key: "sync.queues")
let span = transaction.startChild(operation: "db.fetch")
span.setData(value: "orders", key: "db.table")
span.finish()
transaction.finish()Custom performance measurements
let span = SentrySDK.span
span?.setMeasurement(name: "memory_used",
value: 64,
unit: MeasurementUnitInformation.megabyte)
span?.setMeasurement(name: "profile_load_time",
value: 1.3,
unit: MeasurementUnitDuration.second)
span?.setMeasurement(name: "items_processed", value: 128)---
Automatic Instrumentation
All features are enabled once tracesSampleRate > 0 (or tracesSampler is set). Disable all at once with enableAutoPerformanceTracing = false.
App Start Tracing
Platforms: iOS, tvOS, Mac Catalyst
Measures process creation → first rendered frame. Start type classifications:
| Type | Description |
|---|---|
cold | First launch, post-reboot, or post-update |
warm | Any other process creation |
cold.prewarmed | Cold start with OS pre-warm (iOS 15+) |
warm.prewarmed | Warm start with OS pre-warm (iOS 15+) |
Child spans produced (sequential):
| Span | Measures |
|---|---|
| Pre Runtime Init | Process start → runtime init |
| Runtime Init to Pre Main Initializers | Runtime init → pre-main setup |
| UIKit Init | Pre-main → Sentry SDK startup |
| Application Init | SDK startup → didFinishLaunchingNotification |
| Initial Frame Render | didFinishLaunchingNotification → first CADisplayLink callback (v9+) |
Warning: If more than 180 seconds (3 minutes) elapse between transaction start and app-start end, app start spans are not attached to avoid misassociation. This limit is skipped when standalone app start tracing is enabled.
Standalone App Start Tracing (Experimental, 9.15.0+)
Platforms: iOS, tvOS, visionOS
By default, app start transactions are attached to the first UIViewController transaction. Standalone app start tracing creates a dedicated transaction just for app launch, independent of any UIViewController.
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 1.0
options.experimental.enableStandaloneAppStartTracing = true
}Extending app launch beyond the default end point (9.15.0+):
For apps with initial data loading or authentication flows, you can extend the app start measurement:
// UIKit — in AppDelegate
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 1.0
options.experimental.enableStandaloneAppStartTracing = true
}
SentrySDK.extendAppLaunch() // call before didFinishLaunchingNotification is posted
return true
}
// Later, when your app is fully ready:
func onInitialDataLoaded() {
SentrySDK.finishExtendedAppLaunch()
}// SwiftUI — in App constructor
@main
struct MyApp: App {
init() {
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 1.0
options.experimental.enableStandaloneAppStartTracing = true
}
SentrySDK.extendAppLaunch()
}
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
// After initial data loads:
SentrySDK.finishExtendedAppLaunch()
}
}
}
}Note:extendAppLaunch()must be called afterSentrySDK.start()but before thedidFinishLaunchingNotificationis posted. If not called, or if the extended launch was already finished,finishExtendedAppLaunch()does nothing.
URLSession Network Tracking
Platforms: All Note: NSURLConnection is not supported — only NSURLSession.
Automatically adds HTTP spans to any active scope-bound transaction.
// Disable
options.enableNetworkTracking = falseUIViewController Lifecycle Tracing
Platforms: iOS, tvOS, Mac Catalyst Not available for: SwiftUI (use SentryTracedView instead)
- Transaction operation:
ui.load - Transaction name:
Your_App.MainViewController - Auto-generated child spans:
loadView,viewDidLoad,viewWillAppear,viewDidAppear - Time to Initial Display (TTID) span:
ui.load.initial-display
// Include framework view controllers
options.add(inAppInclude: "MyFramework")
// Exclude specific view controllers
options.swizzleClassNameExcludes = ["MyModalViewController"]
// Disable entirely
options.enableUIViewControllerTracing = falseTime to Full Display (TTFD)
// Enable globally
options.enableTimeToFullDisplayTracing = true
// In your view controller, signal when async content is fully loaded:
SentrySDK.reportFullyDisplayed()TTFD span status:
| Scenario | Status |
|---|---|
reportFullyDisplayed() called | .ok |
| Not finished within 30 seconds | .deadlineExceeded; duration = TTID duration |
| Called before view appears | Reported time = TTID time |
SwiftUI Instrumentation
For SDK 9.4.1+, SwiftUI tracing APIs are available from the main Sentry module. The SentrySwiftUI product/module still exists as a deprecated re-export for older setups.
import Sentry
// Option 1: wrapper
var body: some View {
SentryTracedView("My Awesome Screen") {
List { /* content */ }
}
}
// Option 2: modifier
var body: some View {
List { /* content */ }
.sentryTrace("My Awesome Screen")
}
// With TTFD (v8.44.0+)
SentryTracedView("Content", waitForFullDisplay: true) {
VStack { /* async content */ }
.onAppear {
Task {
data = await loadData()
SentrySDK.reportFullyDisplayed()
}
}
}If maintaining an older project that already uses the SentrySwiftUI product, import SentrySwiftUI still works in SDK 9.x but should be migrated to import Sentry before the next major version. Source-build SentrySPM projects may expose the module as SentrySwift; verify imports against the selected product.
Slow & Frozen Frames
Platforms: iOS, tvOS, Mac Catalyst Tracked automatically during any active transaction. Appears as Mobile Vitals in the Sentry Performance UI.
| Threshold | Classification |
|---|---|
| > 16 ms per frame | Slow frame |
| > 700 ms per frame | Frozen frame |
User Interaction Tracing
Platforms: iOS, tvOS, Mac Catalyst Not available for: SwiftUI
Creates a transaction for every UIControl tap/click.
- Transaction operation:
ui.actionorui.action.click - Transaction name:
YourApp_LoginViewController.loginButton idleTimeout: 3000 ms — transaction finishes if no child spans within 3 seconds- Transactions with no child spans are dropped
// Create child spans inside a tap handler:
func onLoginTapped() {
let span = SentrySDK.span
let child = span?.startChild(operation: "loadUserProfile")
// ... work ...
child?.finish()
}
// Disable
options.enableUserInteractionTracing = falseFile I/O Tracing (NSData)
Platforms: All Tracks NSData read/write operations as spans.
options.enableFileIOTracing = true // default
// iOS 18+ / macOS 15+: NSFileManager no longer backed by NSData
// Enable experimental NSFileManager swizzling:
options.enableFileManagerSwizzling = true // experimental, v9.0.0+Manual tracing extensions (v8.48.0+) — only create spans when an active transaction exists:
// Data read/write
let data = try Data(contentsOfWithSentryTracing: url)
try data.writeWithSentryTracing(to: url)
// FileManager
let fm = FileManager.default
fm.createFileWithSentryTracing(atPath: path, contents: data)
try fm.moveItemWithSentryTracing(at: src, to: dst)
try fm.copyItemWithSentryTracing(at: src, to: dst)
try fm.removeItemWithSentryTracing(at: url)Span operations created:
| Method | Span Op |
|---|---|
Data.init(contentsOf:) | file.read |
data.write(to:) / createFile | file.write |
moveItem | file.rename |
copyItem | file.copy |
removeItem | file.delete |
Core Data Tracing
Platforms: All Instruments NSManagedObjectContext fetch and save operations.
options.enableCoreDataTracing = true // default
// Disable
options.enableCoreDataTracing = false---
Distributed Tracing
Sentry injects two headers into outgoing NSURLSession requests when the host matches tracePropagationTargets:
| Header | Purpose |
|---|---|
sentry-trace | Carries trace ID, span ID, and sampled flag |
baggage | Carries Dynamic Sampling Context key-value pairs |
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 1.0
// Only propagate to your own backend (default: all requests)
options.tracePropagationTargets = [
"api.myapp.com",
".*\\.myapp\\.com" // regex supported
]
// Also add W3C traceparent header (requires sentry-cocoa 9.0.0+)
options.enablePropagateTraceparent = true
}`enablePropagateTraceparent` requires sentry-cocoa 9.0.0+. It is not available in 8.x.
>
Warning: Both headers must be included in CORS allowlists and must not be blocked by proxies or firewalls.
Strict Trace Continuation (SDK 9.x+)
Enable strictTraceContinuation to reject incoming traces from other Sentry organizations. When enabled, the SDK validates that the sentry-trace header's organization ID matches your DSN's organization before continuing the trace:
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.tracesSampleRate = 1.0
// Only accept traces from your own Sentry organization
options.strictTraceContinuation = true
// orgId is auto-parsed from DSN host; override only if needed:
// options.orgId = "12345"
}---
Platform Support Matrix
| Feature | iOS | tvOS | macOS | Mac Catalyst |
|---|---|---|---|---|
tracesSampleRate | Yes | Yes | Yes | Yes |
| App Start Tracing | Yes | Yes | No | Yes |
| UIViewController Lifecycle | Yes | Yes | No | Yes |
| TTID / TTFD | Yes | Yes | No | Yes |
| Slow & Frozen Frames | Yes | Yes | No | Yes |
| Network Tracking (URLSession) | Yes | Yes | Yes | Yes |
| File I/O Tracing | Yes | Yes | Yes | Yes |
| Core Data Tracing | Yes | Yes | Yes | Yes |
| User Interaction Tracing | Yes | Yes | No | Yes |
SwiftUI (SentryTracedView) | Yes (13+) | Yes | Yes | Yes |
| Prewarmed App Start | Yes (15+) | No | No | No |
| NSFileManager Swizzling | Yes (18+) | Yes (18+) | Yes (15+) | Yes |
---
Best Practices
- Start with
tracesSampleRate = 1.0in development; lower to0.1–0.2in production - Use
tracesSampler(nottracesSampleRate) for route-specific or user-tier-based sampling - Use
bindToScope: truewhen starting a transaction so child spans created anywhere in the call stack are automatically linked - Always
finish()spans — unfinished spans are silently dropped - Use
SentryTracedViewor.sentryTrace()from the mainSentrymodule for SwiftUI screens on SDK 9.4.1+ (UIViewController tracing doesn't apply) - Call
SentrySDK.reportFullyDisplayed()only after your async data has been rendered — not just loaded - Avoid setting
tracePropagationTargets = [".*"]in production if you make requests to third-party services not using Sentry
Troubleshooting
| Issue | Solution |
|---|---|
| No transactions appearing | Ensure tracesSampleRate > 0 or tracesSampler returns > 0 |
| Spans missing from transactions | Ensure span.finish() is called; check bindToScope: true for cross-function spans |
| App start spans not attached | Gap between transaction start and app-start end exceeded 180 seconds; check slow initialization or enable standalone app start tracing |
| UIViewController tracing missing | Verify enableSwizzling = true; check class is not in swizzleClassNameExcludes |
| Network spans not appearing | Requires active scope-bound transaction; verify enableNetworkTracking = true and enableSwizzling = true |
| Distributed trace not linking to backend | Propagate both sentry-trace AND baggage headers; add them to CORS allowlist |
| File I/O spans missing on iOS 18+ | Enable enableFileManagerSwizzling = true (experimental) or use manual WithSentryTracing extensions |
SentryTracedView not available | SDK 9.4.1+: use import Sentry with the Sentry product; older/deprecated setups may need the SentrySwiftUI product |
| High-cardinality transaction names | UIViewController transactions use class name — expected; custom transactions should use stable names |
User Feedback — Sentry Cocoa SDK
Minimum SDK: sentry-cocoa v8.46.0+Self-hosted Sentry server: 24.4.2+
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
configureUserFeedback | ((SentryUserFeedbackConfiguration) -> Void)? | nil | Configure the feedback widget and form |
autoInject | Bool | true | Auto-show floating "Report a Bug" button |
useShakeGesture | Bool | false | Open the form on device shake |
showFormForScreenshots | Bool | false | Auto-open form when user takes a screenshot |
animations | Bool | true | Enable present/dismiss animations |
useSentryUser | Bool | true | Pre-fill name/email from SentrySDK.setUser(...) |
isNameRequired | Bool | false | Require name field before submission |
isEmailRequired | Bool | false | Require email field before submission |
showName | Bool | true | Show the name field |
showEmail | Bool | true | Show the email field |
Code Examples
Basic widget setup (auto-inject mode)
By default (autoInject = true), the SDK injects a floating "Report a Bug" button:
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.configureUserFeedback = { config in
config.onSubmitSuccess = { data in
// data keys: "message", "name", "email", "attachments"
print("Feedback submitted: \(data["message"] ?? "")")
}
config.onSubmitError = { error in
print("Submission failed: \(error)")
}
}
}Programmatic widget control
// Show the floating widget button programmatically
SentrySDK.feedback.showWidget()
// Hide the widget button
SentrySDK.feedback.hideWidget()SentrySDK.feedbackis of typeSentryFeedbackAPI. There is noshowUserFeedbackForm()method — always useshowWidget()to trigger the UI.
SwiftUI integration
The feedback widget is UIKit-based. In a SwiftUI app, inject it via .onAppear on the root view:
import SwiftUI
import Sentry
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
SentrySDK.feedback.showWidget()
}
}
}
}Or via a UISceneDelegate:
func sceneDidBecomeActive(_ scene: UIScene) {
SentrySDK.feedback.showWidget()
}Bind to a custom UIButton
SentrySDK.start { options in
options.configureUserFeedback = { config in
config.customButton = myButton // tapping this button opens the form
}
}Trigger via shake gesture or screenshot
options.configureUserFeedback = { config in
config.useShakeGesture = true // shake to open form
config.showFormForScreenshots = true // auto-open after screenshot
}Programmatic feedback capture (no widget, custom UI)
Use SentrySDK.capture(feedback:) to send feedback from your own UI without any Sentry widget:
import Sentry
SentrySDK.capture(feedback: .init(
message: "The checkout button doesn't respond after adding a promo code.",
name: "Jane Doe",
email: "jane@example.org",
source: .custom,
attachments: nil // pass [Attachment] to include screenshots or files; nil for none
))Link feedback to an error event
To associate a feedback submission with a specific Sentry issue, capture the error first and use the resulting event ID:
let eventId = SentrySDK.capture(error: error)
SentrySDK.capture(feedback: .init(
message: "App crashed on the checkout screen.",
name: "User",
email: "user@example.com",
associatedEventId: eventId
))Form customisation
options.configureUserFeedback = { config in
config.configureForm = { form in
form.formTitle = "Share Your Feedback"
form.submitButtonLabel = "Send Feedback"
form.cancelButtonLabel = "Never Mind"
form.messagePlaceholder = "What went wrong? What did you expect?"
form.isNameRequired = true
form.isEmailRequired = true
form.showBranding = false
form.useSentryUser = true // pre-fill from SentrySDK.setUser(...)
}
}Widget placement and labels
options.configureUserFeedback = { config in
config.configureWidget = { widget in
widget.labelText = "Give Feedback"
widget.location = [.bottom, .trailing] // anchor edges
widget.showIcon = true
widget.layoutUIOffset = UIOffset(horizontal: -16, vertical: -32)
}
}Theme customisation
options.configureUserFeedback = { config in
config.configureTheme = { theme in
theme.submitBackground = .systemBlue
theme.fontFamily = "SF Pro Rounded"
}
config.configureDarkTheme = { theme in
theme.background = .black
theme.submitBackground = .systemPurple
}
}Theme properties:
| Property | Light Default | Dark Default |
|---|---|---|
background | rgb(255,255,255) | rgb(41,35,47) |
foreground | rgb(43,34,51) | rgb(235,230,239) |
submitBackground | rgb(88,74,192) | rgb(88,74,192) |
submitForeground | rgb(255,255,255) | rgb(255,255,255) |
errorColor | rgb(223,51,56) | rgb(245,84,89) |
font | UIFontTextStyleCallout | — |
headerFont | UIFontTextStyleTitle1 | — |
fontFamily | nil (system font) | — |
Session Replay integration
When a user opens the feedback form and Session Replay is enabled, the SDK automatically buffers up to 30 seconds of the session. On submission, that replay clip is sent alongside the feedback event — no extra configuration needed.
Full configuration example
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.configureUserFeedback = { config in
config.showFormForScreenshots = true
config.useShakeGesture = false
config.animations = true
config.configureForm = { form in
form.formTitle = "Report a Bug"
form.submitButtonLabel = "Send Bug Report"
form.isNameRequired = true
form.isEmailRequired = false
form.showBranding = false
form.useSentryUser = true
}
config.configureWidget = { widget in
widget.labelText = "Report a Bug"
widget.location = [.bottom, .trailing]
widget.autoInject = true
}
config.configureTheme = { theme in
theme.submitBackground = .systemBlue
}
config.configureDarkTheme = { theme in
theme.background = .black
}
config.onFormOpen = { print("Feedback form opened") }
config.onFormClose = { print("Feedback form closed") }
config.onSubmitSuccess = { data in
print("Feedback: \(data["message"] ?? "")")
}
config.onSubmitError = { error in
print("Submission failed: \(error)")
}
}
}SentryUserFeedbackWidgetConfiguration Reference
| Property | Type | Default |
|---|---|---|
autoInject | Bool | true |
location | [NSDirectionalRectEdge] | [.bottom, .trailing] |
layoutUIOffset | UIOffset | .zero |
windowLevel | UIWindow.Level | normal + 1 |
showIcon | Bool | true |
labelText | String? | "Report a Bug" |
widgetAccessibilityLabel | String | labelText |
customButton is on SentryUserFeedbackConfiguration, not SentryUserFeedbackWidgetConfiguration. If it is set, configureWidget is ignored.
SentryUserFeedbackFormConfiguration Reference
| Property | Type | Default |
|---|---|---|
formTitle | String | "Report a Bug" |
showBranding | Bool | true |
submitButtonLabel | String | "Send Bug Report" |
cancelButtonLabel | String | "Cancel" |
messagePlaceholder | String | "What's the bug? What did you expect?" |
isNameRequired | Bool | false |
showName | Bool | true |
nameLabel | String | "Name" |
namePlaceholder | String | "Your Name" |
isEmailRequired | Bool | false |
showEmail | Bool | true |
emailLabel | String | "Email" |
emailPlaceholder | String | "your.email@example.org" |
useSentryUser | Bool | true |
Best Practices
- Set
useSentryUser = true(default) and callSentrySDK.setUser(...)so the form pre-fills name and email — reduces friction - Enable
showFormForScreenshots = true— users often take screenshots when something goes wrong; it's a natural trigger - Disable
autoInjectfor SwiftUI, or setconfig.customButton = myButtonto bind the form to your own UIKit button - Use
config.onSubmitSuccessto show a native confirmation (toast/alert) after the Sentry form dismisses - If collecting feedback from a known event ID, use
associatedEventIdto link the feedback to the specific issue in Sentry - Add
tagson the configuration to automatically tag all feedback events with context (e.g., app version, screen name)
Troubleshooting
| Issue | Solution |
|---|---|
| Widget not appearing | Verify autoInject = true; in SwiftUI apps call SentrySDK.feedback.showWidget() in .onAppear |
| Form not opening on shake | Set useShakeGesture = true; verify the device is not muted (shake may be overridden by system) |
| Name/email fields not pre-filled | Ensure useSentryUser = true (default) and SentrySDK.setUser(...) was called before the form opens |
| Submission error | Check network connectivity; verify DSN is correct; inspect onSubmitError callback for the error |
| Feedback not linked to an issue | Use associatedEventId parameter with the event ID from SentrySDK.capture(error:) |
| Screenshot not attached | Wrap PNG Data in an Attachment and pass via SentryFeedback.init(attachments:); ensure the data is non-nil and valid |
| Widget floating behind other UI | Raise widget.windowLevel above your custom windows |
configureUserFeedback not available | Requires v8.46.0+; check your SPM/CocoaPods version |
Related skills
How it compares
Choose sentry-cocoa-sdk over generic Sentry setup skills when the target is a native Swift app on iOS, macOS, or watchOS rather than web or server runtimes.
FAQ
What is sentry-cocoa-sdk?
Full Sentry SDK setup for Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). Use when asked to "add Sentry to iOS", "add Sentry to Swift", "install sentry-cocoa", or configure
When should I use sentry-cocoa-sdk?
Full Sentry SDK setup for Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). Use when asked to "add Sentry to iOS", "add Sentry to Swift", "install sentry-cocoa", or configure
Is sentry-cocoa-sdk safe to install?
Review the Security Audits panel on this page before production use.