Skip to content

Commit 7b86079

Browse files
Merge pull request #2562 from DataDog/maxep/RUM-12420/kscrash-report-diagnostics
RUM-12420 Add KSCrash report diagnostic Co-authored-by: maxep <[email protected]>
2 parents 5125313 + 1e2dc64 commit 7b86079

File tree

4 files changed

+361
-1
lines changed

4 files changed

+361
-1
lines changed

Datadog/Datadog.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,10 @@
12481248
D2268AE22EB4F58700875E44 /* DatadogMinifyFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2268AE02EB4F58700875E44 /* DatadogMinifyFilter.swift */; };
12491249
D2268AE42EB4F5CA00875E44 /* DatadogMinifyFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2268AE32EB4F5CA00875E44 /* DatadogMinifyFilterTests.swift */; };
12501250
D2268AE52EB4F5CA00875E44 /* DatadogMinifyFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2268AE32EB4F5CA00875E44 /* DatadogMinifyFilterTests.swift */; };
1251+
D2268AE72EB50F4C00875E44 /* DatadogDiagnosticFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2268AE62EB50F4C00875E44 /* DatadogDiagnosticFilter.swift */; };
1252+
D2268AE82EB50F4C00875E44 /* DatadogDiagnosticFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2268AE62EB50F4C00875E44 /* DatadogDiagnosticFilter.swift */; };
1253+
D2268AEA2EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2268AE92EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift */; };
1254+
D2268AEB2EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2268AE92EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift */; };
12511255
D22743D829DEB8B4001A7EF9 /* VitalInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3FC3C3B2653A97700DEED9E /* VitalInfoTests.swift */; };
12521256
D22743D929DEB8B4001A7EF9 /* VitalInfoSamplerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2EF44E2694FA14008A7DAE /* VitalInfoSamplerTests.swift */; };
12531257
D22743DA29DEB8B4001A7EF9 /* VitalMemoryReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BBBCBB265E71D100943419 /* VitalMemoryReaderTests.swift */; };
@@ -3332,6 +3336,8 @@
33323336
D2268ADD2EB4EC6700875E44 /* DatadogTypeSafeFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogTypeSafeFilterTests.swift; sourceTree = "<group>"; };
33333337
D2268AE02EB4F58700875E44 /* DatadogMinifyFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogMinifyFilter.swift; sourceTree = "<group>"; };
33343338
D2268AE32EB4F5CA00875E44 /* DatadogMinifyFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogMinifyFilterTests.swift; sourceTree = "<group>"; };
3339+
D2268AE62EB50F4C00875E44 /* DatadogDiagnosticFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogDiagnosticFilter.swift; sourceTree = "<group>"; };
3340+
D2268AE92EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogDiagnosticFilterTests.swift; sourceTree = "<group>"; };
33353341
D22789352D64A0D3007E9DB0 /* UploadQualityMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadQualityMetric.swift; sourceTree = "<group>"; };
33363342
D227A0A32C7622EA00C83324 /* BenchmarkProfiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkProfiler.swift; sourceTree = "<group>"; };
33373343
D22C1F5B271484B400922024 /* LogEventMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEventMapper.swift; sourceTree = "<group>"; };
@@ -6547,6 +6553,7 @@
65476553
D2268AD72EB4DAC400875E44 /* AnyCrashReport.swift */,
65486554
D2268AD12EB4DA4100875E44 /* DatadogTypeSafeFilter.swift */,
65496555
D2268AE02EB4F58700875E44 /* DatadogMinifyFilter.swift */,
6556+
D2268AE62EB50F4C00875E44 /* DatadogDiagnosticFilter.swift */,
65506557
);
65516558
path = KSCrashIntegration;
65526559
sourceTree = "<group>";
@@ -6558,6 +6565,7 @@
65586565
D2268ADA2EB4E69C00875E44 /* CrashFieldDictionaryTests.swift */,
65596566
D2268ADD2EB4EC6700875E44 /* DatadogTypeSafeFilterTests.swift */,
65606567
D2268AE32EB4F5CA00875E44 /* DatadogMinifyFilterTests.swift */,
6568+
D2268AE92EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift */,
65616569
);
65626570
path = KSCrashIntegration;
65636571
sourceTree = "<group>";
@@ -9401,6 +9409,7 @@
94019409
612556BB268DD9BF002BCE74 /* DDCrashReportExporter.swift in Sources */,
94029410
61FDBA1326971953001D9D43 /* CrashReportMinifier.swift in Sources */,
94039411
D214DA8329DF2D5E004D0AE8 /* CrashReporting.swift in Sources */,
9412+
D2268AE72EB50F4C00875E44 /* DatadogDiagnosticFilter.swift in Sources */,
94049413
D2268AD82EB4DAC400875E44 /* AnyCrashReport.swift in Sources */,
94059414
61F2728B25C9561A00D54BF8 /* PLCrashReporterIntegration.swift in Sources */,
94069415
D214DA8129DF2D5E004D0AE8 /* CrashReportingPlugin.swift in Sources */,
@@ -9424,6 +9433,7 @@
94249433
D2268ADF2EB4EC6700875E44 /* DatadogTypeSafeFilterTests.swift in Sources */,
94259434
D2268ADC2EB4E69C00875E44 /* CrashFieldDictionaryTests.swift in Sources */,
94269435
D243BBC0276C9D640019C857 /* PLCrashReporterIntegrationTests.swift in Sources */,
9436+
D2268AEB2EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift in Sources */,
94279437
61FDBA15269722B4001D9D43 /* CrashReportInfoMinifierTests.swift in Sources */,
94289438
D22689C82EB2151F00875E44 /* KSCrashPluginTests.swift in Sources */,
94299439
61E95D882695C00200EA3115 /* DDCrashReportExporterTests.swift in Sources */,
@@ -10617,6 +10627,7 @@
1061710627
D2CB6FC127C5348200A62B57 /* DDCrashReportExporter.swift in Sources */,
1061810628
D2CB6FC227C5348200A62B57 /* CrashReportMinifier.swift in Sources */,
1061910629
D214DA8429DF2D5E004D0AE8 /* CrashReporting.swift in Sources */,
10630+
D2268AE82EB50F4C00875E44 /* DatadogDiagnosticFilter.swift in Sources */,
1062010631
D2268AD92EB4DAC400875E44 /* AnyCrashReport.swift in Sources */,
1062110632
D2CB6FC327C5348200A62B57 /* PLCrashReporterIntegration.swift in Sources */,
1062210633
D214DA8229DF2D5E004D0AE8 /* CrashReportingPlugin.swift in Sources */,
@@ -10640,6 +10651,7 @@
1064010651
D2268ADE2EB4EC6700875E44 /* DatadogTypeSafeFilterTests.swift in Sources */,
1064110652
D2268ADB2EB4E69C00875E44 /* CrashFieldDictionaryTests.swift in Sources */,
1064210653
D2CB6FDC27C5352300A62B57 /* PLCrashReporterIntegrationTests.swift in Sources */,
10654+
D2268AEA2EB50F7F00875E44 /* DatadogDiagnosticFilterTests.swift in Sources */,
1064310655
D2CB6FDD27C5352300A62B57 /* CrashReportInfoMinifierTests.swift in Sources */,
1064410656
D22689C72EB2151F00875E44 /* KSCrashPluginTests.swift in Sources */,
1064510657
D2CB6FDE27C5352300A62B57 /* DDCrashReportExporterTests.swift in Sources */,
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2019-Present Datadog, Inc.
5+
*/
6+
7+
import Foundation
8+
9+
// swiftlint:disable duplicate_imports
10+
#if COCOAPODS
11+
import KSCrash
12+
#elseif swift(>=6.0)
13+
internal import KSCrashRecording
14+
#else
15+
@_implementationOnly import KSCrashRecording
16+
#endif
17+
// swiftlint:enable duplicate_imports
18+
19+
/// A KSCrash filter that generates human-readable diagnostic messages for crash reports.
20+
///
21+
/// This filter is modeled after KSCrash's `KSCrashReportFilterDoctor` and serves the same
22+
/// purpose: analyzing crash reports and producing descriptive diagnostic messages that explain
23+
/// the cause of the crash in plain language.
24+
///
25+
/// ## Crash Types Handled
26+
///
27+
/// 1. **Uncaught Exceptions**: Objective-C/Swift exceptions that weren't caught by the application
28+
/// - Extracts exception name and reason
29+
/// - Format: `"Terminating app due to uncaught exception 'ExceptionName', reason: 'detailed reason'."`
30+
///
31+
/// 2. **Signal-based Crashes**: Low-level crashes caused by Unix signals
32+
/// - Maps signal names to human-readable descriptions (SIGSEGV → Segmentation fault, etc.)
33+
/// - Format: `"Application crash: SIGSEGV (Segmentation fault)"`
34+
///
35+
/// ## Processing Flow
36+
///
37+
/// For each crash report:
38+
/// 1. Validates the report structure
39+
/// 2. Analyzes crash data to determine the crash type
40+
/// 3. Generates an appropriate diagnostic message
41+
/// 4. Injects the diagnosis into both the main crash and recrash reports (if present)
42+
/// 5. Returns the updated report
43+
///
44+
/// ## Integration
45+
///
46+
/// This filter should be placed early in the KSCrash filter chain, before the
47+
/// `DatadogCrashReportFilter`, to ensure diagnostic information is available for
48+
/// subsequent processing and reporting.
49+
internal final class DatadogDiagnosticFilter: NSObject, CrashReportFilter {
50+
/// Placeholder text used when crash information is unavailable.
51+
private let unknown = "<unknown>"
52+
53+
/// Mapping of Unix signal names to human-readable descriptions.
54+
///
55+
/// This dictionary provides user-friendly descriptions for all standard POSIX signals,
56+
/// matching the behavior of KSCrash's diagnostic filter.
57+
private let knownSignalDescriptionByName = [
58+
"SIGSIGNAL 0": "Signal 0",
59+
"SIGHUP": "Hangup",
60+
"SIGINT": "Interrupt",
61+
"SIGQUIT": "Quit",
62+
"SIGILL": "Illegal instruction",
63+
"SIGTRAP": "Trace/BPT trap",
64+
"SIGABRT": "Abort trap",
65+
"SIGEMT": "EMT trap",
66+
"SIGFPE": "Floating point exception",
67+
"SIGKILL": "Killed",
68+
"SIGBUS": "Bus error",
69+
"SIGSEGV": "Segmentation fault",
70+
"SIGSYS": "Bad system call",
71+
"SIGPIPE": "Broken pipe",
72+
"SIGALRM": "Alarm clock",
73+
"SIGTERM": "Terminated",
74+
"SIGURG": "Urgent I/O condition",
75+
"SIGSTOP": "Suspended (signal)",
76+
"SIGTSTP": "Suspended",
77+
"SIGCONT": "Continued",
78+
"SIGCHLD": "Child exited",
79+
"SIGTTIN": "Stopped (tty input)",
80+
"SIGTTOU": "Stopped (tty output)",
81+
"SIGIO": "I/O possible",
82+
"SIGXCPU": "Cputime limit exceeded",
83+
"SIGXFSZ": "Filesize limit exceeded",
84+
"SIGVTALRM": "Virtual timer expired",
85+
"SIGPROF": "Profiling timer expired",
86+
"SIGWINCH": "Window size changes",
87+
"SIGINFO": "Information request",
88+
"SIGUSR1": "User defined signal 1",
89+
"SIGUSR2": "User defined signal 2",
90+
]
91+
92+
/// Processes crash reports by adding human-readable diagnostic messages.
93+
///
94+
/// This method analyzes each crash report, generates a diagnostic message describing
95+
/// the crash cause, and injects it into the report. The diagnosis is added to both
96+
/// the main crash report and any recrash reports present.
97+
///
98+
/// - Parameters:
99+
/// - reports: The crash reports to process and enhance with diagnostic information
100+
/// - onCompletion: Completion handler called with the processed reports. If diagnosis
101+
/// generation fails for any report, the original reports are returned
102+
/// along with the error.
103+
func filterReports(
104+
_ reports: [CrashReport],
105+
onCompletion: (([CrashReport]?, (Error)?) -> Void)?
106+
) {
107+
do {
108+
let reports = try reports.map { report in
109+
// Validate and extract the type-safe report dictionary
110+
guard var dict = report.untypedValue as? CrashFieldDictionary else {
111+
throw CrashReportException(description: "KSCrash report untypedValue is not a CrashDictionary")
112+
}
113+
114+
if let crash: CrashFieldDictionary = try dict.valueIfPresent(forKey: .crash) {
115+
let diagnosis = try diagnose(crash: crash)
116+
dict.setValue(forKey: .crash, .diagnosis, value: diagnosis)
117+
}
118+
119+
if let crash: CrashFieldDictionary = try dict.valueIfPresent(forKey: .recrashReport, .crash) {
120+
let diagnosis = try diagnose(crash: crash)
121+
dict.setValue(forKey: .recrashReport, .crash, .diagnosis, value: diagnosis)
122+
}
123+
124+
return AnyCrashReport(dict)
125+
}
126+
127+
onCompletion?(reports, nil)
128+
} catch {
129+
onCompletion?(reports, error)
130+
}
131+
}
132+
133+
/// Analyzes a crash report and generates a human-readable diagnostic message.
134+
///
135+
/// This method examines the crash data to determine the crash type and constructs
136+
/// an appropriate diagnostic message. It follows a prioritized analysis:
137+
///
138+
/// 1. **Exception-based crashes**: Checks for uncaught NSException
139+
/// 2. **Signal-based crashes**: Checks for Unix signals with known descriptions
140+
/// 3. **Unknown crashes**: Returns a generic message for unrecognized crash types
141+
///
142+
/// - Parameter dict: The crash report dictionary containing crash information
143+
/// - Returns: A diagnostic message describing the crash, or `nil` if no crash data is found
144+
/// - Throws: `CrashReportException` if the report structure is invalid
145+
///
146+
/// ## Example Messages
147+
///
148+
/// - Exception: `"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unrecognized selector'."`
149+
/// - Signal: `"Application crash: SIGSEGV (Segmentation fault)"`
150+
/// - Unknown: `"Application crash: <unknown>"`
151+
func diagnose(crash dict: CrashFieldDictionary) throws -> String {
152+
// Check for uncaught exception
153+
if let exception: CrashFieldDictionary = try dict.valueIfPresent(forKey: .error, .nsException) {
154+
let name: String = try exception.valueIfPresent(forKey: .name) ?? unknown // e.g. `NSInvalidArgumentException`
155+
let reason = try dict.valueIfPresent(forKey: .error, .reason) ?? unknown // e.g. `-[NSObject objectForKey:]: unrecognized selector sent to instance 0x...`
156+
return "Terminating app due to uncaught exception '\(name)', reason: '\(reason)'."
157+
}
158+
159+
// Check for signal-based crash with known description
160+
if
161+
let name: String = try dict.valueIfPresent(forKey: .error, .signal, .name),
162+
let description = knownSignalDescriptionByName[name] {
163+
return "Application crash: \(name) (\(description))"
164+
}
165+
166+
// Fallback for unknown crash types
167+
return "Application crash: \(unknown)"
168+
}
169+
}

DatadogCrashReporting/Sources/KSCrashIntegration/KSCrashPlugin.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ internal class KSCrashPlugin: NSObject, CrashReportingPlugin {
3030
kscrash.reportStore?.sink = CrashReportFilterPipeline(
3131
filters: [
3232
DatadogTypeSafeFilter(),
33-
DatadogMinifyFilter()
33+
DatadogMinifyFilter(),
34+
DatadogDiagnosticFilter()
3435
]
3536
)
3637

0 commit comments

Comments
 (0)