-
Notifications
You must be signed in to change notification settings - Fork 134
/
Copy pathCrashReporter.swift
169 lines (145 loc) · 7.13 KB
/
CrashReporter.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-2020 Datadog, Inc.
*/
import Foundation
internal class CrashReporter {
/// Queue for synchronizing internal operations.
private let queue: DispatchQueue
/// An interface for accessing the `DDCrashReportingPlugin` from `DatadogCrashReporting`.
let plugin: DDCrashReportingPluginType
/// Integration enabling sending crash reports as Logs or RUM Errors.
let loggingOrRUMIntegration: CrashReportingIntegration
let crashContextProvider: CrashContextProviderType
// MARK: - Initialization
convenience init?(crashReportingFeature: CrashReportingFeature) {
let loggingOrRUMIntegration: CrashReportingIntegration?
// If RUM rum is enabled prefer it for sending crash reports, otherwise use Logging feature.
if let rumFeature = RUMFeature.instance {
loggingOrRUMIntegration = CrashReportingWithRUMIntegration(rumFeature: rumFeature)
} else if let loggingFeature = LoggingFeature.instance {
loggingOrRUMIntegration = CrashReportingWithLoggingIntegration(loggingFeature: loggingFeature)
} else {
loggingOrRUMIntegration = nil
}
guard let availableLoggingOrRUMIntegration = loggingOrRUMIntegration else {
// This case is not reachable in higher abstraction but we add sanity warning.
userLogger.error(
"""
In order to use Crash Reporting, RUM or Logging feature must be enabled.
Make sure `.enableRUM(true)` or `.enableLogging(true)` are configured
when initializing Datadog SDK.
"""
)
return nil
}
self.init(
crashReportingPlugin: crashReportingFeature.configuration.crashReportingPlugin,
crashContextProvider: CrashContextProvider(
consentProvider: crashReportingFeature.consentProvider,
userInfoProvider: crashReportingFeature.userInfoProvider,
networkConnectionInfoProvider: crashReportingFeature.networkConnectionInfoProvider,
carrierInfoProvider: crashReportingFeature.carrierInfoProvider,
rumViewEventProvider: crashReportingFeature.rumViewEventProvider,
rumSessionStateProvider: crashReportingFeature.rumSessionStateProvider,
appStateListener: crashReportingFeature.appStateListener
),
loggingOrRUMIntegration: availableLoggingOrRUMIntegration
)
}
init(
crashReportingPlugin: DDCrashReportingPluginType,
crashContextProvider: CrashContextProviderType,
loggingOrRUMIntegration: CrashReportingIntegration
) {
self.queue = DispatchQueue(
label: "com.datadoghq.crash-reporter",
target: .global(qos: .utility)
)
self.plugin = crashReportingPlugin
self.loggingOrRUMIntegration = loggingOrRUMIntegration
self.crashContextProvider = crashContextProvider
// Inject current `CrashContext`
self.inject(currentCrashContext: crashContextProvider.currentCrashContext)
// Register for future `CrashContext` changes
self.crashContextProvider.onCrashContextChange = { [weak self] newCrashContext in
guard let self = self else {
return
}
self.inject(currentCrashContext: newCrashContext)
}
}
// MARK: - Interaction with `DatadogCrashReporting` plugin
func sendCrashReportIfFound() {
queue.async {
self.plugin.readPendingCrashReport { [weak self] crashReport in
guard let self = self, let availableCrashReport = crashReport else {
return false
}
#if DD_SDK_ENABLE_INTERNAL_MONITORING
InternalMonitoringFeature.instance?.monitor.sdkLogger
.debug("Loaded pending crash report", attributes: availableCrashReport.diagnosticInfo)
#endif
guard let crashContext = availableCrashReport.context.flatMap({ self.decode(crashContextData: $0) }) else {
// `CrashContext` is malformed and and cannot be read. Return `true` to let the crash reporter
// purge this crash report as we are not able to process it respectively.
return true
}
self.loggingOrRUMIntegration.send(crashReport: availableCrashReport, with: crashContext)
return true
}
}
}
private func inject(currentCrashContext: CrashContext) {
queue.async {
if let crashContextData = self.encode(crashContext: currentCrashContext) {
self.plugin.inject(context: crashContextData)
}
}
}
// MARK: - CrashContext Encoding and Decoding
/// JSON encoder used for writing `CrashContext` into JSON `Data` injected to crash report.
/// Note: this `JSONEncoder` must have the same configuration as the `JSONEncoder` used later for writing payloads to uploadable files.
/// Otherwise the format of data read and uploaded from crash report context will be different than the format of data retrieved from the user
/// and written directly to uploadable file.
private let crashContextEncoder: JSONEncoder = .default()
/// JSON decoder used for reading `CrashContext` from JSON `Data` injected to crash report.
private let crashContextDecoder = JSONDecoder()
private func encode(crashContext: CrashContext) -> Data? {
do {
return try crashContextEncoder.encode(crashContext)
} catch {
userLogger.warn(
"""
Failed to encode crash report context. The app state information associated with eventual crash
report may be not in sync with the current state of the application.
Error details: \(error)
"""
)
InternalMonitoringFeature.instance?.monitor.sdkLogger
.error("Failed to encode crash report context", error: error)
return nil
}
}
private func decode(crashContextData: Data) -> CrashContext? {
do {
return try crashContextDecoder.decode(CrashContext.self, from: crashContextData)
} catch {
userLogger.error(
"""
Failed to decode crash report context. The app state information associated with the crash
report won't be in sync with the state of the application when it crashed.
Error details: \(error)
"""
)
#if DD_SDK_ENABLE_INTERNAL_MONITORING
let contextUTF8String = String(data: crashContextData, encoding: .utf8)
let attributes = ["context_utf8_string": contextUTF8String ?? "none"]
InternalMonitoringFeature.instance?.monitor.sdkLogger
.error("Failed to decode crash report context", error: error, attributes: attributes)
#endif
return nil
}
}
}