How I Read a Crash Report
Crash reports are the most valuable data you get after a release. But a report nobody reads is worthless — knowing what order to look at things in, and what is genuinely urgent, is a skill of its own.
A user will not tell you the app crashed. They delete it and never open it again. That is why crash reports are the most valuable data you have after a release — and most developers either never look at them or look at them in the wrong order.
Below is the routine I follow after releasing my own apps.
1. First, make sure the report is readable
This is the step that makes everything else pointless if you skip it. A release build is minified and obfuscated, and the crash traces come back that way too. If what you have looks like this, you cannot read the report:
at a.b.c.d(Unknown Source:2)
at a.b.e.f(Unknown Source:11)
Fixing this means uploading the symbol files: on Android the mapping file (the R8/ProGuard output), on iOS the dSYM. Both are generated separately for every build and are specific to that build.
If you leave the symbol file upload as a manual step, sooner or later it gets forgotten and every report from that build is garbage. Put the upload step inside your release script.
2. Look at affected users, not at the crash count
This is the most common mistake in prioritization. An issue showing "1,240 crashes" in the dashboard may all be coming from one user's device stuck in a loop. Another issue showing "38 crashes" may be hitting 38 separate users.
The order I go through:
- Number of affected users — how many people got hurt?
- Crash-free user rate — the overall health of the release.
- New or old — a crash that arrived with the latest release is always a priority.
- Where it happens — a crash at launch is far more serious than one on a screen buried deep in the app.
A crash at launch beats everything else. It means the user cannot use the app at all; you halt the rollout of that release and ship a fix.
3. Look at the clustering: what do they have in common?
The distribution table next to a crash report sometimes tells you more than the stack trace does. The breakdowns I check:
| Breakdown | What it tells you |
|---|---|
| On a single device model | A hardware-specific problem — camera, memory, screen ratio |
| On a single OS version | An API behavior change |
| Only in one country | Caused by language, time zone or region settings |
| Only on low-memory devices | Memory pressure, loading large images |
| While the app is in the background | A lifecycle issue, the process being killed and restored |
The "only in one country" row has a special place for me — that is exactly how I once found out that a bug was caused entirely by the device's language.
4. Read the stack trace from your own code, not from the top
The top of a stack trace is usually a system library and tells you nothing. Find the first line where your own package name appears — that is where the error was triggered.
Then look at how execution got to that line. The cause of a crash is usually not the line that crashed, but the wrong value that reached it.
5. If you can't reproduce it, leave breadcrumbs
Some crashes simply refuse to reproduce locally. When that happens, leaving breadcrumbs in the app gets you an answer faster than changing code on a guess.
// Add the steps the user went through to the report
Crashlytics.log("Sipariş ekranı açıldı, id=" + siparisId)
Crashlytics.setCustomKey("odeme_yontemi", secilenYontem)
In the next crash report those breadcrumbs show up next to the stack trace, and the path the user took becomes visible. You have to be careful not to write personal data — things like email, name or location should never go into these logs.
6. Record the errors that don't crash, too
There are errors that don't crash the app but do break it: a failed network request, data that comes back empty, an exception that gets swallowed silently. Sending those to the reporting tool as well makes it much easier to understand why a user is complaining.
try {
veriyiKaydet()
} catch (e: Exception) {
// The app keeps running, but we want to know what happened
Crashlytics.recordException(e)
kullaniciyaHataGoster()
}
My weekly routine
While I have an app live, once a week I check these:
- Has the crash-free user rate dropped compared to last week?
- Is there a new crash that arrived with the latest release?
- What are the top three issues by affected users?
- Does any store review mention a crash?
It is half an hour of work. But when you skip it, you lose users quietly and only figure out months later why the downloads went down.
A crash report is the complaint letter the user never wrote to you. Until you read it, it doesn't count as sent.
- Crashlytics
- Crash analysis
- Debugging
- Post-release
- Quality
Demir Taşdemir
Mobile App & Web Developer
I have been building software since 2018. I have shipped 11 apps on the App Store and Google Play; right now I am working on 6 mobile apps, 1 e-commerce platform and 1 desktop game.
Someone who owns the post-release phase too
Keeping an app alive after it ships is as much a part of the job as shipping it.