In the last segment, you implemented the delegate to receive metrics payloads. In this segment, you’ll implement the 2nd delegate in MXMetricManagerSubscriber to receive app diagnostics.
Those payloads can be delivered to your app immediately if the issue wasn’t a crash, or in the next app launch if it was. Unlike metrics payloads that are sent at most once per day.
In MetricsKitService.swift add this method inside the extension:
public func didReceive(_ payloads: [MXDiagnosticPayload]) {
payloads.forEach { payload in
}
}
Payloads are also sent as a collection.
Each payload can contain those information:
CPU Exception Diagnostics
Disk Write Exception Diagnostics
Hang Diagnostics
App Launch Diagnostics
Crash Diagnostics
They’re all rather similar, and they are all collections too. Start by Crash Diagnostics.
Add the following in the loop:
payload.crashDiagnostics?.forEach { crashDiagnostic in
var crashMessage = ""
if let terminationReason = crashDiagnostic.terminationReason {
crashMessage += "Termination Reason: \(terminationReason)"
}
if let exceptionType = crashDiagnostic.exceptionType {
crashMessage += "\nException Type: \(exceptionType)"
}
if let exceptionCode = crashDiagnostic.exceptionCode {
crashMessage += "\nException Code: \(exceptionCode)"
}
if let stackTrace = String(data: crashDiagnostic.callStackTree.jsonRepresentation(), encoding: .utf8) {
crashMessage += "\nStackTrace: \(stackTrace)"
}
OTelLogs.sendLog(scope: "Crash Diagnostic", message: crashMessage)
}
You’re basically constructing a string to send as part of the log report. Since those values are optionals, you’ll add them only if they exist. The most interesting one is the Stack Trace as you may have already expected.
Before you build and run the app, change the project’s build settings for Debug Information Format for Debug to be DWARF with dSYM File
Set 'Debug Information Format' to be 'DWARF with dSYM File'
Build and run the app on your phone. The build log should show the location of where the dSYM file is generated. The file will be generated in the derived data folder. Open the build log and make sure that All Messages is selected and filter for “dSYM”. The path should be similar to this
Go to that folder in the Finder app, and open the Terminal as you’ll use it shortly.
When the app starts stop the debug session, then launch the app directly from the phone without connecting it to Xcode. Press the red Crash button on the top. This button accesses a far index in store.objects its intentional to crash the app so you can see the crash logs.
Open the app again to receive the diagnostics and the app will automatically send it to Grafana.
Open Logs under Drilldown on Grafana and open the logs for TheMet:
The 'Logs' graph on Grafana showing the crash
If you click on the small eye button on the left of the log, it will open the full crash log message that you built from the app.
The stack trace is impossible to understand in this format. Making it meaningful directly on Grafana is quite complicated and its not worth the trouble. But its good to learn what this means
This is an example of a stack trace entry, aka: Frame:
binaryUUID in a GUID representing the ID of the framework or a binary.
offsetIntoBinaryTextSegment is the memory address where the symbol exists.
subFrames are the nested stack traces. They have the same structure as this one.
binaryName is the name of the binary that contains the address.
Inside the crash log look and find the first entry that has a binary name of TheMet.debug.dylib. You may need to copy the JSON text into new xcode file or in VSCode, and fold the subFrames array to find the frame you need. Its probably the 6th from the top.
Take the value from offsetIntoBinaryTextSegment and convert it to its hex value and run this command in the Terminal:
atos is the command to identify the symbols from a crash report. You pass it the architecture, path of the symbols file and the address to identify, and it will give you the line of code.
The output on terminal should be:
closure #1 in closure #2 in closure #1 in closure #1 in ContentView.body.getter (in TheMet.debug.dylib) (ContentView.swift:66)
Open ContentView.swift on line 66 and you’ll find the reason of the crash:
print(store.objects[50])
You’ll notice that the usual crash message of “index out of bounds” didn’t exist, reading every frame to find its symbol from the terminal isn’t trivial and defies the purpose. But its worth knowing that any crash reporting tool like Crashlytics or Sentry does this for you. This is why they require the dSYM files. Without them, you’ll be seeing crash logs just the same way as you saw on your log.
The final project has the implementation of the other 4 diagnostics and their logs are uploaded in the same format.
You can build your own dashboards to track those diagnostics by sending them as events with an event name so you can filter them the same way as the metrics, or you can filter them with scope_name since they all have different values there. Or you can rely on other tools like Crashlytics, Sentry or many of the others that have large solutions to make your life easier so you can focus on making your apps better, not spend a lot of time and effort to build those tools yourself and end up reinventing the wheel. :]
See forum comments
This content was released on Oct 24 2025. The official support period is 6-months
from this date.
In this segment, you’ll learn about Diagnostic Payloads from Metrics kit and how Symbolification works for stack traces.
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.