Every iOS app crashes. The question is not whether yours will — it is whether you will know about it before your users leave a one-star review. Crash reporting is non-negotiable for any app that ships to the App Store.
We run 27 apps across iOS, macOS, watchOS, and visionOS. Every one of them uses Sentry for crash reporting. We evaluated Firebase Crashlytics before settling on Sentry, and the decision came down to five concrete factors: breadcrumbs, release health monitoring, performance tracing, self-hosted option, and privacy manifest impact. Here is the full comparison.
The Baseline: What Both Tools Do Well
Both Sentry and Crashlytics are mature crash reporting tools. Both capture stack traces, device info, OS version, and app version. Both support dSYM symbolication. Both have free tiers generous enough for most indie apps. If all you need is "tell me when my app crashes and show me the stack trace," either tool works.
The differences emerge when you need more than crash reports.
Breadcrumbs: The Context That Stack Traces Miss
A stack trace tells you where the crash happened. Breadcrumbs tell you what the user did in the 30 seconds before the crash. Sentry captures breadcrumbs automatically — network requests, UI navigation, system events — and lets you add custom breadcrumbs for business logic:
import Sentry
// Automatic breadcrumbs: network, navigation, system
// are captured without any code
// Custom breadcrumb for business context
SentrySDK.addBreadcrumb(Breadcrumb(
level: .info,
category: "purchase",
message: "User tapped Subscribe button",
data: ["product": "yearly", "source": "paywall"]
))
When a crash report arrives, you see the full trail: the user opened the app, navigated to settings, tapped the subscription button, a network request to RevenueCat returned a 429 status, and then the app crashed on the response handler. Without breadcrumbs, you get a stack trace pointing at a line in your network layer with no context about what triggered it.
Crashlytics has custom logging (Crashlytics.log()), but it does not capture UI navigation or network requests automatically. You have to instrument everything manually.
Release Health: Crash-Free Sessions at a Glance
Sentry's release health dashboard shows crash-free session rates per app version. When you ship v1.3.2 and the crash-free rate drops from 99.8% to 97.2%, you see it within hours — not after a wave of one-star reviews.
This is the single most useful metric for deciding whether to expedite a hotfix. Crashlytics has a similar feature called "Crash-Free Users," but Sentry's implementation is more granular: you can filter by release, environment (debug vs. production), and platform (iOS vs. macOS) in one view. Across 27 apps with multiple platform targets each, this filtering is essential.
Performance Monitoring: Traces Beyond Crashes
Sentry includes performance monitoring in the same SDK. You can trace app startup time, screen load duration, and custom spans for operations like database reads or API calls:
let transaction = SentrySDK.startTransaction(
name: "load-expense-list",
operation: "ui.load"
)
let span = transaction.startChild(
operation: "db.query",
description: "Fetch expenses for current month"
)
let expenses = try await store.fetchCurrentMonth()
span.finish()
transaction.finish()
This is not a replacement for Instruments profiling. It is production-grade performance data from real users on real devices. When a user reports that "the app is slow," you can check whether the median screen load time is 200ms (the user's device is old) or 2 seconds (you shipped a regression).
Crashlytics does not include performance monitoring. Firebase Performance Monitoring is a separate SDK with its own initialization, its own dashboard, and its own privacy manifest entries. One SDK vs. two matters when you are managing dependencies across 27 apps through a shared Swift Package.
The Setup: DSN Configuration in a Shared Package
Our entire crash reporting setup lives in AppFoundation, the shared Swift Package that powers all 27 apps. Each app passes its unique Sentry DSN at launch:
import Sentry
// Inside AppFoundation's AppSetup
static func configure(sentryDSN: String, ...) {
SentrySDK.start { options in
options.dsn = sentryDSN
options.tracesSampleRate = 0.2
options.profilesSampleRate = 0.1
options.enableAutoSessionTracking = true
options.attachScreenshot = true
options.enableMetricKit = true
}
}
Each app gets its own Sentry project and its own DSN. This is critical — if multiple apps share a Sentry project, their crash data gets merged and you cannot distinguish which app is crashing. We created 27 separate projects, one per app, with consistent naming (wattora-ios, expense-ios, paramail-macos).
The attachScreenshot option captures a screenshot at the moment of the crash. This is invaluable for UI-related crashes where the stack trace points at a SwiftUI layout issue but you cannot tell which view was on screen.
dSYM Upload: The Step Everyone Forgets
Both Sentry and Crashlytics require dSYM files to symbolicate crash reports. Without dSYMs, you get memory addresses instead of function names — useless for debugging.
The difference is in the upload workflow. Crashlytics integrates dSYM upload into the Xcode build phase with a Run Script. This works well for single-app projects but becomes fragile with XcodeGen-managed projects where build phases are regenerated from YAML.
Sentry provides both a build-phase script and a CLI tool. We use the CLI in our archive pipeline:
# After xcodebuild archive + export
sentry-cli debug-files upload \
--org techconceptsorg \
--project wattora-ios \
--include-sources \
DerivedData/Build/Products/*.dSYM
The --include-sources flag embeds source context into the dSYM upload, so Sentry can show the actual source code around the crash line — not just the function name. This cuts diagnosis time significantly when the crash is in a line you do not immediately recognize.
Filtering Noise: The 80/20 of Crash Reports
A fresh Sentry project for an iOS app will be noisy. System-level crashes, watchdog terminations, and low-memory kills generate events that you cannot fix because they are not your code. Here is how we filter:
SentrySDK.start { options in
options.dsn = sentryDSN
// Ignore common system noise
options.beforeSend = { event in
// Filter out watchdog terminations
if event.exceptions?.first?.type == "watchdog" {
return nil
}
// Filter out known third-party SDK crashes
let frames = event.exceptions?.first?.stacktrace?.frames ?? []
let thirdPartyPrefixes = ["RevenueCat", "TelemetryDeck"]
if frames.contains(where: { frame in
thirdPartyPrefixes.contains(where: {
frame.module?.hasPrefix($0) == true
})
}) {
return nil
}
return event
}
}
Crashlytics has less flexibility here. You can set custom keys and filter in the dashboard, but the beforeSend pattern — where you programmatically decide whether to send an event at all — is a Sentry-specific feature that reduces noise at the source rather than in the UI.
The Privacy Angle: PrivacyInfo.xcprivacy
Since Spring 2024, every app must include a privacy manifest. Both Sentry and Crashlytics bundle their own PrivacyInfo.xcprivacy within the SDK, declaring what data they collect.
Sentry's privacy manifest declares diagnostic data (crash logs, device info) and performance data. It does not declare advertising identifiers, user identifiers, or analytics data. The data categories are minimal: device model, OS version, app version, crash stack traces.
Crashlytics, as part of the Firebase family, shares privacy infrastructure with Firebase Analytics. Even if you only use Crashlytics, the Firebase core SDK declares additional data categories. The practical impact: your App Store privacy nutrition label will list more data collection categories with Firebase than with Sentry alone.
Combined with TelemetryDeck for analytics, the Sentry-only crash reporting stack keeps our privacy label to the absolute minimum: diagnostics data, not linked to identity, not used for tracking.
The Self-Hosted Option
Sentry is open source. You can self-host the entire platform on your own infrastructure. We use the cloud-hosted version, but the self-hosted option is a meaningful differentiator for teams with strict data residency requirements or for organizations that cannot send crash data to third-party servers.
Crashlytics is a Google service with no self-hosted option. Your crash data goes to Google's servers. For most indie developers this is fine, but it is a constraint worth knowing about.
Comparison Table
| Sentry | Firebase Crashlytics | |
|---|---|---|
| Automatic breadcrumbs | Yes (network, UI, system) | Manual logging only |
| Performance monitoring | Built-in (same SDK) | Separate SDK (Firebase Performance) |
| Release health | Crash-free sessions + adoption | Crash-free users |
| Screenshot on crash | Yes (opt-in) | No |
| beforeSend filtering | Yes (programmatic) | Limited (dashboard filters) |
| Self-hosted option | Yes (open source) | No |
| Privacy manifest impact | Diagnostics only | Diagnostics + Firebase core |
| Source context in reports | Yes (with --include-sources) | No |
| Swift-native SDK | Yes (SPM) | SPM supported, Obj-C core |
| Free tier | 5K errors, 10K transactions/mo | Unlimited crash reports |
When to Use Crashlytics Instead
Crashlytics is the right choice when:
- You already use Firebase for authentication, Firestore, or remote config. Adding Crashlytics is one line of code.
- You need unlimited free crash reports. Sentry's free tier caps at 5,000 errors per month. For a high-volume consumer app, this fills up fast.
- You do not need performance monitoring or breadcrumbs. If all you want is basic crash reports, Crashlytics is simpler.
- Your app is cross-platform with Android. Crashlytics has deeper Android integration than Sentry.
Key Takeaways
- Breadcrumbs change how you debug. Automatic capture of network requests and UI navigation gives you the "why" behind every crash, not just the "where."
- One SDK beats two. Sentry combines crash reporting, performance monitoring, and profiling in a single dependency. Fewer SDKs means fewer privacy manifest entries and simpler dependency management.
- dSYM upload is not optional. Unsymbolicated crash reports are useless. Automate the upload in your archive pipeline with
sentry-cli— do not rely on remembering to upload manually. - Filter noise at the source. Use
beforeSendto drop system crashes and third-party SDK crashes before they hit your dashboard. Otherwise you will spend more time triaging noise than fixing bugs. - Privacy manifest matters at scale. Across 27 apps, every additional data category declaration is multiplied. Sentry + TelemetryDeck keeps the privacy label minimal.