@logbrew/react-native 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/apple-native-diagnostics.d.ts +5 -0
- package/apple-native-diagnostics.js +53 -6
- package/index.cjs +1 -1
- package/index.native.js +57 -4
- package/instrumentation.js +3 -632
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +5 -0
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +31 -3
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnosticEvidence.swift +2 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +3 -2
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +10 -2
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +29 -4
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +169 -38
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +27 -5
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +6 -6
- package/lifecycle.js +6 -120
- package/metadata.js +10 -181
- package/native-bridge.js +5 -120
- package/package.json +2 -2
- package/src/NativeLogBrewAppleDiagnostics.ts +3 -0
|
@@ -77,7 +77,8 @@ public struct IssueDiagnosticEvidence: Codable, Equatable, Sendable {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
@_spi(CrashReplay)
|
|
81
|
+
public func validateIssueDiagnosticEvidence(_ value: IssueDiagnosticEvidence) throws -> IssueDiagnosticEvidence {
|
|
81
82
|
let cause = try value.likelyRootCause.map {
|
|
82
83
|
try issueText($0, label: "issue evidence likelyRootCause", maximum: 1024)
|
|
83
84
|
}
|
|
@@ -174,7 +174,7 @@ final class IssueBreadcrumbStore: @unchecked Sendable {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
let maximumIssueBreadcrumbs = 64
|
|
177
|
+
@_spi(CrashReplay) public let maximumIssueBreadcrumbs = 64
|
|
178
178
|
|
|
179
179
|
func normalizeIssueAttributes(_ value: IssueAttributes) throws -> IssueAttributes {
|
|
180
180
|
let exception = try value.exception.map(validateIssueException)
|
|
@@ -251,7 +251,8 @@ func validateIssueException(_ value: IssueException) throws -> IssueException {
|
|
|
251
251
|
return IssueException(type: exceptionType, mechanism: mechanism)
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
|
|
254
|
+
@_spi(CrashReplay)
|
|
255
|
+
public func validateIssueBreadcrumb(_ value: IssueBreadcrumb) throws -> IssueBreadcrumb {
|
|
255
256
|
try requireTimestamp(value.timestamp)
|
|
256
257
|
guard validMachineKey(value.category, maximum: 64, separators: ["_", ".", ":", "-"]) else {
|
|
257
258
|
throw issueValidationError("issue breadcrumb category is invalid")
|
|
@@ -23,6 +23,10 @@ struct CrashReportSanitizer {
|
|
|
23
23
|
let nativeStackFrames = NativeStackFrameSanitizer().frames(from: rawReport)
|
|
24
24
|
let artifactIdentity = try NativeArtifactIdentityValue.persistedIdentity(in: rawReport)
|
|
25
25
|
let correlation = NativeCrashCorrelation.captured(in: rawReport)
|
|
26
|
+
let correlationState = correlation.0?.correlationContext == nil && correlation.1 == .captured
|
|
27
|
+
? NativeCrashContextState.notCaptured
|
|
28
|
+
: correlation.1
|
|
29
|
+
let breadcrumbs = NativeCrashBreadcrumbs.captured(in: rawReport)
|
|
26
30
|
return NativeCrashRecord(
|
|
27
31
|
eventID: uuid.uuidString.lowercased(),
|
|
28
32
|
timestamp: timestamp.normalized,
|
|
@@ -32,9 +36,13 @@ struct CrashReportSanitizer {
|
|
|
32
36
|
context: nativeCrashContext(
|
|
33
37
|
system: rawReport["system"] as? [String: Any],
|
|
34
38
|
artifactIdentity: artifactIdentity,
|
|
35
|
-
correlation: correlation.0,
|
|
39
|
+
correlation: correlation.0?.correlationContext,
|
|
36
40
|
),
|
|
37
|
-
correlationState:
|
|
41
|
+
correlationState: correlationState,
|
|
42
|
+
impact: correlation.0?.impact,
|
|
43
|
+
breadcrumbs: breadcrumbs.0?.breadcrumbs,
|
|
44
|
+
breadcrumbsTruncated: breadcrumbs.0?.truncated,
|
|
45
|
+
breadcrumbState: breadcrumbs.1,
|
|
38
46
|
hangState: nil,
|
|
39
47
|
hangDurationMs: nil,
|
|
40
48
|
source: .engine(reportID: reportID),
|
|
@@ -109,6 +109,34 @@ public final class NativeCrashCapture: NSObject, @unchecked Sendable {
|
|
|
109
109
|
/// Replaces the crash-time trace, session, and opaque subject snapshot in one bounded write.
|
|
110
110
|
@nonobjc
|
|
111
111
|
public func setCorrelationContext(_ context: TelemetryContext?) throws {
|
|
112
|
+
try setDiagnosticContext(context: context, impact: nil)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@nonobjc
|
|
116
|
+
func setDiagnosticContext(
|
|
117
|
+
context: TelemetryContext?,
|
|
118
|
+
impact: IssueImpactEvidence?,
|
|
119
|
+
) throws {
|
|
120
|
+
try setSnapshot(forKey: NativeCrashCorrelation.reportKey) {
|
|
121
|
+
try NativeCrashCorrelation.encoded(context: context, impact: impact)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Replaces the crash-time breadcrumb snapshot in one bounded write.
|
|
126
|
+
@nonobjc
|
|
127
|
+
public func setBreadcrumbs(
|
|
128
|
+
_ breadcrumbs: [IssueBreadcrumb]?,
|
|
129
|
+
truncated: Bool = false,
|
|
130
|
+
) throws {
|
|
131
|
+
try setSnapshot(forKey: NativeCrashBreadcrumbs.reportKey) {
|
|
132
|
+
try NativeCrashBreadcrumbs.encoded(breadcrumbs, truncated: truncated)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private func setSnapshot(
|
|
137
|
+
forKey key: String,
|
|
138
|
+
encoded: () throws -> String?,
|
|
139
|
+
) throws {
|
|
112
140
|
lock.lock()
|
|
113
141
|
defer { lock.unlock() }
|
|
114
142
|
try verifyProcessLocked()
|
|
@@ -116,10 +144,7 @@ public final class NativeCrashCapture: NSObject, @unchecked Sendable {
|
|
|
116
144
|
throw NativeCrashError(.notInstalled)
|
|
117
145
|
}
|
|
118
146
|
try verifyStorageLocked()
|
|
119
|
-
try driver.setUserInfo(
|
|
120
|
-
NativeCrashCorrelation.encoded(context),
|
|
121
|
-
forKey: NativeCrashCorrelation.reportKey,
|
|
122
|
-
)
|
|
147
|
+
try driver.setUserInfo(encoded(), forKey: key)
|
|
123
148
|
}
|
|
124
149
|
|
|
125
150
|
@objc(replayPendingReportsWithHandler:error:)
|
|
@@ -2,76 +2,207 @@
|
|
|
2
2
|
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
3
|
import Foundation
|
|
4
4
|
|
|
5
|
-
enum
|
|
5
|
+
enum NativeCrashContextState: String {
|
|
6
6
|
case captured
|
|
7
7
|
case notCaptured = "not_captured"
|
|
8
8
|
case unavailable
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
struct NativeCrashDiagnosticSnapshot: Codable, Equatable {
|
|
12
|
+
let schemaVersion: Int
|
|
13
|
+
let trace: TelemetryTraceContext?
|
|
14
|
+
let session: TelemetrySessionContext?
|
|
15
|
+
let subject: TelemetrySubjectContext?
|
|
16
|
+
let impact: IssueImpactEvidence?
|
|
17
|
+
|
|
18
|
+
init(context: TelemetryContext?, impact: IssueImpactEvidence?) throws {
|
|
19
|
+
let context = try context.map(validateNativeCrashCorrelationContext)
|
|
20
|
+
let impact = try impact.map { value in
|
|
21
|
+
guard value.affectedUserSegment == nil,
|
|
22
|
+
let value = try validateIssueDiagnosticEvidence(
|
|
23
|
+
IssueDiagnosticEvidence(impact: value),
|
|
24
|
+
).impact,
|
|
25
|
+
value.failedAction != nil
|
|
26
|
+
else {
|
|
27
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
28
|
+
}
|
|
29
|
+
return value
|
|
30
|
+
}
|
|
31
|
+
guard context != nil || impact != nil else {
|
|
32
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
33
|
+
}
|
|
34
|
+
schemaVersion = 1
|
|
35
|
+
trace = context?.trace
|
|
36
|
+
session = context?.session
|
|
37
|
+
subject = context?.subject
|
|
38
|
+
self.impact = impact
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var correlationContext: TelemetryContext? {
|
|
42
|
+
trace == nil && session == nil && subject == nil
|
|
43
|
+
? nil
|
|
44
|
+
: TelemetryContext(trace: trace, session: session, subject: subject)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
11
49
|
enum NativeCrashCorrelation {
|
|
12
50
|
static let reportKey = "logbrew_native_correlation"
|
|
13
|
-
private static let maximumBytes = 1024
|
|
51
|
+
private static let maximumBytes = 4 * 1024
|
|
14
52
|
|
|
15
|
-
static func encoded(
|
|
16
|
-
|
|
53
|
+
static func encoded(
|
|
54
|
+
context: TelemetryContext?,
|
|
55
|
+
impact: IssueImpactEvidence? = nil,
|
|
56
|
+
) throws -> String? {
|
|
57
|
+
guard context != nil || impact != nil else {
|
|
17
58
|
return nil
|
|
18
59
|
}
|
|
19
60
|
do {
|
|
20
|
-
let data = try
|
|
61
|
+
let data = try nativeCrashEncoded(NativeCrashDiagnosticSnapshot(
|
|
62
|
+
context: context,
|
|
63
|
+
impact: impact,
|
|
64
|
+
))
|
|
21
65
|
guard data.count <= maximumBytes, let value = String(data: data, encoding: .utf8) else {
|
|
22
66
|
throw NativeCrashError(.invalidConfiguration)
|
|
23
67
|
}
|
|
24
68
|
return value
|
|
25
|
-
} catch let error as NativeCrashError {
|
|
26
|
-
throw error
|
|
27
69
|
} catch {
|
|
28
70
|
throw NativeCrashError(.invalidConfiguration)
|
|
29
71
|
}
|
|
30
72
|
}
|
|
31
73
|
|
|
32
|
-
static func captured(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
guard let value = value as? String,
|
|
37
|
-
let data = value.data(using: .utf8),
|
|
38
|
-
data.count <= maximumBytes,
|
|
39
|
-
let object = try? JSONSerialization.jsonObject(with: data),
|
|
40
|
-
let context = try? validated(object)
|
|
41
|
-
else {
|
|
42
|
-
return (nil, .unavailable)
|
|
43
|
-
}
|
|
44
|
-
return (context, .captured)
|
|
74
|
+
static func captured(
|
|
75
|
+
in rawReport: [String: Any],
|
|
76
|
+
) -> (NativeCrashDiagnosticSnapshot?, NativeCrashContextState) {
|
|
77
|
+
nativeCrashCaptured(in: rawReport, key: reportKey, maximumBytes: maximumBytes, validate: validated)
|
|
45
78
|
}
|
|
46
79
|
|
|
47
|
-
static func validated(_ object: Any) throws ->
|
|
48
|
-
|
|
49
|
-
guard
|
|
80
|
+
static func validated(_ object: Any) throws -> NativeCrashDiagnosticSnapshot {
|
|
81
|
+
try nativeCrashValidated(object, maximumBytes: maximumBytes) { decoded in
|
|
82
|
+
guard decoded.schemaVersion == 1 else {
|
|
50
83
|
throw NativeCrashError(.invalidConfiguration)
|
|
51
84
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
let context = try validateNativeCrashCorrelationContext(
|
|
57
|
-
JSONDecoder().decode(TelemetryContext.self, from: data),
|
|
85
|
+
return try NativeCrashDiagnosticSnapshot(
|
|
86
|
+
context: decoded.correlationContext,
|
|
87
|
+
impact: decoded.impact,
|
|
58
88
|
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
struct NativeCrashBreadcrumbSnapshot: Codable, Equatable {
|
|
94
|
+
let schemaVersion: Int
|
|
95
|
+
let breadcrumbs: [IssueBreadcrumb]
|
|
96
|
+
let truncated: Bool
|
|
97
|
+
|
|
98
|
+
init(breadcrumbs: [IssueBreadcrumb], truncated: Bool) {
|
|
99
|
+
schemaVersion = 1
|
|
100
|
+
self.breadcrumbs = breadcrumbs
|
|
101
|
+
self.truncated = truncated
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
enum NativeCrashBreadcrumbs {
|
|
106
|
+
static let reportKey = "logbrew_native_breadcrumbs"
|
|
107
|
+
static let maximumBytes = 64 * 1024
|
|
108
|
+
|
|
109
|
+
static func encoded(_ values: [IssueBreadcrumb]?, truncated: Bool) throws -> String? {
|
|
110
|
+
guard let values, !values.isEmpty else {
|
|
111
|
+
return nil
|
|
112
|
+
}
|
|
113
|
+
do {
|
|
114
|
+
var breadcrumbs = try values.suffix(maximumIssueBreadcrumbs).map(validateIssueBreadcrumb)
|
|
115
|
+
var wasTruncated = truncated || breadcrumbs.count < values.count
|
|
116
|
+
while !breadcrumbs.isEmpty {
|
|
117
|
+
let data = try nativeCrashEncoded(NativeCrashBreadcrumbSnapshot(
|
|
118
|
+
breadcrumbs: breadcrumbs,
|
|
119
|
+
truncated: wasTruncated,
|
|
120
|
+
))
|
|
121
|
+
if data.count <= maximumBytes, let value = String(data: data, encoding: .utf8) {
|
|
122
|
+
return value
|
|
123
|
+
}
|
|
124
|
+
breadcrumbs.removeFirst()
|
|
125
|
+
wasTruncated = true
|
|
63
126
|
}
|
|
64
|
-
return context
|
|
65
127
|
} catch let error as NativeCrashError {
|
|
66
128
|
throw error
|
|
67
129
|
} catch {
|
|
68
130
|
throw NativeCrashError(.invalidConfiguration)
|
|
69
131
|
}
|
|
132
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static func captured(
|
|
136
|
+
in rawReport: [String: Any],
|
|
137
|
+
) -> (NativeCrashBreadcrumbSnapshot?, NativeCrashContextState) {
|
|
138
|
+
nativeCrashCaptured(in: rawReport, key: reportKey, maximumBytes: maximumBytes, validate: validated)
|
|
70
139
|
}
|
|
71
140
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
141
|
+
static func validated(_ object: Any) throws -> NativeCrashBreadcrumbSnapshot {
|
|
142
|
+
try nativeCrashValidated(object, maximumBytes: maximumBytes) { decoded in
|
|
143
|
+
guard decoded.schemaVersion == 1,
|
|
144
|
+
!decoded.breadcrumbs.isEmpty,
|
|
145
|
+
decoded.breadcrumbs.count <= maximumIssueBreadcrumbs
|
|
146
|
+
else {
|
|
147
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
148
|
+
}
|
|
149
|
+
return try NativeCrashBreadcrumbSnapshot(
|
|
150
|
+
breadcrumbs: decoded.breadcrumbs.map(validateIssueBreadcrumb),
|
|
151
|
+
truncated: decoded.truncated,
|
|
152
|
+
)
|
|
153
|
+
}
|
|
76
154
|
}
|
|
77
155
|
}
|
|
156
|
+
|
|
157
|
+
private func nativeCrashCaptured<Value>(
|
|
158
|
+
in rawReport: [String: Any],
|
|
159
|
+
key: String,
|
|
160
|
+
maximumBytes: Int,
|
|
161
|
+
validate: (Any) throws -> Value,
|
|
162
|
+
) -> (Value?, NativeCrashContextState) {
|
|
163
|
+
guard let user = rawReport["user"] as? [String: Any], let rawValue = user[key] else {
|
|
164
|
+
return (nil, .notCaptured)
|
|
165
|
+
}
|
|
166
|
+
guard let value = rawValue as? String,
|
|
167
|
+
let data = value.data(using: .utf8),
|
|
168
|
+
data.count <= maximumBytes,
|
|
169
|
+
let object = try? JSONSerialization.jsonObject(with: data),
|
|
170
|
+
let snapshot = try? validate(object)
|
|
171
|
+
else {
|
|
172
|
+
return (nil, .unavailable)
|
|
173
|
+
}
|
|
174
|
+
return (snapshot, .captured)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private func nativeCrashValidated<Value: Codable>(
|
|
178
|
+
_ object: Any,
|
|
179
|
+
maximumBytes: Int,
|
|
180
|
+
normalize: (Value) throws -> Value,
|
|
181
|
+
) throws -> Value {
|
|
182
|
+
do {
|
|
183
|
+
guard JSONSerialization.isValidJSONObject(object) else {
|
|
184
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
185
|
+
}
|
|
186
|
+
let source = try JSONSerialization.data(withJSONObject: object, options: [.sortedKeys])
|
|
187
|
+
guard source.count <= maximumBytes else {
|
|
188
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
189
|
+
}
|
|
190
|
+
let value = try normalize(JSONDecoder().decode(Value.self, from: source))
|
|
191
|
+
let normalized = try nativeCrashEncoded(value)
|
|
192
|
+
let normalizedObject = try JSONSerialization.jsonObject(with: normalized)
|
|
193
|
+
guard object as? NSDictionary == normalizedObject as? NSDictionary else {
|
|
194
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
195
|
+
}
|
|
196
|
+
return value
|
|
197
|
+
} catch let error as NativeCrashError {
|
|
198
|
+
throw error
|
|
199
|
+
} catch {
|
|
200
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private func nativeCrashEncoded(_ value: some Encodable) throws -> Data {
|
|
205
|
+
let encoder = JSONEncoder()
|
|
206
|
+
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
|
|
207
|
+
return try encoder.encode(value)
|
|
208
|
+
}
|
|
@@ -164,7 +164,11 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
164
164
|
private let nativeStackFrames: [NativeStackFrame]?
|
|
165
165
|
private let artifactIdentity: NativeArtifactIdentity?
|
|
166
166
|
private let context: TelemetryContext?
|
|
167
|
-
private let correlationState:
|
|
167
|
+
private let correlationState: NativeCrashContextState?
|
|
168
|
+
private let impact: IssueImpactEvidence?
|
|
169
|
+
private let breadcrumbs: [IssueBreadcrumb]?
|
|
170
|
+
private let breadcrumbsTruncated: Bool?
|
|
171
|
+
private let breadcrumbState: NativeCrashContextState?
|
|
168
172
|
private let hangState: NativeHangIncidentState?
|
|
169
173
|
private let hangDurationMs: Double?
|
|
170
174
|
let source: NativeCrashRecordSource
|
|
@@ -178,7 +182,11 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
178
182
|
nativeStackFrames: [NativeStackFrame]?,
|
|
179
183
|
artifactIdentity: NativeArtifactIdentity?,
|
|
180
184
|
context: TelemetryContext?,
|
|
181
|
-
correlationState:
|
|
185
|
+
correlationState: NativeCrashContextState? = nil,
|
|
186
|
+
impact: IssueImpactEvidence? = nil,
|
|
187
|
+
breadcrumbs: [IssueBreadcrumb]? = nil,
|
|
188
|
+
breadcrumbsTruncated: Bool? = nil,
|
|
189
|
+
breadcrumbState: NativeCrashContextState? = nil,
|
|
182
190
|
hangState: NativeHangIncidentState?,
|
|
183
191
|
hangDurationMs: Double? = nil,
|
|
184
192
|
source: NativeCrashRecordSource,
|
|
@@ -192,6 +200,10 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
192
200
|
self.artifactIdentity = artifactIdentity
|
|
193
201
|
self.context = context
|
|
194
202
|
self.correlationState = correlationState
|
|
203
|
+
self.impact = impact
|
|
204
|
+
self.breadcrumbs = breadcrumbs
|
|
205
|
+
self.breadcrumbsTruncated = breadcrumbsTruncated
|
|
206
|
+
self.breadcrumbState = breadcrumbState
|
|
195
207
|
self.hangState = hangState
|
|
196
208
|
self.hangDurationMs = hangDurationMs
|
|
197
209
|
self.source = source
|
|
@@ -234,6 +246,9 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
234
246
|
if let correlationState {
|
|
235
247
|
metadata["crash.correlation"] = .string(correlationState.rawValue)
|
|
236
248
|
}
|
|
249
|
+
if let breadcrumbState {
|
|
250
|
+
metadata["crash.breadcrumbs"] = .string(breadcrumbState.rawValue)
|
|
251
|
+
}
|
|
237
252
|
if let hangState {
|
|
238
253
|
metadata["crash.handled"] = .bool(hangState == .recovered)
|
|
239
254
|
}
|
|
@@ -245,6 +260,9 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
245
260
|
level: hangState == .recovered ? .error : .fatal,
|
|
246
261
|
exception: issueException,
|
|
247
262
|
exceptionChain: nativeCrashExceptionChain(for: issueException),
|
|
263
|
+
breadcrumbs: breadcrumbs,
|
|
264
|
+
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
265
|
+
evidence: impact.map { IssueDiagnosticEvidence(impact: $0) },
|
|
248
266
|
metadata: metadata,
|
|
249
267
|
context: context,
|
|
250
268
|
nativeStackFrames: nativeStackFrames,
|
|
@@ -287,12 +305,16 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
287
305
|
else {
|
|
288
306
|
return false
|
|
289
307
|
}
|
|
290
|
-
|
|
308
|
+
let legacyFields = ["exception", "exceptionChain", "evidence", "breadcrumbs", "breadcrumbsTruncated", "context"]
|
|
309
|
+
for legacyField in legacyFields where actual[legacyField] == nil {
|
|
291
310
|
expected.removeValue(forKey: legacyField)
|
|
292
311
|
}
|
|
293
|
-
|
|
312
|
+
let actualMetadata = actual["metadata"] as? [String: Any]
|
|
313
|
+
if actualMetadata?["crash.correlation"] == nil || actualMetadata?["crash.breadcrumbs"] == nil {
|
|
294
314
|
var metadata = expected["metadata"] as? [String: Any]
|
|
295
|
-
|
|
315
|
+
for key in ["crash.correlation", "crash.breadcrumbs"] where actualMetadata?[key] == nil {
|
|
316
|
+
metadata?.removeValue(forKey: key)
|
|
317
|
+
}
|
|
296
318
|
expected["metadata"] = metadata
|
|
297
319
|
}
|
|
298
320
|
return actual as NSDictionary == expected as NSDictionary
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStore.swift": "7c292dda68efc3de790daf44733b25e4392226793343c21ba5df4f51fc1b402b",
|
|
11
11
|
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStoreRecovery.swift": "79fee27975b6571954bf0b3692a50e60bbbcf53b5ab47f653a474db5c56d68ba",
|
|
12
12
|
"swift/logbrew-swift/Sources/LogBrew/EventEncoding.swift": "b88b6b2d17f7d0cee9ac1a1f34ef35b192cd33d213ef3c9e1c2d21cbcc304a97",
|
|
13
|
-
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnosticEvidence.swift": "
|
|
14
|
-
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnostics.swift": "
|
|
13
|
+
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnosticEvidence.swift": "1042b5a6283cf440529572844e5ad1f3078f4ba715133932ea305e075614a53c",
|
|
14
|
+
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnostics.swift": "bbdfe6169961ff27b8f2cee63c7dcdeacf2e5cee6a3ca17f6a26b933f0adfc39",
|
|
15
15
|
"swift/logbrew-swift/Sources/LogBrew/IssueExceptionChain.swift": "10f81cbce95b47da0a44ef9e515b150e60d71dfcc8b04be24b037b1222a373a5",
|
|
16
16
|
"swift/logbrew-swift/Sources/LogBrew/LifecycleTrace.swift": "738cd3f129fac821a892404d1d42ee92bd806b6583d3dbb5379e88b14ab23c16",
|
|
17
17
|
"swift/logbrew-swift/Sources/LogBrew/LogBrewClient.swift": "186cdbda3754a811f17b82818ca8e7a1711b3b75123686cd17dc3b7441fa21cb",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"swift/logbrew-swift/Sources/LogBrew/URLSessionTracer.swift": "0cdf08f92d6111c0dc2883b87837b81f69b5c2f9a8e5501780a114c23a741af7",
|
|
30
30
|
"swift/logbrew-swift/Sources/LogBrew/Validation.swift": "07ff5c2daeb829b1df4f50edb0b79727f9a81b1c9b5cde5f995f5ad6ce660aa7",
|
|
31
31
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashEngine.swift": "a94dfce653e3e7f381be42b6d2c4540f0be77e83777ecc53e06682cd2e569c5a",
|
|
32
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "
|
|
32
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "a56a2f6c6c7f1db900804a37df51aebba7b83dce14fa7816de5753c33409e4a5",
|
|
33
33
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashStorageDirectory.swift": "7cd566703cbf704dc99155451ee93dcace12c35c805abc09a6dcf23975cf43f9",
|
|
34
34
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeArtifactIdentity.swift": "3d785ad717dcf7302451531c18b5e1183983270a6cd78dd543a407e3facc6ae0",
|
|
35
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCapture.swift": "
|
|
36
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCorrelation.swift": "
|
|
35
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCapture.swift": "0b773a63987b6c694b044e56c0f81878f4f5e71eae3fdd126744cba3cf15aac1",
|
|
36
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCorrelation.swift": "e5a7c25448c83ca2ae149c650ecfc72c5193741291a6ff9623eabadad8a9c440",
|
|
37
37
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashExceptionEvidence.swift": "ce191e46298d91d87b379528d610caa88b924b15a8dd912b4a164b51eb172a7a",
|
|
38
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "
|
|
38
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "70aa9700efd26d03b9656f1df96876de0b9eb769c03512ec53f667696773954f",
|
|
39
39
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashReplay.swift": "3eb3d93b32cf14de416f56fa30d6d65e6f268b5ec9d1a67081d7525c4efd4f80",
|
|
40
40
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangIncidentStore.swift": "5a8150083f16d6b495c9c63434809dc5f11f13ebb23211f57151bad62040781b",
|
|
41
41
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdog.swift": "37b25904cd6576e20c40500be2c153156403b57ad7b4186a1b6b35b414145ecb",
|
package/lifecycle.js
CHANGED
|
@@ -1,121 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
createReactNativeSpanAttributes,
|
|
4
|
-
createReactNativeTraceContext,
|
|
5
|
-
getActiveLogBrewTrace,
|
|
6
|
-
getReactNativeContext
|
|
7
|
-
} from "./index.js";
|
|
1
|
+
import runtime from "./lifecycle.cjs";
|
|
8
2
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} =
|
|
14
|
-
const safeFromState = normalizeLifecycleState(fromState);
|
|
15
|
-
const safeToState = normalizeLifecycleState(toState ?? state);
|
|
16
|
-
const transition = [safeFromState, safeToState].filter(Boolean).join("->");
|
|
17
|
-
const spanName = name ?? `app_state:${transition || safeToState || safeFromState || "change"}`;
|
|
18
|
-
const activeTrace = trace ?? getActiveLogBrewTrace() ?? createReactNativeTraceContext();
|
|
19
|
-
return {
|
|
20
|
-
id: id ?? idFactory({ fromState: safeFromState, screen, toState: safeToState }),
|
|
21
|
-
timestamp: timestamp ?? now(),
|
|
22
|
-
attributes: createReactNativeSpanAttributes({
|
|
23
|
-
name: spanName,
|
|
24
|
-
status,
|
|
25
|
-
durationMs,
|
|
26
|
-
trace: activeTrace,
|
|
27
|
-
metadata: {
|
|
28
|
-
...getReactNativeContext({ platform, appState }),
|
|
29
|
-
source: "react-native.lifecycle",
|
|
30
|
-
appState: safeToState,
|
|
31
|
-
durationMs,
|
|
32
|
-
fromAppState: safeFromState,
|
|
33
|
-
screen,
|
|
34
|
-
sessionId,
|
|
35
|
-
toAppState: safeToState,
|
|
36
|
-
...metadata
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function captureReactNativeLifecycleSpan(client, input = {}) {
|
|
43
|
-
requireClient(client);
|
|
44
|
-
const event = createReactNativeLifecycleSpanEvent(input);
|
|
45
|
-
client.span(event.id, event.timestamp, event.attributes);
|
|
46
|
-
return event;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function createAppStateLifecycleSpanListener(client, appState, {
|
|
50
|
-
captureInitialState = false, metadata = {}, now = () => new Date().toISOString(), nowMs = () => Date.now(),
|
|
51
|
-
onError, platform, screen, sessionId, trace
|
|
52
|
-
} = {}) {
|
|
53
|
-
requireClient(client);
|
|
54
|
-
if (!appState || typeof appState.addEventListener !== "function") {
|
|
55
|
-
throw new SdkError("configuration_error", "createAppStateLifecycleSpanListener requires AppState.addEventListener");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let previousState = normalizeLifecycleState(appState.currentState);
|
|
59
|
-
let previousChangedAtMs = nowMs();
|
|
60
|
-
if (captureInitialState && previousState !== undefined) {
|
|
61
|
-
captureReactNativeLifecycleSpan(client, {
|
|
62
|
-
appState, metadata, now, platform, screen, sessionId, toState: previousState, trace
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const subscription = appState.addEventListener("change", (nextState) => {
|
|
67
|
-
try {
|
|
68
|
-
const safeNextState = normalizeLifecycleState(nextState);
|
|
69
|
-
if (safeNextState === undefined) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const changedAtMs = nowMs();
|
|
73
|
-
const durationMs = previousState === undefined ? undefined : Math.max(0, changedAtMs - previousChangedAtMs);
|
|
74
|
-
captureReactNativeLifecycleSpan(client, {
|
|
75
|
-
appState, durationMs, fromState: previousState, metadata, now, platform, screen, sessionId,
|
|
76
|
-
timestamp: now(), toState: safeNextState, trace
|
|
77
|
-
});
|
|
78
|
-
previousState = safeNextState;
|
|
79
|
-
previousChangedAtMs = changedAtMs;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (typeof onError === "function") {
|
|
82
|
-
onError(error);
|
|
83
|
-
} else {
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
return subscriptionRemover(subscription);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function requireClient(client) {
|
|
93
|
-
if (!client) {
|
|
94
|
-
throw new SdkError("configuration_error", "LogBrew React Native lifecycle helpers require a client");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function defaultLifecycleSpanEventId({ fromState, screen, toState }) {
|
|
99
|
-
return `evt_native_lifecycle_${slugify([screen, fromState, toState].filter(Boolean).join("_") || "app_state")}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function normalizeLifecycleState(state) {
|
|
103
|
-
return typeof state === "string" && state.trim() !== "" ? state.trim() : undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function subscriptionRemover(subscription) {
|
|
107
|
-
if (typeof subscription === "function") {
|
|
108
|
-
return subscription;
|
|
109
|
-
}
|
|
110
|
-
if (subscription && typeof subscription.remove === "function") {
|
|
111
|
-
return () => subscription.remove();
|
|
112
|
-
}
|
|
113
|
-
return () => {};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function slugify(value) {
|
|
117
|
-
return String(value)
|
|
118
|
-
.toLowerCase()
|
|
119
|
-
.replace(/[^a-z0-9]+/g, "_")
|
|
120
|
-
.replace(/^_+|_+$/g, "") || "event";
|
|
121
|
-
}
|
|
3
|
+
export const {
|
|
4
|
+
captureReactNativeLifecycleSpan,
|
|
5
|
+
createAppStateLifecycleSpanListener,
|
|
6
|
+
createReactNativeLifecycleSpanEvent
|
|
7
|
+
} = runtime;
|