@logbrew/react-native 0.1.16 → 0.1.18
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/LogBrewReactNative.podspec +1 -1
- package/README.md +2 -1
- package/index.cjs +3 -1
- package/index.d.cts +2 -3
- package/index.d.ts +2 -3
- package/index.js +3 -1
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +1 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +2 -2
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +11 -12
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnosticEvidence.swift +184 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +6 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +4 -47
- package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +5 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +7 -2
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +53 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashExceptionEvidence.swift +0 -29
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +12 -67
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +1 -0
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +11 -10
- package/package.json +1 -1
|
@@ -33,7 +33,7 @@ Pod::Spec.new do |spec|
|
|
|
33
33
|
"ios/GeneratedAppleDiagnostics/**/*.swift"
|
|
34
34
|
]
|
|
35
35
|
diagnostics.dependency "#{spec.name}/Core"
|
|
36
|
-
diagnostics.dependency "KSCrash/Recording", "2.
|
|
36
|
+
diagnostics.dependency "KSCrash/Recording", "2.6.0"
|
|
37
37
|
diagnostics.pod_target_xcconfig = {
|
|
38
38
|
"DEFINES_MODULE" => "YES",
|
|
39
39
|
"SWIFT_VERSION" => "5.0"
|
package/README.md
CHANGED
|
@@ -335,7 +335,8 @@ the configured project does not replay an earlier project's records with the
|
|
|
335
335
|
new project's key. The raw local KSCrash report can still contain sensitive
|
|
336
336
|
process details, so apply the app's consent and retention policy to it. Hosted
|
|
337
337
|
replay uploads only the bounded crash or hang issue described by the Swift
|
|
338
|
-
`LogBrewCrash` contract
|
|
338
|
+
`LogBrewCrash` contract, including its capture-time service, deployment, OS,
|
|
339
|
+
device-class, and application-version context. Status and error results expose fixed codes and bounded
|
|
339
340
|
counters, not keys, paths, raw reports, or native error text. A custom endpoint
|
|
340
341
|
must be a plain HTTPS path without embedded authentication, a query, or a fragment.
|
|
341
342
|
|
package/index.cjs
CHANGED
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
} = require("./metadata.cjs");
|
|
17
17
|
|
|
18
18
|
const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
19
|
-
const DEFAULT_SDK_VERSION = "0.1.
|
|
19
|
+
const DEFAULT_SDK_VERSION = "0.1.18";
|
|
20
20
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
21
21
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
22
22
|
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|
|
@@ -595,6 +595,7 @@ function createReactNavigationSpanListener(client, navigationContainer, {
|
|
|
595
595
|
|
|
596
596
|
function createReactNativeErrorEvent(error, {
|
|
597
597
|
debugIdMap,
|
|
598
|
+
evidence,
|
|
598
599
|
environment,
|
|
599
600
|
fingerprint,
|
|
600
601
|
handled = true,
|
|
@@ -618,6 +619,7 @@ function createReactNativeErrorEvent(error, {
|
|
|
618
619
|
const traceContext = resolveTraceContext(trace ?? getActiveLogBrewTrace());
|
|
619
620
|
const attributes = createIssueAttributesFromError(error?.reason ?? error?.error ?? error, {
|
|
620
621
|
debugIdMap: debugIdMap === undefined ? runtimeReactNativeDebugIdMap() : debugIdMap,
|
|
622
|
+
evidence,
|
|
621
623
|
environment,
|
|
622
624
|
fingerprint,
|
|
623
625
|
handled,
|
package/index.d.cts
CHANGED
|
@@ -20,9 +20,7 @@ export type ReactNativePlatformLike = {
|
|
|
20
20
|
OS?: string;
|
|
21
21
|
Version?: string | number;
|
|
22
22
|
isPad?: boolean;
|
|
23
|
-
constants?: {
|
|
24
|
-
isTesting?: boolean;
|
|
25
|
-
};
|
|
23
|
+
constants?: { isTesting?: boolean; [key: string]: unknown };
|
|
26
24
|
};
|
|
27
25
|
|
|
28
26
|
export type ReactNativeAppStateLike = {
|
|
@@ -287,6 +285,7 @@ export type ReactNativeErrorIdFactoryContext = {
|
|
|
287
285
|
|
|
288
286
|
export type CaptureReactNativeErrorOptions = ReactNativeContextOptions & {
|
|
289
287
|
debugIdMap?: Record<string, string>;
|
|
288
|
+
evidence?: IssueAttributes["evidence"];
|
|
290
289
|
environment?: string;
|
|
291
290
|
fingerprint?: string;
|
|
292
291
|
handled?: boolean;
|
package/index.d.ts
CHANGED
|
@@ -20,9 +20,7 @@ export type ReactNativePlatformLike = {
|
|
|
20
20
|
OS?: string;
|
|
21
21
|
Version?: string | number;
|
|
22
22
|
isPad?: boolean;
|
|
23
|
-
constants?: {
|
|
24
|
-
isTesting?: boolean;
|
|
25
|
-
};
|
|
23
|
+
constants?: { isTesting?: boolean; [key: string]: unknown };
|
|
26
24
|
};
|
|
27
25
|
|
|
28
26
|
export type ReactNativeAppStateLike = {
|
|
@@ -287,6 +285,7 @@ export type ReactNativeErrorIdFactoryContext = {
|
|
|
287
285
|
|
|
288
286
|
export type CaptureReactNativeErrorOptions = ReactNativeContextOptions & {
|
|
289
287
|
debugIdMap?: Record<string, string>;
|
|
288
|
+
evidence?: IssueAttributes["evidence"];
|
|
290
289
|
environment?: string;
|
|
291
290
|
fingerprint?: string;
|
|
292
291
|
handled?: boolean;
|
package/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "./metadata.js";
|
|
17
17
|
|
|
18
18
|
const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
19
|
-
const DEFAULT_SDK_VERSION = "0.1.
|
|
19
|
+
const DEFAULT_SDK_VERSION = "0.1.18";
|
|
20
20
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
21
21
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
22
22
|
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|
|
@@ -584,6 +584,7 @@ export function createReactNavigationSpanListener(client, navigationContainer, {
|
|
|
584
584
|
|
|
585
585
|
export function createReactNativeErrorEvent(error, {
|
|
586
586
|
debugIdMap,
|
|
587
|
+
evidence,
|
|
587
588
|
environment,
|
|
588
589
|
fingerprint,
|
|
589
590
|
handled = true,
|
|
@@ -607,6 +608,7 @@ export function createReactNativeErrorEvent(error, {
|
|
|
607
608
|
const traceContext = resolveTraceContext(trace ?? getActiveLogBrewTrace());
|
|
608
609
|
const attributes = createIssueAttributesFromError(error?.reason ?? error?.error ?? error, {
|
|
609
610
|
debugIdMap: debugIdMap === undefined ? runtimeReactNativeDebugIdMap() : debugIdMap,
|
|
611
|
+
evidence,
|
|
610
612
|
environment,
|
|
611
613
|
fingerprint,
|
|
612
614
|
handled,
|
|
@@ -3,7 +3,7 @@ import Foundation
|
|
|
3
3
|
@objc(LBRNAppleNativeDiagnostics)
|
|
4
4
|
public final class LBRNAppleNativeDiagnostics: NSObject, @unchecked Sendable {
|
|
5
5
|
private static let shared = LBRNAppleNativeDiagnostics()
|
|
6
|
-
private static let sdkVersion = "0.1.
|
|
6
|
+
private static let sdkVersion = "0.1.18"
|
|
7
7
|
|
|
8
8
|
private let lock = NSLock()
|
|
9
9
|
private let replayQueue = DispatchQueue(label: "co.logbrew.react-native.apple-diagnostics-replay")
|
|
@@ -76,7 +76,7 @@ final class DurableDeliveryStore: @unchecked Sendable {
|
|
|
76
76
|
recoveredEvents = recovery.events
|
|
77
77
|
recoveredPrefix = recovery.prefix
|
|
78
78
|
if let last = recovery.events.last {
|
|
79
|
-
let lastSequence = try Self.sequence(from: last)
|
|
79
|
+
let lastSequence = try Self.sequence(from: last.recordName)
|
|
80
80
|
guard lastSequence < UInt64.max else {
|
|
81
81
|
throw DurableStoreFailure.corrupt
|
|
82
82
|
}
|
|
@@ -109,7 +109,7 @@ final class DurableDeliveryStore: @unchecked Sendable {
|
|
|
109
109
|
throw DurableStoreFailure.corrupt
|
|
110
110
|
}
|
|
111
111
|
let name = Self.eventName(sequence: nextSequence)
|
|
112
|
-
let eventData = try Self.
|
|
112
|
+
let eventData = try Self.encode(event)
|
|
113
113
|
guard eventData.count == encodedBytes else {
|
|
114
114
|
throw DurableStoreFailure.corrupt
|
|
115
115
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
2
|
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
-
|
|
3
|
+
#if canImport(Musl)
|
|
4
|
+
import Musl
|
|
5
|
+
#elseif canImport(Glibc)
|
|
6
|
+
import Glibc
|
|
7
|
+
#elseif canImport(Darwin)
|
|
8
|
+
import Darwin
|
|
9
|
+
#else
|
|
10
|
+
#error("LogBrew durable delivery requires a POSIX platform")
|
|
11
|
+
#endif
|
|
4
12
|
import Foundation
|
|
5
13
|
|
|
6
14
|
extension DurableDeliveryStore {
|
|
@@ -167,13 +175,8 @@ extension DurableDeliveryStore {
|
|
|
167
175
|
String(format: "event-%020llu.json", sequence)
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
static func sequence(from recovered: RecoveredEvent) throws -> UInt64 {
|
|
171
|
-
try sequence(from: recovered.recordName)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
178
|
static func sequence(from name: String) throws -> UInt64 {
|
|
175
|
-
let prefix = "event-"
|
|
176
|
-
let suffix = ".json"
|
|
179
|
+
let (prefix, suffix) = ("event-", ".json")
|
|
177
180
|
guard name.hasPrefix(prefix), name.hasSuffix(suffix) else {
|
|
178
181
|
throw DurableStoreFailure.corrupt
|
|
179
182
|
}
|
|
@@ -204,10 +207,6 @@ extension DurableDeliveryStore {
|
|
|
204
207
|
}
|
|
205
208
|
}
|
|
206
209
|
|
|
207
|
-
static func encodeEvent(_ event: Event) throws -> Data {
|
|
208
|
-
try encode(event)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
210
|
static func checksum(_ data: Data) -> String {
|
|
212
211
|
var hash: UInt64 = 14_695_981_039_346_656_037
|
|
213
212
|
for byte in data {
|
|
@@ -259,7 +258,7 @@ extension DurableDeliveryStore {
|
|
|
259
258
|
throw DurableStoreFailure.corrupt
|
|
260
259
|
}
|
|
261
260
|
let record = try decodeEventRecord(from: file)
|
|
262
|
-
let encodedEvent = try
|
|
261
|
+
let encodedEvent = try encode(record.event)
|
|
263
262
|
guard record.version == version,
|
|
264
263
|
record.sequence == sequence,
|
|
265
264
|
record.sdk == sdk,
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
/// App-reported code location that narrows the smallest likely fix area.
|
|
6
|
+
public struct IssueLikelyFixArea: Codable, Equatable, Sendable {
|
|
7
|
+
public let component: String?
|
|
8
|
+
public let module: String?
|
|
9
|
+
public let function: String?
|
|
10
|
+
public let file: String?
|
|
11
|
+
public let line: Int?
|
|
12
|
+
public let column: Int?
|
|
13
|
+
public let inApp: Bool?
|
|
14
|
+
|
|
15
|
+
public init(
|
|
16
|
+
component: String? = nil,
|
|
17
|
+
module: String? = nil,
|
|
18
|
+
function: String? = nil,
|
|
19
|
+
file: String? = nil,
|
|
20
|
+
line: Int? = nil,
|
|
21
|
+
column: Int? = nil,
|
|
22
|
+
inApp: Bool? = nil,
|
|
23
|
+
) {
|
|
24
|
+
self.component = component
|
|
25
|
+
self.module = module
|
|
26
|
+
self.function = function
|
|
27
|
+
self.file = file
|
|
28
|
+
self.line = line
|
|
29
|
+
self.column = column
|
|
30
|
+
self.inApp = inApp
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// App-reported impact without user identities or raw request data.
|
|
35
|
+
public struct IssueImpactEvidence: Codable, Equatable, Sendable {
|
|
36
|
+
public let affectedUserSegment: String?
|
|
37
|
+
public let failedAction: String?
|
|
38
|
+
public let userVisibleOutcome: String?
|
|
39
|
+
|
|
40
|
+
public init(
|
|
41
|
+
affectedUserSegment: String? = nil,
|
|
42
|
+
failedAction: String? = nil,
|
|
43
|
+
userVisibleOutcome: String? = nil,
|
|
44
|
+
) {
|
|
45
|
+
self.affectedUserSegment = affectedUserSegment
|
|
46
|
+
self.failedAction = failedAction
|
|
47
|
+
self.userVisibleOutcome = userVisibleOutcome
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Explicit app-reported cause, fix, impact, and capture-state evidence.
|
|
52
|
+
public struct IssueDiagnosticEvidence: Codable, Equatable, Sendable {
|
|
53
|
+
public let likelyRootCause: String?
|
|
54
|
+
public let likelyFixArea: IssueLikelyFixArea?
|
|
55
|
+
public let impact: IssueImpactEvidence?
|
|
56
|
+
public let capturedFields: [String]?
|
|
57
|
+
public let missingFields: [String]?
|
|
58
|
+
public let redactedFields: [String]?
|
|
59
|
+
public let truncatedFields: [String]?
|
|
60
|
+
|
|
61
|
+
public init(
|
|
62
|
+
likelyRootCause: String? = nil,
|
|
63
|
+
likelyFixArea: IssueLikelyFixArea? = nil,
|
|
64
|
+
impact: IssueImpactEvidence? = nil,
|
|
65
|
+
capturedFields: [String]? = nil,
|
|
66
|
+
missingFields: [String]? = nil,
|
|
67
|
+
redactedFields: [String]? = nil,
|
|
68
|
+
truncatedFields: [String]? = nil,
|
|
69
|
+
) {
|
|
70
|
+
self.likelyRootCause = likelyRootCause
|
|
71
|
+
self.likelyFixArea = likelyFixArea
|
|
72
|
+
self.impact = impact
|
|
73
|
+
self.capturedFields = capturedFields
|
|
74
|
+
self.missingFields = missingFields
|
|
75
|
+
self.redactedFields = redactedFields
|
|
76
|
+
self.truncatedFields = truncatedFields
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func validateIssueDiagnosticEvidence(_ value: IssueDiagnosticEvidence) throws -> IssueDiagnosticEvidence {
|
|
81
|
+
let cause = try value.likelyRootCause.map {
|
|
82
|
+
try issueText($0, label: "issue evidence likelyRootCause", maximum: 1024)
|
|
83
|
+
}
|
|
84
|
+
let fixArea = try value.likelyFixArea.map(validateIssueLikelyFixArea)
|
|
85
|
+
let impact = try value.impact.map(validateIssueImpactEvidence)
|
|
86
|
+
var seen = Set<String>()
|
|
87
|
+
let fieldLists = try [
|
|
88
|
+
("capturedFields", value.capturedFields),
|
|
89
|
+
("missingFields", value.missingFields),
|
|
90
|
+
("redactedFields", value.redactedFields),
|
|
91
|
+
("truncatedFields", value.truncatedFields),
|
|
92
|
+
].map { name, fields in
|
|
93
|
+
try fields.map { try validateIssueEvidenceFields($0, name: name, seen: &seen) }
|
|
94
|
+
}
|
|
95
|
+
guard cause != nil || fixArea != nil || impact != nil || fieldLists.contains(where: { $0 != nil }) else {
|
|
96
|
+
throw issueValidationError("issue evidence must contain at least one field")
|
|
97
|
+
}
|
|
98
|
+
return IssueDiagnosticEvidence(
|
|
99
|
+
likelyRootCause: cause,
|
|
100
|
+
likelyFixArea: fixArea,
|
|
101
|
+
impact: impact,
|
|
102
|
+
capturedFields: fieldLists[0],
|
|
103
|
+
missingFields: fieldLists[1],
|
|
104
|
+
redactedFields: fieldLists[2],
|
|
105
|
+
truncatedFields: fieldLists[3],
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private func validateIssueLikelyFixArea(_ value: IssueLikelyFixArea) throws -> IssueLikelyFixArea {
|
|
110
|
+
let identities = try [value.component, value.module, value.function].map { identity in
|
|
111
|
+
try identity.map {
|
|
112
|
+
try issueText($0, label: "issue evidence likelyFixArea identity", maximum: 256,
|
|
113
|
+
disallowLocationDelimiters: true)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let file = try value.file.map(validateIssueRelativePath)
|
|
117
|
+
let coordinates = try [value.line, value.column].map { coordinate in
|
|
118
|
+
try coordinate.map {
|
|
119
|
+
guard (1 ... Int(Int32.max)).contains($0) else {
|
|
120
|
+
throw issueValidationError("issue evidence likelyFixArea coordinate must be a positive integer")
|
|
121
|
+
}
|
|
122
|
+
return $0
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
guard identities.contains(where: { $0 != nil }) || file != nil || coordinates.contains(where: { $0 != nil }) else {
|
|
126
|
+
throw issueValidationError("issue evidence likelyFixArea must identify a code location")
|
|
127
|
+
}
|
|
128
|
+
return IssueLikelyFixArea(
|
|
129
|
+
component: identities[0], module: identities[1], function: identities[2], file: file,
|
|
130
|
+
line: coordinates[0], column: coordinates[1], inApp: value.inApp,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func validateIssueImpactEvidence(_ value: IssueImpactEvidence) throws -> IssueImpactEvidence {
|
|
135
|
+
let values = try [
|
|
136
|
+
(value.affectedUserSegment, 256, true),
|
|
137
|
+
(value.failedAction, 256, true),
|
|
138
|
+
(value.userVisibleOutcome, 512, false),
|
|
139
|
+
].map { text, maximum, rejectLocation in
|
|
140
|
+
try text.map {
|
|
141
|
+
try issueText($0, label: "issue evidence impact", maximum: maximum,
|
|
142
|
+
disallowLocationDelimiters: rejectLocation)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
guard values.contains(where: { $0 != nil }) else {
|
|
146
|
+
throw issueValidationError("issue evidence impact must contain at least one field")
|
|
147
|
+
}
|
|
148
|
+
return IssueImpactEvidence(
|
|
149
|
+
affectedUserSegment: values[0], failedAction: values[1], userVisibleOutcome: values[2],
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private func validateIssueEvidenceFields(
|
|
154
|
+
_ fields: [String],
|
|
155
|
+
name: String,
|
|
156
|
+
seen: inout Set<String>,
|
|
157
|
+
) throws -> [String] {
|
|
158
|
+
guard (1 ... 32).contains(fields.count), Set(fields).count == fields.count,
|
|
159
|
+
fields.allSatisfy({ validMachineKey($0, maximum: 64, separators: ["_", ".", "-"], allowNumericStart: true) })
|
|
160
|
+
else {
|
|
161
|
+
throw issueValidationError("issue evidence \(name) must contain 1-32 unique bounded fields")
|
|
162
|
+
}
|
|
163
|
+
for field in fields where !seen.insert(field).inserted {
|
|
164
|
+
throw issueValidationError("issue evidence field \(field) has conflicting states")
|
|
165
|
+
}
|
|
166
|
+
return fields
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private func validateIssueRelativePath(_ value: String) throws -> String {
|
|
170
|
+
let path = try issueText(
|
|
171
|
+
value.replacingOccurrences(of: "\\", with: "/"),
|
|
172
|
+
label: "issue evidence likelyFixArea file",
|
|
173
|
+
maximum: 256,
|
|
174
|
+
disallowLocationDelimiters: true,
|
|
175
|
+
)
|
|
176
|
+
let parts = path.split(separator: "/", omittingEmptySubsequences: false)
|
|
177
|
+
let drivePath = path.count >= 3 && path[path.index(after: path.startIndex)] == ":"
|
|
178
|
+
guard !path.hasPrefix("/"), !drivePath, !path.contains("://"),
|
|
179
|
+
parts.allSatisfy({ !$0.isEmpty && $0 != "." && $0 != ".." })
|
|
180
|
+
else {
|
|
181
|
+
throw issueValidationError("issue evidence likelyFixArea file must be a safe relative path")
|
|
182
|
+
}
|
|
183
|
+
return path
|
|
184
|
+
}
|
|
@@ -101,6 +101,7 @@ public extension IssueAttributes {
|
|
|
101
101
|
handled: Bool = true,
|
|
102
102
|
breadcrumbs: [IssueBreadcrumb]? = nil,
|
|
103
103
|
breadcrumbsTruncated: Bool? = nil,
|
|
104
|
+
evidence: IssueDiagnosticEvidence? = nil,
|
|
104
105
|
metadata: Metadata? = nil,
|
|
105
106
|
context: TelemetryContext? = nil,
|
|
106
107
|
fileID: String = #fileID,
|
|
@@ -136,6 +137,7 @@ public extension IssueAttributes {
|
|
|
136
137
|
stackFrames: [rootFrame],
|
|
137
138
|
breadcrumbs: breadcrumbs,
|
|
138
139
|
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
140
|
+
evidence: evidence,
|
|
139
141
|
metadata: metadata,
|
|
140
142
|
context: context,
|
|
141
143
|
)
|
|
@@ -186,6 +188,7 @@ func normalizeIssueAttributes(_ value: IssueAttributes) throws -> IssueAttribute
|
|
|
186
188
|
}
|
|
187
189
|
let breadcrumbs = try normalizeIssueBreadcrumbs(value.breadcrumbs)
|
|
188
190
|
let breadcrumbsWereCapped = (value.breadcrumbs?.count ?? 0) > maximumIssueBreadcrumbs
|
|
191
|
+
let evidence = try value.evidence.map(validateIssueDiagnosticEvidence)
|
|
189
192
|
let context = try value.context.map { try validateTelemetryContext($0) }
|
|
190
193
|
try validateMetadata(value.metadata, label: "issue metadata")
|
|
191
194
|
return IssueAttributes(
|
|
@@ -197,6 +200,7 @@ func normalizeIssueAttributes(_ value: IssueAttributes) throws -> IssueAttribute
|
|
|
197
200
|
stackFrames: stackFrames,
|
|
198
201
|
breadcrumbs: breadcrumbs,
|
|
199
202
|
breadcrumbsTruncated: breadcrumbsWereCapped ? true : value.breadcrumbsTruncated,
|
|
203
|
+
evidence: evidence,
|
|
200
204
|
metadata: value.metadata,
|
|
201
205
|
context: context,
|
|
202
206
|
nativeStackFrames: value.nativeStackFrames,
|
|
@@ -224,6 +228,7 @@ func issueAttributes(
|
|
|
224
228
|
breadcrumbsTruncated: value.breadcrumbsTruncated == true
|
|
225
229
|
|| snapshot.truncated
|
|
226
230
|
|| combined.count > maximumIssueBreadcrumbs,
|
|
231
|
+
evidence: value.evidence,
|
|
227
232
|
metadata: value.metadata,
|
|
228
233
|
context: value.context,
|
|
229
234
|
nativeStackFrames: value.nativeStackFrames,
|
|
@@ -377,7 +382,7 @@ func issueText(
|
|
|
377
382
|
disallowLocationDelimiters: Bool = false,
|
|
378
383
|
) throws -> String {
|
|
379
384
|
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
380
|
-
guard !normalized.isEmpty, normalized.count <= maximum, !containsForbiddenControl(normalized),
|
|
385
|
+
guard !normalized.isEmpty, normalized.unicodeScalars.count <= maximum, !containsForbiddenControl(normalized),
|
|
381
386
|
!disallowLocationDelimiters || (!normalized.contains("?") && !normalized.contains("#"))
|
|
382
387
|
else {
|
|
383
388
|
throw issueValidationError("\(label) is invalid")
|
|
@@ -64,7 +64,6 @@ public final class LogBrewClient: @unchecked Sendable {
|
|
|
64
64
|
let context = try resolvedContext(event: attributes.context)
|
|
65
65
|
let attributes = issueAttributes(attributes, adding: breadcrumbStore.snapshot())
|
|
66
66
|
.withContext(context)
|
|
67
|
-
.withCorrelationMetadata()
|
|
68
67
|
try pushEvent(.issue(validateIssue(attributes)), id: id, timestamp: timestamp)
|
|
69
68
|
}
|
|
70
69
|
|
|
@@ -76,7 +75,7 @@ public final class LogBrewClient: @unchecked Sendable {
|
|
|
76
75
|
public func log(_ id: String, timestamp: String, attributes: LogAttributes) throws {
|
|
77
76
|
let context = try resolvedContext(event: attributes.context)
|
|
78
77
|
try pushEvent(
|
|
79
|
-
.log(validateLog(attributes.withContext(context)
|
|
78
|
+
.log(validateLog(attributes.withContext(context))),
|
|
80
79
|
id: id,
|
|
81
80
|
timestamp: timestamp,
|
|
82
81
|
)
|
|
@@ -103,7 +102,7 @@ public final class LogBrewClient: @unchecked Sendable {
|
|
|
103
102
|
public func action(_ id: String, timestamp: String, attributes: ActionAttributes) throws {
|
|
104
103
|
let context = try resolvedContext(event: attributes.context)
|
|
105
104
|
try pushEvent(
|
|
106
|
-
.action(validateAction(attributes.withContext(context)
|
|
105
|
+
.action(validateAction(attributes.withContext(context))),
|
|
107
106
|
id: id,
|
|
108
107
|
timestamp: timestamp,
|
|
109
108
|
)
|
|
@@ -112,7 +111,7 @@ public final class LogBrewClient: @unchecked Sendable {
|
|
|
112
111
|
public func metric(_ id: String, timestamp: String, attributes: MetricAttributes) throws {
|
|
113
112
|
let context = try resolvedContext(event: attributes.context)
|
|
114
113
|
try pushEvent(
|
|
115
|
-
.metric(validateMetric(attributes.withContext(context)
|
|
114
|
+
.metric(validateMetric(attributes.withContext(context))),
|
|
116
115
|
id: id,
|
|
117
116
|
timestamp: timestamp,
|
|
118
117
|
)
|
|
@@ -209,22 +208,7 @@ private extension IssueAttributes {
|
|
|
209
208
|
stackFrames: stackFrames,
|
|
210
209
|
breadcrumbs: breadcrumbs,
|
|
211
210
|
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
212
|
-
|
|
213
|
-
context: context,
|
|
214
|
-
nativeStackFrames: nativeStackFrames,
|
|
215
|
-
)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
func withCorrelationMetadata() -> IssueAttributes {
|
|
219
|
-
IssueAttributes(
|
|
220
|
-
title: title,
|
|
221
|
-
level: level,
|
|
222
|
-
message: message,
|
|
223
|
-
exception: exception,
|
|
224
|
-
exceptionChain: exceptionChain,
|
|
225
|
-
stackFrames: stackFrames,
|
|
226
|
-
breadcrumbs: breadcrumbs,
|
|
227
|
-
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
211
|
+
evidence: evidence,
|
|
228
212
|
metadata: correlationMetadata(metadata, context: context),
|
|
229
213
|
context: context,
|
|
230
214
|
nativeStackFrames: nativeStackFrames,
|
|
@@ -234,16 +218,6 @@ private extension IssueAttributes {
|
|
|
234
218
|
|
|
235
219
|
private extension LogAttributes {
|
|
236
220
|
func withContext(_ context: TelemetryContext?) -> LogAttributes {
|
|
237
|
-
LogAttributes(
|
|
238
|
-
message: message,
|
|
239
|
-
level: level,
|
|
240
|
-
logger: logger,
|
|
241
|
-
metadata: metadata,
|
|
242
|
-
context: context,
|
|
243
|
-
)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
func withCorrelationMetadata() -> LogAttributes {
|
|
247
221
|
LogAttributes(
|
|
248
222
|
message: message,
|
|
249
223
|
level: level,
|
|
@@ -256,10 +230,6 @@ private extension LogAttributes {
|
|
|
256
230
|
|
|
257
231
|
private extension ActionAttributes {
|
|
258
232
|
func withContext(_ context: TelemetryContext?) -> ActionAttributes {
|
|
259
|
-
ActionAttributes(name: name, status: status, metadata: metadata, context: context)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
func withCorrelationMetadata() -> ActionAttributes {
|
|
263
233
|
ActionAttributes(
|
|
264
234
|
name: name,
|
|
265
235
|
status: status,
|
|
@@ -271,19 +241,6 @@ private extension ActionAttributes {
|
|
|
271
241
|
|
|
272
242
|
private extension MetricAttributes {
|
|
273
243
|
func withContext(_ context: TelemetryContext?) -> MetricAttributes {
|
|
274
|
-
MetricAttributes(
|
|
275
|
-
name: name,
|
|
276
|
-
kind: kind,
|
|
277
|
-
value: value,
|
|
278
|
-
unit: unit,
|
|
279
|
-
temporality: temporality,
|
|
280
|
-
metadata: metadata,
|
|
281
|
-
context: context,
|
|
282
|
-
description: description,
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
func withCorrelationMetadata() -> MetricAttributes {
|
|
287
244
|
MetricAttributes(
|
|
288
245
|
name: name,
|
|
289
246
|
kind: kind,
|
|
@@ -175,6 +175,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
175
175
|
public let stackFrames: [IssueStackFrame]?
|
|
176
176
|
public let breadcrumbs: [IssueBreadcrumb]?
|
|
177
177
|
public let breadcrumbsTruncated: Bool?
|
|
178
|
+
public let evidence: IssueDiagnosticEvidence?
|
|
178
179
|
public let metadata: Metadata?
|
|
179
180
|
public let context: TelemetryContext?
|
|
180
181
|
@_spi(CrashReplay) public let nativeStackFrames: [NativeStackFrame]?
|
|
@@ -188,6 +189,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
188
189
|
stackFrames: [IssueStackFrame]? = nil,
|
|
189
190
|
breadcrumbs: [IssueBreadcrumb]? = nil,
|
|
190
191
|
breadcrumbsTruncated: Bool? = nil,
|
|
192
|
+
evidence: IssueDiagnosticEvidence? = nil,
|
|
191
193
|
metadata: Metadata? = nil,
|
|
192
194
|
context: TelemetryContext? = nil,
|
|
193
195
|
) {
|
|
@@ -199,6 +201,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
199
201
|
self.stackFrames = stackFrames
|
|
200
202
|
self.breadcrumbs = breadcrumbs
|
|
201
203
|
self.breadcrumbsTruncated = breadcrumbsTruncated
|
|
204
|
+
self.evidence = evidence
|
|
202
205
|
self.metadata = metadata
|
|
203
206
|
self.context = context
|
|
204
207
|
nativeStackFrames = nil
|
|
@@ -214,6 +217,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
214
217
|
stackFrames: [IssueStackFrame]? = nil,
|
|
215
218
|
breadcrumbs: [IssueBreadcrumb]? = nil,
|
|
216
219
|
breadcrumbsTruncated: Bool? = nil,
|
|
220
|
+
evidence: IssueDiagnosticEvidence? = nil,
|
|
217
221
|
metadata: Metadata? = nil,
|
|
218
222
|
context: TelemetryContext? = nil,
|
|
219
223
|
nativeStackFrames: [NativeStackFrame]?,
|
|
@@ -226,6 +230,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
226
230
|
self.stackFrames = stackFrames
|
|
227
231
|
self.breadcrumbs = breadcrumbs
|
|
228
232
|
self.breadcrumbsTruncated = breadcrumbsTruncated
|
|
233
|
+
self.evidence = evidence
|
|
229
234
|
self.metadata = metadata
|
|
230
235
|
self.context = context
|
|
231
236
|
self.nativeStackFrames = nativeStackFrames
|
|
@@ -81,9 +81,14 @@ func validateTelemetryTraceContext(
|
|
|
81
81
|
)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
func validMachineKey(
|
|
84
|
+
func validMachineKey(
|
|
85
|
+
_ value: String,
|
|
86
|
+
maximum: Int,
|
|
87
|
+
separators: Set<Character>,
|
|
88
|
+
allowNumericStart: Bool = false,
|
|
89
|
+
) -> Bool {
|
|
85
90
|
guard !value.isEmpty, value.count <= maximum, value.first?.isASCII == true,
|
|
86
|
-
value.first?.isLetter == true
|
|
91
|
+
value.first?.isLetter == true || allowNumericStart && value.first?.isNumber == true
|
|
87
92
|
else {
|
|
88
93
|
return false
|
|
89
94
|
}
|
|
@@ -28,6 +28,10 @@ struct CrashReportSanitizer {
|
|
|
28
28
|
mechanism: mechanism(error?["type"] as? String),
|
|
29
29
|
nativeStackFrames: nativeStackFrames,
|
|
30
30
|
artifactIdentity: artifactIdentity,
|
|
31
|
+
context: nativeCrashContext(
|
|
32
|
+
system: rawReport["system"] as? [String: Any],
|
|
33
|
+
artifactIdentity: artifactIdentity,
|
|
34
|
+
),
|
|
31
35
|
hangState: nil,
|
|
32
36
|
hangDurationMs: nil,
|
|
33
37
|
source: .engine(reportID: reportID),
|
|
@@ -70,6 +74,55 @@ struct CrashReportSanitizer {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
func nativeCrashContext(
|
|
78
|
+
system: [String: Any]?,
|
|
79
|
+
artifactIdentity: NativeArtifactIdentity?,
|
|
80
|
+
) -> TelemetryContext? {
|
|
81
|
+
let value = { (key: String) in crashContextValue(system?[key]) }
|
|
82
|
+
let operatingSystem = value("system_name").map {
|
|
83
|
+
TelemetryOperatingSystem(name: $0, version: value("system_version"), build: value("os_version"))
|
|
84
|
+
}
|
|
85
|
+
let family = value("model")
|
|
86
|
+
let model = value("machine")
|
|
87
|
+
let architecture = value("cpu_arch")
|
|
88
|
+
let device = family == nil && model == nil && architecture == nil
|
|
89
|
+
? nil
|
|
90
|
+
: TelemetryDevice(family: family, model: model, architecture: architecture)
|
|
91
|
+
let applicationName = value("CFBundleName")
|
|
92
|
+
let applicationVersion = value("CFBundleShortVersionString")
|
|
93
|
+
let applicationBuild = value("CFBundleVersion")
|
|
94
|
+
let application = applicationName == nil && applicationVersion == nil && applicationBuild == nil
|
|
95
|
+
? nil
|
|
96
|
+
: TelemetryApplication(name: applicationName, version: applicationVersion, build: applicationBuild)
|
|
97
|
+
guard artifactIdentity != nil || operatingSystem != nil || device != nil || application != nil else {
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
return TelemetryContext(resource: TelemetryResource(
|
|
101
|
+
service: artifactIdentity.map { TelemetryNamedVersion(name: $0.service) },
|
|
102
|
+
deployment: artifactIdentity.map {
|
|
103
|
+
TelemetryDeployment(environment: $0.environment, release: $0.release)
|
|
104
|
+
},
|
|
105
|
+
operatingSystem: operatingSystem,
|
|
106
|
+
device: device,
|
|
107
|
+
application: application,
|
|
108
|
+
))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private func crashContextValue(_ rawValue: Any?) -> String? {
|
|
112
|
+
guard let value = rawValue as? String else {
|
|
113
|
+
return nil
|
|
114
|
+
}
|
|
115
|
+
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
116
|
+
guard (1 ... 256).contains(normalized.utf8.count),
|
|
117
|
+
!normalized.unicodeScalars.contains(where: CharacterSet.controlCharacters.contains),
|
|
118
|
+
!normalized.contains("/"),
|
|
119
|
+
!normalized.contains("\\")
|
|
120
|
+
else {
|
|
121
|
+
return nil
|
|
122
|
+
}
|
|
123
|
+
return normalized
|
|
124
|
+
}
|
|
125
|
+
|
|
73
126
|
struct NativeCrashTimestamp: Comparable {
|
|
74
127
|
let normalized: String
|
|
75
128
|
private let wholeSecond: String
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
2
|
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
-
import Foundation
|
|
4
3
|
|
|
5
4
|
func nativeCrashExceptionChain(for exception: IssueException) -> IssueExceptionChain {
|
|
6
5
|
IssueExceptionChain(entries: [
|
|
@@ -14,31 +13,3 @@ func nativeCrashExceptionChain(for exception: IssueException) -> IssueExceptionC
|
|
|
14
13
|
),
|
|
15
14
|
])
|
|
16
15
|
}
|
|
17
|
-
|
|
18
|
-
func nativeCrashExceptionChainMatches(
|
|
19
|
-
_ value: Any?,
|
|
20
|
-
expected exception: IssueException,
|
|
21
|
-
) -> Bool {
|
|
22
|
-
guard let value else {
|
|
23
|
-
// Preserve retry identity for an event queued by an older SDK before chains existed.
|
|
24
|
-
return true
|
|
25
|
-
}
|
|
26
|
-
guard let chain = value as? [String: Any] else {
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
guard let mechanism = exception.mechanism else {
|
|
30
|
-
return false
|
|
31
|
-
}
|
|
32
|
-
let expected = [
|
|
33
|
-
"entries": [[
|
|
34
|
-
"id": 0,
|
|
35
|
-
"relationship": "reported",
|
|
36
|
-
"type": exception.type,
|
|
37
|
-
"messageState": "not_captured",
|
|
38
|
-
"mechanism": ["type": mechanism.type, "handled": mechanism.handled],
|
|
39
|
-
"stackFramesState": "not_captured",
|
|
40
|
-
]],
|
|
41
|
-
"truncated": false,
|
|
42
|
-
] as NSDictionary
|
|
43
|
-
return chain as NSDictionary == expected
|
|
44
|
-
}
|
|
@@ -163,6 +163,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
163
163
|
|
|
164
164
|
private let nativeStackFrames: [NativeStackFrame]?
|
|
165
165
|
private let artifactIdentity: NativeArtifactIdentity?
|
|
166
|
+
private let context: TelemetryContext?
|
|
166
167
|
private let hangState: NativeHangIncidentState?
|
|
167
168
|
private let hangDurationMs: Double?
|
|
168
169
|
let source: NativeCrashRecordSource
|
|
@@ -175,6 +176,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
175
176
|
mechanism: NativeCrashMechanism,
|
|
176
177
|
nativeStackFrames: [NativeStackFrame]?,
|
|
177
178
|
artifactIdentity: NativeArtifactIdentity?,
|
|
179
|
+
context: TelemetryContext?,
|
|
178
180
|
hangState: NativeHangIncidentState?,
|
|
179
181
|
hangDurationMs: Double? = nil,
|
|
180
182
|
source: NativeCrashRecordSource,
|
|
@@ -186,6 +188,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
186
188
|
self.mechanism = mechanism
|
|
187
189
|
self.nativeStackFrames = nativeStackFrames
|
|
188
190
|
self.artifactIdentity = artifactIdentity
|
|
191
|
+
self.context = context
|
|
189
192
|
self.hangState = hangState
|
|
190
193
|
self.hangDurationMs = hangDurationMs
|
|
191
194
|
self.source = source
|
|
@@ -237,6 +240,7 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
237
240
|
exception: issueException,
|
|
238
241
|
exceptionChain: nativeCrashExceptionChain(for: issueException),
|
|
239
242
|
metadata: metadata,
|
|
243
|
+
context: context,
|
|
240
244
|
nativeStackFrames: nativeStackFrames,
|
|
241
245
|
)
|
|
242
246
|
}
|
|
@@ -254,57 +258,13 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
254
258
|
guard event["type"] as? String == "issue",
|
|
255
259
|
event["timestamp"] as? String == timestamp,
|
|
256
260
|
let attributes = event["attributes"] as? [String: Any],
|
|
257
|
-
attributes
|
|
258
|
-
attributes["level"] as? String == issueAttributes.level.canonicalValue,
|
|
259
|
-
attributes["message"] == nil,
|
|
260
|
-
exceptionMatches(attributes["exception"]),
|
|
261
|
-
nativeCrashExceptionChainMatches(
|
|
262
|
-
attributes["exceptionChain"],
|
|
263
|
-
expected: issueException,
|
|
264
|
-
),
|
|
265
|
-
attributes["stackFrames"] == nil,
|
|
266
|
-
attributes["breadcrumbs"] == nil,
|
|
267
|
-
attributes["breadcrumbsTruncated"] == nil,
|
|
268
|
-
attributes["context"] == nil,
|
|
269
|
-
let metadata = attributes["metadata"] as? [String: Any],
|
|
270
|
-
metadata as NSDictionary == expectedMetadata,
|
|
271
|
-
nativeStackFramesMatch(attributes["nativeStackFrames"])
|
|
261
|
+
attributesMatch(attributes)
|
|
272
262
|
else {
|
|
273
263
|
return .collision
|
|
274
264
|
}
|
|
275
265
|
return .matching
|
|
276
266
|
}
|
|
277
267
|
|
|
278
|
-
private var expectedMetadata: NSDictionary {
|
|
279
|
-
var metadata: [String: Any] = [
|
|
280
|
-
"crash.mechanism": mechanism.name,
|
|
281
|
-
"crash.replayed": true,
|
|
282
|
-
]
|
|
283
|
-
if let artifactIdentity {
|
|
284
|
-
metadata["projectId"] = artifactIdentity.projectId
|
|
285
|
-
metadata["release"] = artifactIdentity.release
|
|
286
|
-
metadata["environment"] = artifactIdentity.environment
|
|
287
|
-
metadata["service"] = artifactIdentity.service
|
|
288
|
-
}
|
|
289
|
-
if let hangState {
|
|
290
|
-
metadata["crash.handled"] = hangState == .recovered
|
|
291
|
-
}
|
|
292
|
-
if let hangDurationMs {
|
|
293
|
-
metadata["durationMs"] = hangDurationMs
|
|
294
|
-
}
|
|
295
|
-
return metadata as NSDictionary
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private var expectedException: NSDictionary {
|
|
299
|
-
[
|
|
300
|
-
"type": hangState == nil ? "AppleNativeCrash" : "AppleNativeHang",
|
|
301
|
-
"mechanism": [
|
|
302
|
-
"type": mechanism.name,
|
|
303
|
-
"handled": hangState == .recovered,
|
|
304
|
-
],
|
|
305
|
-
] as NSDictionary
|
|
306
|
-
}
|
|
307
|
-
|
|
308
268
|
private var issueException: IssueException {
|
|
309
269
|
IssueException(
|
|
310
270
|
type: hangState == nil ? "AppleNativeCrash" : "AppleNativeHang",
|
|
@@ -315,31 +275,16 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
315
275
|
)
|
|
316
276
|
}
|
|
317
277
|
|
|
318
|
-
private func
|
|
319
|
-
guard let
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
guard let exception = value as? [String: Any] else {
|
|
324
|
-
return false
|
|
325
|
-
}
|
|
326
|
-
return exception as NSDictionary == expectedException
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private func nativeStackFramesMatch(_ value: Any?) -> Bool {
|
|
330
|
-
guard let nativeStackFrames else {
|
|
331
|
-
return value == nil
|
|
332
|
-
}
|
|
333
|
-
guard let frames = value as? [[String: Any]], frames.count == nativeStackFrames.count else {
|
|
278
|
+
private func attributesMatch(_ actual: [String: Any]) -> Bool {
|
|
279
|
+
guard let encoded = try? JSONEncoder().encode(issueAttributes),
|
|
280
|
+
var expected = try? JSONSerialization.jsonObject(with: encoded) as? [String: Any]
|
|
281
|
+
else {
|
|
334
282
|
return false
|
|
335
283
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
"imageUuid": expected.imageUuid,
|
|
339
|
-
"architecture": expected.architecture.rawValue,
|
|
340
|
-
"instructionOffset": expected.instructionOffset,
|
|
341
|
-
] as NSDictionary
|
|
284
|
+
for legacyField in ["exception", "exceptionChain", "context"] where actual[legacyField] == nil {
|
|
285
|
+
expected.removeValue(forKey: legacyField)
|
|
342
286
|
}
|
|
287
|
+
return actual as NSDictionary == expected as NSDictionary
|
|
343
288
|
}
|
|
344
289
|
}
|
|
345
290
|
|
|
@@ -93,6 +93,7 @@ struct NativeHangIncident: Codable, Equatable {
|
|
|
93
93
|
mechanism: .deadlock,
|
|
94
94
|
nativeStackFrames: nativeStackFrames,
|
|
95
95
|
artifactIdentity: artifactIdentity.validatedIdentity(),
|
|
96
|
+
context: nil,
|
|
96
97
|
hangState: state,
|
|
97
98
|
hangDurationMs: durationMs,
|
|
98
99
|
source: .hang(eventID: eventID),
|
|
@@ -7,35 +7,36 @@
|
|
|
7
7
|
"swift/logbrew-swift/Sources/LogBrew/DeliveryEngineDurable.swift": "b0d2ca07cce7fe01fc110238c7d10aa9c8ada0d28509e4c022e7fa950a4c79b9",
|
|
8
8
|
"swift/logbrew-swift/Sources/LogBrew/DeliveryEngineQueue.swift": "9c8503da4023368922b8b73dd989213685a64312806ecd73d0513958ad85f883",
|
|
9
9
|
"swift/logbrew-swift/Sources/LogBrew/DeliveryLifecycle.swift": "7b76541c2659cecf6614d9d6dcf9b61dc85b7a562d44fda03a026076afa02fcd",
|
|
10
|
-
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStore.swift": "
|
|
11
|
-
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStoreRecovery.swift": "
|
|
10
|
+
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStore.swift": "7c292dda68efc3de790daf44733b25e4392226793343c21ba5df4f51fc1b402b",
|
|
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/
|
|
13
|
+
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnosticEvidence.swift": "e471f27fadaefe06971c1a98892aeb0774e840a2ce0cbb243ae9aabfa41b1c47",
|
|
14
|
+
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnostics.swift": "297b8bbefd42c314a984fecb38237fb1395700c9eafcfdd1b545267bc34294cc",
|
|
14
15
|
"swift/logbrew-swift/Sources/LogBrew/IssueExceptionChain.swift": "10f81cbce95b47da0a44ef9e515b150e60d71dfcc8b04be24b037b1222a373a5",
|
|
15
16
|
"swift/logbrew-swift/Sources/LogBrew/LifecycleTrace.swift": "738cd3f129fac821a892404d1d42ee92bd806b6583d3dbb5379e88b14ab23c16",
|
|
16
|
-
"swift/logbrew-swift/Sources/LogBrew/LogBrewClient.swift": "
|
|
17
|
+
"swift/logbrew-swift/Sources/LogBrew/LogBrewClient.swift": "186cdbda3754a811f17b82818ca8e7a1711b3b75123686cd17dc3b7441fa21cb",
|
|
17
18
|
"swift/logbrew-swift/Sources/LogBrew/LogBrewLogger.swift": "d3266353a5f5139a2a1ca1a86bacce793518eb1489bf67ed9e3720b57b1c29b2",
|
|
18
19
|
"swift/logbrew-swift/Sources/LogBrew/LogBrewTrace.swift": "f67edf4ca464e6a6eb1c7a22cd4997928cd27b6c2d07535855a18a053d8de924",
|
|
19
20
|
"swift/logbrew-swift/Sources/LogBrew/Metadata.swift": "65840ebb3f3b79fbfc1f5faa3026c2583319e89ff26061676dc552c03ee15dbe",
|
|
20
21
|
"swift/logbrew-swift/Sources/LogBrew/NativeStackFrame.swift": "ecbd418969ec4a31503d9916a8bf27f22e6863356c6fabe457c1e61027a8d805",
|
|
21
22
|
"swift/logbrew-swift/Sources/LogBrew/ProductTimeline.swift": "209906aa0e8f096a347e35832840522710eadfb530053253cd73c748518e362d",
|
|
22
|
-
"swift/logbrew-swift/Sources/LogBrew/PublicTypes.swift": "
|
|
23
|
+
"swift/logbrew-swift/Sources/LogBrew/PublicTypes.swift": "a21aee058f716ec9d41d54ad032b2c7a4d64a4b99d749a9fa2be3fd2df5c6568",
|
|
23
24
|
"swift/logbrew-swift/Sources/LogBrew/TelemetryContext.swift": "ce3ac93a62a345f8c9a8f4c69ce84efc991bf02a688f49bad5e68edcff2f6e08",
|
|
24
|
-
"swift/logbrew-swift/Sources/LogBrew/TelemetryContextValidation.swift": "
|
|
25
|
+
"swift/logbrew-swift/Sources/LogBrew/TelemetryContextValidation.swift": "23524d281a185962a2ecc75f1ca1cb775171e2f72ecd39ca325ace7e45082485",
|
|
25
26
|
"swift/logbrew-swift/Sources/LogBrew/TraceEvidence.swift": "40aaa42976164c53d9f4d9e9e30d236534238da43bbe90832a673c8809ad54b6",
|
|
26
27
|
"swift/logbrew-swift/Sources/LogBrew/Transport.swift": "73eec81aaebec4bc922b2a372429c88fcd3e85606a7862cd08e1795eb2a8e6cd",
|
|
27
28
|
"swift/logbrew-swift/Sources/LogBrew/URLSessionTrace.swift": "d0076c72671716c41bd7498d0509e453b8182c418156e169196fee9f26fc66b1",
|
|
28
29
|
"swift/logbrew-swift/Sources/LogBrew/URLSessionTracer.swift": "0cdf08f92d6111c0dc2883b87837b81f69b5c2f9a8e5501780a114c23a741af7",
|
|
29
30
|
"swift/logbrew-swift/Sources/LogBrew/Validation.swift": "07ff5c2daeb829b1df4f50edb0b79727f9a81b1c9b5cde5f995f5ad6ce660aa7",
|
|
30
31
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashEngine.swift": "7a46035fcaa72dd7504b4e274a92070d73dd1c45fe20e008474340d6d7c44ede",
|
|
31
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "
|
|
32
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "8798c8900ea28a85d6909183dc8435bc08da96573b89310d26ed178217fbc04b",
|
|
32
33
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashStorageDirectory.swift": "7cd566703cbf704dc99155451ee93dcace12c35c805abc09a6dcf23975cf43f9",
|
|
33
34
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeArtifactIdentity.swift": "3d785ad717dcf7302451531c18b5e1183983270a6cd78dd543a407e3facc6ae0",
|
|
34
35
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCapture.swift": "e9ab5f1850a096f40592a025d16c5ffa2fdff0222004a37056898cf1cb4b0dd9",
|
|
35
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashExceptionEvidence.swift": "
|
|
36
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "
|
|
36
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashExceptionEvidence.swift": "ce191e46298d91d87b379528d610caa88b924b15a8dd912b4a164b51eb172a7a",
|
|
37
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "7c1d028bea143c6ab34ebddaaaab64aeb69d4d950ec7a42264c567e672991748",
|
|
37
38
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashReplay.swift": "3eb3d93b32cf14de416f56fa30d6d65e6f268b5ec9d1a67081d7525c4efd4f80",
|
|
38
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangIncidentStore.swift": "
|
|
39
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangIncidentStore.swift": "5a8150083f16d6b495c9c63434809dc5f11f13ebb23211f57151bad62040781b",
|
|
39
40
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdog.swift": "37b25904cd6576e20c40500be2c153156403b57ad7b4186a1b6b35b414145ecb",
|
|
40
41
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdogContracts.swift": "59f51554d86c326122bb2bdce47c5a7e01996bd61f2b371eba23855032487b9f",
|
|
41
42
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdogSystem.swift": "b87e3db246691d5b0719898cdc02ba068836647b1ac9339a73cf866eabe235ae",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logbrew/react-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "React Native offline delivery, Apple native diagnostics, screen, error, trace, action, and network timeline helpers for LogBrew.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.cjs",
|