@logbrew/react-native 0.1.13 → 0.1.15
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/index.cjs +4 -1
- package/index.js +4 -1
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +1 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +29 -24
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueExceptionChain.swift +355 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +4 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +8 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +19 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashExceptionEvidence.swift +44 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +16 -7
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +7 -5
- package/metadata.cjs +17 -0
- package/metadata.js +16 -0
- package/package.json +1 -1
package/index.cjs
CHANGED
|
@@ -10,12 +10,13 @@ const {
|
|
|
10
10
|
} = require("@logbrew/sdk");
|
|
11
11
|
const {
|
|
12
12
|
runtimeReactNativeDebugIdMap,
|
|
13
|
+
sanitizeReactNativeIssueExceptionChain,
|
|
13
14
|
sanitizeReactNativeIssueMetadata,
|
|
14
15
|
sanitizeReactNativeIssueStackFrames
|
|
15
16
|
} = require("./metadata.cjs");
|
|
16
17
|
|
|
17
18
|
const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
18
|
-
const DEFAULT_SDK_VERSION = "0.1.
|
|
19
|
+
const DEFAULT_SDK_VERSION = "0.1.15";
|
|
19
20
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
20
21
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
21
22
|
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|
|
@@ -636,11 +637,13 @@ function createReactNativeErrorEvent(error, {
|
|
|
636
637
|
trace: traceContext
|
|
637
638
|
});
|
|
638
639
|
const stackFrames = sanitizeReactNativeIssueStackFrames(attributes.stackFrames);
|
|
640
|
+
const exceptionChain = sanitizeReactNativeIssueExceptionChain(attributes.exceptionChain);
|
|
639
641
|
return {
|
|
640
642
|
id: id ?? idFactory({ error, message: details.message, screen }),
|
|
641
643
|
timestamp: timestamp ?? now(),
|
|
642
644
|
attributes: {
|
|
643
645
|
...attributes,
|
|
646
|
+
...(exceptionChain ? { exceptionChain } : {}),
|
|
644
647
|
...(stackFrames ? { stackFrames } : {}),
|
|
645
648
|
title: `React Native error: ${details.message}`,
|
|
646
649
|
message: details.message,
|
package/index.js
CHANGED
|
@@ -10,12 +10,13 @@ import {
|
|
|
10
10
|
} from "@logbrew/sdk";
|
|
11
11
|
import {
|
|
12
12
|
runtimeReactNativeDebugIdMap,
|
|
13
|
+
sanitizeReactNativeIssueExceptionChain,
|
|
13
14
|
sanitizeReactNativeIssueMetadata,
|
|
14
15
|
sanitizeReactNativeIssueStackFrames
|
|
15
16
|
} from "./metadata.js";
|
|
16
17
|
|
|
17
18
|
const DEFAULT_SDK_NAME = "logbrew-react-native";
|
|
18
|
-
const DEFAULT_SDK_VERSION = "0.1.
|
|
19
|
+
const DEFAULT_SDK_VERSION = "0.1.15";
|
|
19
20
|
const DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events";
|
|
20
21
|
const MAX_ACTION_NAME_LENGTH = 64;
|
|
21
22
|
const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
|
|
@@ -625,11 +626,13 @@ export function createReactNativeErrorEvent(error, {
|
|
|
625
626
|
trace: traceContext
|
|
626
627
|
});
|
|
627
628
|
const stackFrames = sanitizeReactNativeIssueStackFrames(attributes.stackFrames);
|
|
629
|
+
const exceptionChain = sanitizeReactNativeIssueExceptionChain(attributes.exceptionChain);
|
|
628
630
|
return {
|
|
629
631
|
id: id ?? idFactory({ error, message: details.message, screen }),
|
|
630
632
|
timestamp: timestamp ?? now(),
|
|
631
633
|
attributes: {
|
|
632
634
|
...attributes,
|
|
635
|
+
...(exceptionChain ? { exceptionChain } : {}),
|
|
633
636
|
...(stackFrames ? { stackFrames } : {}),
|
|
634
637
|
title: `React Native error: ${details.message}`,
|
|
635
638
|
message: details.message,
|
|
@@ -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.15"
|
|
7
7
|
|
|
8
8
|
private let lock = NSLock()
|
|
9
9
|
private let replayQueue = DispatchQueue(label: "co.logbrew.react-native.apple-diagnostics-replay")
|
|
@@ -110,24 +110,30 @@ public extension IssueAttributes {
|
|
|
110
110
|
) -> IssueAttributes {
|
|
111
111
|
let exceptionType = safeSwiftErrorType(error)
|
|
112
112
|
let fileModule = fileID.split(separator: "/", maxSplits: 1).first.map(String.init)
|
|
113
|
+
let exceptionMechanism = IssueExceptionMechanism(type: mechanism, handled: handled)
|
|
114
|
+
let rootFrame = IssueStackFrame(
|
|
115
|
+
filename: fileID,
|
|
116
|
+
line: line,
|
|
117
|
+
column: column,
|
|
118
|
+
function: function,
|
|
119
|
+
module: fileModule,
|
|
120
|
+
inApp: true,
|
|
121
|
+
)
|
|
113
122
|
return IssueAttributes(
|
|
114
123
|
title: title ?? exceptionType,
|
|
115
124
|
level: level,
|
|
116
125
|
message: message,
|
|
117
126
|
exception: IssueException(
|
|
118
127
|
type: exceptionType,
|
|
119
|
-
mechanism:
|
|
128
|
+
mechanism: exceptionMechanism,
|
|
129
|
+
),
|
|
130
|
+
exceptionChain: swiftExceptionChain(
|
|
131
|
+
from: error,
|
|
132
|
+
rootType: exceptionType,
|
|
133
|
+
rootMechanism: exceptionMechanism,
|
|
134
|
+
rootFrame: rootFrame,
|
|
120
135
|
),
|
|
121
|
-
stackFrames: [
|
|
122
|
-
IssueStackFrame(
|
|
123
|
-
filename: fileID,
|
|
124
|
-
line: line,
|
|
125
|
-
column: column,
|
|
126
|
-
function: function,
|
|
127
|
-
module: fileModule,
|
|
128
|
-
inApp: true,
|
|
129
|
-
),
|
|
130
|
-
],
|
|
136
|
+
stackFrames: [rootFrame],
|
|
131
137
|
breadcrumbs: breadcrumbs,
|
|
132
138
|
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
133
139
|
metadata: metadata,
|
|
@@ -171,6 +177,13 @@ let maximumIssueBreadcrumbs = 64
|
|
|
171
177
|
func normalizeIssueAttributes(_ value: IssueAttributes) throws -> IssueAttributes {
|
|
172
178
|
let exception = try value.exception.map(validateIssueException)
|
|
173
179
|
let stackFrames = try normalizeIssueStackFrames(value.stackFrames)
|
|
180
|
+
let exceptionChain = try value.exceptionChain.map {
|
|
181
|
+
try normalizeIssueExceptionChain(
|
|
182
|
+
$0,
|
|
183
|
+
exception: exception,
|
|
184
|
+
stackFrames: stackFrames,
|
|
185
|
+
)
|
|
186
|
+
}
|
|
174
187
|
let breadcrumbs = try normalizeIssueBreadcrumbs(value.breadcrumbs)
|
|
175
188
|
let breadcrumbsWereCapped = (value.breadcrumbs?.count ?? 0) > maximumIssueBreadcrumbs
|
|
176
189
|
let context = try value.context.map { try validateTelemetryContext($0) }
|
|
@@ -180,6 +193,7 @@ func normalizeIssueAttributes(_ value: IssueAttributes) throws -> IssueAttribute
|
|
|
180
193
|
level: value.level,
|
|
181
194
|
message: value.message,
|
|
182
195
|
exception: exception,
|
|
196
|
+
exceptionChain: exceptionChain,
|
|
183
197
|
stackFrames: stackFrames,
|
|
184
198
|
breadcrumbs: breadcrumbs,
|
|
185
199
|
breadcrumbsTruncated: breadcrumbsWereCapped ? true : value.breadcrumbsTruncated,
|
|
@@ -204,6 +218,7 @@ func issueAttributes(
|
|
|
204
218
|
level: value.level,
|
|
205
219
|
message: value.message,
|
|
206
220
|
exception: value.exception,
|
|
221
|
+
exceptionChain: value.exceptionChain,
|
|
207
222
|
stackFrames: value.stackFrames,
|
|
208
223
|
breadcrumbs: capped,
|
|
209
224
|
breadcrumbsTruncated: value.breadcrumbsTruncated == true
|
|
@@ -256,7 +271,7 @@ func validateIssueBreadcrumb(_ value: IssueBreadcrumb) throws -> IssueBreadcrumb
|
|
|
256
271
|
)
|
|
257
272
|
}
|
|
258
273
|
|
|
259
|
-
|
|
274
|
+
func normalizeIssueStackFrames(_ values: [IssueStackFrame]?) throws -> [IssueStackFrame]? {
|
|
260
275
|
guard let values else {
|
|
261
276
|
return nil
|
|
262
277
|
}
|
|
@@ -355,7 +370,7 @@ private func sanitizedIssueFilename(_ value: String) throws -> String {
|
|
|
355
370
|
)
|
|
356
371
|
}
|
|
357
372
|
|
|
358
|
-
|
|
373
|
+
func issueText(
|
|
359
374
|
_ value: String,
|
|
360
375
|
label: String,
|
|
361
376
|
maximum: Int,
|
|
@@ -370,16 +385,6 @@ private func issueText(
|
|
|
370
385
|
return normalized
|
|
371
386
|
}
|
|
372
387
|
|
|
373
|
-
|
|
374
|
-
let reflected = String(reflecting: Swift.type(of: error))
|
|
375
|
-
let normalized = reflected.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
376
|
-
guard !normalized.isEmpty, !containsForbiddenControl(normalized) else {
|
|
377
|
-
return "Swift.Error"
|
|
378
|
-
}
|
|
379
|
-
let filtered = normalized.filter { $0 != "?" && $0 != "#" }
|
|
380
|
-
return String(filtered.prefix(256))
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
private func issueValidationError(_ message: String) -> SdkError {
|
|
388
|
+
func issueValidationError(_ message: String) -> SdkError {
|
|
384
389
|
SdkError(code: "validation_error", message: message)
|
|
385
390
|
}
|
|
@@ -0,0 +1,355 @@
|
|
|
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
|
+
/// How one runtime exception relates to its earlier parent node.
|
|
6
|
+
public enum IssueExceptionRelationship: String, Codable, Equatable, Sendable {
|
|
7
|
+
case reported
|
|
8
|
+
case cause
|
|
9
|
+
case context
|
|
10
|
+
case aggregateMember = "aggregate_member"
|
|
11
|
+
case suppressed
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// Explicit capture state for one exception message.
|
|
15
|
+
public enum IssueExceptionMessageState: String, Codable, Equatable, Sendable {
|
|
16
|
+
case captured
|
|
17
|
+
case truncated
|
|
18
|
+
case redacted
|
|
19
|
+
case notCaptured = "not_captured"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Explicit capture state for one exception stack.
|
|
23
|
+
public enum IssueExceptionStackFramesState: String, Codable, Equatable, Sendable {
|
|
24
|
+
case captured
|
|
25
|
+
case truncated
|
|
26
|
+
case notCaptured = "not_captured"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// One parent-first runtime exception with its own bounded evidence states.
|
|
30
|
+
public struct IssueExceptionChainEntry: Codable, Equatable, Sendable {
|
|
31
|
+
public let id: Int
|
|
32
|
+
public let parentId: Int?
|
|
33
|
+
public let relationship: IssueExceptionRelationship
|
|
34
|
+
public let type: String
|
|
35
|
+
public let message: String?
|
|
36
|
+
public let messageState: IssueExceptionMessageState
|
|
37
|
+
public let module: String?
|
|
38
|
+
public let mechanism: IssueExceptionMechanism?
|
|
39
|
+
public let stackFrames: [IssueStackFrame]?
|
|
40
|
+
public let stackFramesState: IssueExceptionStackFramesState
|
|
41
|
+
|
|
42
|
+
public init(
|
|
43
|
+
id: Int,
|
|
44
|
+
parentId: Int? = nil,
|
|
45
|
+
relationship: IssueExceptionRelationship,
|
|
46
|
+
type: String,
|
|
47
|
+
message: String? = nil,
|
|
48
|
+
messageState: IssueExceptionMessageState = .notCaptured,
|
|
49
|
+
module: String? = nil,
|
|
50
|
+
mechanism: IssueExceptionMechanism? = nil,
|
|
51
|
+
stackFrames: [IssueStackFrame]? = nil,
|
|
52
|
+
stackFramesState: IssueExceptionStackFramesState = .notCaptured,
|
|
53
|
+
) {
|
|
54
|
+
self.id = id
|
|
55
|
+
self.parentId = parentId
|
|
56
|
+
self.relationship = relationship
|
|
57
|
+
self.type = type
|
|
58
|
+
self.message = message
|
|
59
|
+
self.messageState = messageState
|
|
60
|
+
self.module = module
|
|
61
|
+
self.mechanism = mechanism
|
|
62
|
+
self.stackFrames = stackFrames
|
|
63
|
+
self.stackFramesState = stackFramesState
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// At most eight parent-first runtime exceptions.
|
|
68
|
+
public struct IssueExceptionChain: Codable, Equatable, Sendable {
|
|
69
|
+
public let entries: [IssueExceptionChainEntry]
|
|
70
|
+
public let truncated: Bool
|
|
71
|
+
|
|
72
|
+
public init(entries: [IssueExceptionChainEntry], truncated: Bool = false) {
|
|
73
|
+
self.entries = entries
|
|
74
|
+
self.truncated = truncated
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private struct PendingSwiftException {
|
|
79
|
+
let error: any Error
|
|
80
|
+
let parentId: Int?
|
|
81
|
+
let relationship: IssueExceptionRelationship
|
|
82
|
+
let mechanism: IssueExceptionMechanism
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private let maximumIssueExceptions = 8
|
|
86
|
+
private let multipleUnderlyingErrorsKey = "NSMultipleUnderlyingErrors"
|
|
87
|
+
|
|
88
|
+
func swiftExceptionChain(
|
|
89
|
+
from error: any Error,
|
|
90
|
+
rootType: String,
|
|
91
|
+
rootMechanism: IssueExceptionMechanism,
|
|
92
|
+
rootFrame: IssueStackFrame,
|
|
93
|
+
) -> IssueExceptionChain {
|
|
94
|
+
var pending = [PendingSwiftException(
|
|
95
|
+
error: error,
|
|
96
|
+
parentId: nil,
|
|
97
|
+
relationship: .reported,
|
|
98
|
+
mechanism: rootMechanism,
|
|
99
|
+
)]
|
|
100
|
+
var entries: [IssueExceptionChainEntry] = []
|
|
101
|
+
var seenErrors: [NSError] = []
|
|
102
|
+
var truncated = false
|
|
103
|
+
while !pending.isEmpty {
|
|
104
|
+
let current = pending.removeFirst()
|
|
105
|
+
let nsError = current.error as NSError
|
|
106
|
+
if swiftErrorWasSeen(nsError, in: seenErrors) {
|
|
107
|
+
truncated = true
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
guard entries.count < maximumIssueExceptions else {
|
|
111
|
+
truncated = true
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
seenErrors.append(nsError)
|
|
115
|
+
let id = entries.count
|
|
116
|
+
entries.append(swiftExceptionEntry(current, id: id, rootType: rootType, rootFrame: rootFrame))
|
|
117
|
+
pending.append(contentsOf: swiftPendingChildren(nsError, parentId: id))
|
|
118
|
+
}
|
|
119
|
+
return IssueExceptionChain(entries: entries, truncated: truncated || !pending.isEmpty)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private func swiftErrorWasSeen(_ error: NSError, in seenErrors: [NSError]) -> Bool {
|
|
123
|
+
let identity = ObjectIdentifier(error)
|
|
124
|
+
return seenErrors.contains { ObjectIdentifier($0) == identity }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private func swiftExceptionEntry(
|
|
128
|
+
_ current: PendingSwiftException,
|
|
129
|
+
id: Int,
|
|
130
|
+
rootType: String,
|
|
131
|
+
rootFrame: IssueStackFrame,
|
|
132
|
+
) -> IssueExceptionChainEntry {
|
|
133
|
+
let type = id == 0 ? rootType : safeSwiftErrorType(current.error)
|
|
134
|
+
return IssueExceptionChainEntry(
|
|
135
|
+
id: id,
|
|
136
|
+
parentId: current.parentId,
|
|
137
|
+
relationship: current.relationship,
|
|
138
|
+
type: type,
|
|
139
|
+
messageState: .redacted,
|
|
140
|
+
module: swiftExceptionModule(type),
|
|
141
|
+
mechanism: current.mechanism,
|
|
142
|
+
stackFrames: id == 0 ? [rootFrame] : nil,
|
|
143
|
+
stackFramesState: id == 0 ? .captured : .notCaptured,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private func swiftPendingChildren(
|
|
148
|
+
_ error: NSError,
|
|
149
|
+
parentId: Int,
|
|
150
|
+
) -> [PendingSwiftException] {
|
|
151
|
+
swiftUnderlyingErrors(error).map { child in
|
|
152
|
+
PendingSwiftException(
|
|
153
|
+
error: child.error,
|
|
154
|
+
parentId: parentId,
|
|
155
|
+
relationship: child.relationship,
|
|
156
|
+
mechanism: IssueExceptionMechanism(
|
|
157
|
+
type: child.relationship == .aggregateMember
|
|
158
|
+
? "swift.aggregate_member"
|
|
159
|
+
: "swift.underlying_error",
|
|
160
|
+
handled: true,
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private func swiftUnderlyingErrors(
|
|
167
|
+
_ error: NSError,
|
|
168
|
+
) -> [(error: any Error, relationship: IssueExceptionRelationship)] {
|
|
169
|
+
if let aggregate = error.userInfo[multipleUnderlyingErrorsKey] as? [any Error], !aggregate.isEmpty {
|
|
170
|
+
return aggregate.map { ($0, .aggregateMember) }
|
|
171
|
+
}
|
|
172
|
+
if let underlying = error.userInfo[NSUnderlyingErrorKey] as? any Error {
|
|
173
|
+
return [(underlying, .cause)]
|
|
174
|
+
}
|
|
175
|
+
return []
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func safeSwiftErrorType(_ error: any Error) -> String {
|
|
179
|
+
let reflected = String(reflecting: Swift.type(of: error))
|
|
180
|
+
let normalized = reflected.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
181
|
+
guard !normalized.isEmpty, !containsForbiddenControl(normalized) else {
|
|
182
|
+
return "Swift.Error"
|
|
183
|
+
}
|
|
184
|
+
let filtered = normalized.filter { $0 != "?" && $0 != "#" }
|
|
185
|
+
return String(filtered.prefix(256))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private func swiftExceptionModule(_ type: String) -> String? {
|
|
189
|
+
guard let delimiter = type.lastIndex(of: ".") else {
|
|
190
|
+
return nil
|
|
191
|
+
}
|
|
192
|
+
let value = String(type[..<delimiter])
|
|
193
|
+
return value.isEmpty ? nil : String(value.prefix(512))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func normalizeIssueExceptionChain(
|
|
197
|
+
_ value: IssueExceptionChain,
|
|
198
|
+
exception: IssueException?,
|
|
199
|
+
stackFrames: [IssueStackFrame]?,
|
|
200
|
+
) throws -> IssueExceptionChain {
|
|
201
|
+
guard (1 ... maximumIssueExceptions).contains(value.entries.count) else {
|
|
202
|
+
throw issueValidationError("issue exceptionChain entries must contain 1-8 exceptions")
|
|
203
|
+
}
|
|
204
|
+
let entries = try value.entries.enumerated().map { index, entry in
|
|
205
|
+
try validateExceptionChainTopology(entry, at: index)
|
|
206
|
+
return try normalizeIssueExceptionChainEntry(entry)
|
|
207
|
+
}
|
|
208
|
+
try validateReportedException(entries[0], exception: exception, stackFrames: stackFrames)
|
|
209
|
+
return IssueExceptionChain(entries: entries, truncated: value.truncated)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private func validateExceptionChainTopology(
|
|
213
|
+
_ entry: IssueExceptionChainEntry,
|
|
214
|
+
at index: Int,
|
|
215
|
+
) throws {
|
|
216
|
+
guard entry.id == index else {
|
|
217
|
+
throw issueValidationError(
|
|
218
|
+
"issue exceptionChain ids must be contiguous and match array order",
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
if index == 0 {
|
|
222
|
+
guard entry.relationship == .reported, entry.parentId == nil else {
|
|
223
|
+
throw issueValidationError(
|
|
224
|
+
"issue exceptionChain entry 0 must be the parentless reported exception",
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
guard entry.relationship != .reported,
|
|
230
|
+
let parentId = entry.parentId,
|
|
231
|
+
parentId >= 0,
|
|
232
|
+
parentId < index
|
|
233
|
+
else {
|
|
234
|
+
throw issueValidationError("issue exceptionChain parent relationship is invalid")
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private func validateReportedException(
|
|
239
|
+
_ root: IssueExceptionChainEntry,
|
|
240
|
+
exception: IssueException?,
|
|
241
|
+
stackFrames: [IssueStackFrame]?,
|
|
242
|
+
) throws {
|
|
243
|
+
guard let exception,
|
|
244
|
+
root.type == exception.type,
|
|
245
|
+
root.mechanism == exception.mechanism
|
|
246
|
+
else {
|
|
247
|
+
throw issueValidationError(
|
|
248
|
+
"issue exceptionChain reported exception must match exception",
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
let stackMatches = switch root.stackFramesState {
|
|
252
|
+
case .notCaptured:
|
|
253
|
+
stackFrames == nil
|
|
254
|
+
case .captured, .truncated:
|
|
255
|
+
root.stackFrames == stackFrames
|
|
256
|
+
}
|
|
257
|
+
guard stackMatches else {
|
|
258
|
+
throw issueValidationError(
|
|
259
|
+
"issue exceptionChain reported stack must match stackFrames",
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private func normalizeIssueExceptionChainEntry(
|
|
265
|
+
_ value: IssueExceptionChainEntry,
|
|
266
|
+
) throws -> IssueExceptionChainEntry {
|
|
267
|
+
let type = try issueText(
|
|
268
|
+
value.type,
|
|
269
|
+
label: "issue exceptionChain type",
|
|
270
|
+
maximum: 256,
|
|
271
|
+
disallowLocationDelimiters: true,
|
|
272
|
+
)
|
|
273
|
+
let message = try normalizedExceptionMessage(value)
|
|
274
|
+
let module = try value.module.map {
|
|
275
|
+
try issueText(
|
|
276
|
+
$0,
|
|
277
|
+
label: "issue exceptionChain module",
|
|
278
|
+
maximum: 512,
|
|
279
|
+
disallowLocationDelimiters: true,
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
let mechanism = try normalizedExceptionMechanism(value.mechanism, type: type)
|
|
283
|
+
let stack = try normalizedExceptionStack(value)
|
|
284
|
+
return IssueExceptionChainEntry(
|
|
285
|
+
id: value.id,
|
|
286
|
+
parentId: value.parentId,
|
|
287
|
+
relationship: value.relationship,
|
|
288
|
+
type: type,
|
|
289
|
+
message: message,
|
|
290
|
+
messageState: value.messageState,
|
|
291
|
+
module: module,
|
|
292
|
+
mechanism: mechanism,
|
|
293
|
+
stackFrames: stack.frames,
|
|
294
|
+
stackFramesState: stack.state,
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private func normalizedExceptionMessage(
|
|
299
|
+
_ value: IssueExceptionChainEntry,
|
|
300
|
+
) throws -> String? {
|
|
301
|
+
switch value.messageState {
|
|
302
|
+
case .captured, .truncated:
|
|
303
|
+
guard let message = value.message else {
|
|
304
|
+
throw issueValidationError("issue exceptionChain message must match messageState")
|
|
305
|
+
}
|
|
306
|
+
return try issueText(
|
|
307
|
+
message,
|
|
308
|
+
label: "issue exceptionChain message",
|
|
309
|
+
maximum: 1024,
|
|
310
|
+
)
|
|
311
|
+
case .redacted, .notCaptured:
|
|
312
|
+
guard value.message == nil else {
|
|
313
|
+
throw issueValidationError("issue exceptionChain message must match messageState")
|
|
314
|
+
}
|
|
315
|
+
return nil
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private func normalizedExceptionMechanism(
|
|
320
|
+
_ mechanism: IssueExceptionMechanism?,
|
|
321
|
+
type: String,
|
|
322
|
+
) throws -> IssueExceptionMechanism? {
|
|
323
|
+
guard let mechanism else {
|
|
324
|
+
return nil
|
|
325
|
+
}
|
|
326
|
+
let normalized = try validateIssueException(
|
|
327
|
+
IssueException(type: type, mechanism: mechanism),
|
|
328
|
+
)
|
|
329
|
+
guard let result = normalized.mechanism else {
|
|
330
|
+
throw issueValidationError("issue exception mechanism type is invalid")
|
|
331
|
+
}
|
|
332
|
+
return result
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private func normalizedExceptionStack(
|
|
336
|
+
_ value: IssueExceptionChainEntry,
|
|
337
|
+
) throws -> (frames: [IssueStackFrame]?, state: IssueExceptionStackFramesState) {
|
|
338
|
+
switch value.stackFramesState {
|
|
339
|
+
case .captured, .truncated:
|
|
340
|
+
guard let frames = value.stackFrames, !frames.isEmpty else {
|
|
341
|
+
throw issueValidationError(
|
|
342
|
+
"issue exceptionChain stackFrames must match stackFramesState",
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
let normalized = try normalizeIssueStackFrames(frames)
|
|
346
|
+
return (normalized, frames.count > 32 ? .truncated : value.stackFramesState)
|
|
347
|
+
case .notCaptured:
|
|
348
|
+
guard value.stackFrames == nil else {
|
|
349
|
+
throw issueValidationError(
|
|
350
|
+
"issue exceptionChain stackFrames must match stackFramesState",
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
return (nil, .notCaptured)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -205,6 +205,7 @@ private extension IssueAttributes {
|
|
|
205
205
|
level: level,
|
|
206
206
|
message: message,
|
|
207
207
|
exception: exception,
|
|
208
|
+
exceptionChain: exceptionChain,
|
|
208
209
|
stackFrames: stackFrames,
|
|
209
210
|
breadcrumbs: breadcrumbs,
|
|
210
211
|
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
@@ -220,6 +221,7 @@ private extension IssueAttributes {
|
|
|
220
221
|
level: level,
|
|
221
222
|
message: message,
|
|
222
223
|
exception: exception,
|
|
224
|
+
exceptionChain: exceptionChain,
|
|
223
225
|
stackFrames: stackFrames,
|
|
224
226
|
breadcrumbs: breadcrumbs,
|
|
225
227
|
breadcrumbsTruncated: breadcrumbsTruncated,
|
|
@@ -277,6 +279,7 @@ private extension MetricAttributes {
|
|
|
277
279
|
temporality: temporality,
|
|
278
280
|
metadata: metadata,
|
|
279
281
|
context: context,
|
|
282
|
+
description: description,
|
|
280
283
|
)
|
|
281
284
|
}
|
|
282
285
|
|
|
@@ -289,6 +292,7 @@ private extension MetricAttributes {
|
|
|
289
292
|
temporality: temporality,
|
|
290
293
|
metadata: correlationMetadata(metadata, context: context),
|
|
291
294
|
context: context,
|
|
295
|
+
description: description,
|
|
292
296
|
)
|
|
293
297
|
}
|
|
294
298
|
}
|
|
@@ -171,6 +171,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
171
171
|
public let level: IssueLevel
|
|
172
172
|
public let message: String?
|
|
173
173
|
public let exception: IssueException?
|
|
174
|
+
public let exceptionChain: IssueExceptionChain?
|
|
174
175
|
public let stackFrames: [IssueStackFrame]?
|
|
175
176
|
public let breadcrumbs: [IssueBreadcrumb]?
|
|
176
177
|
public let breadcrumbsTruncated: Bool?
|
|
@@ -183,6 +184,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
183
184
|
level: IssueLevel,
|
|
184
185
|
message: String? = nil,
|
|
185
186
|
exception: IssueException? = nil,
|
|
187
|
+
exceptionChain: IssueExceptionChain? = nil,
|
|
186
188
|
stackFrames: [IssueStackFrame]? = nil,
|
|
187
189
|
breadcrumbs: [IssueBreadcrumb]? = nil,
|
|
188
190
|
breadcrumbsTruncated: Bool? = nil,
|
|
@@ -193,6 +195,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
193
195
|
self.level = level
|
|
194
196
|
self.message = message
|
|
195
197
|
self.exception = exception
|
|
198
|
+
self.exceptionChain = exceptionChain
|
|
196
199
|
self.stackFrames = stackFrames
|
|
197
200
|
self.breadcrumbs = breadcrumbs
|
|
198
201
|
self.breadcrumbsTruncated = breadcrumbsTruncated
|
|
@@ -207,6 +210,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
207
210
|
level: IssueLevel,
|
|
208
211
|
message: String? = nil,
|
|
209
212
|
exception: IssueException? = nil,
|
|
213
|
+
exceptionChain: IssueExceptionChain? = nil,
|
|
210
214
|
stackFrames: [IssueStackFrame]? = nil,
|
|
211
215
|
breadcrumbs: [IssueBreadcrumb]? = nil,
|
|
212
216
|
breadcrumbsTruncated: Bool? = nil,
|
|
@@ -218,6 +222,7 @@ public struct IssueAttributes: Codable, Equatable, Sendable {
|
|
|
218
222
|
self.level = level
|
|
219
223
|
self.message = message
|
|
220
224
|
self.exception = exception
|
|
225
|
+
self.exceptionChain = exceptionChain
|
|
221
226
|
self.stackFrames = stackFrames
|
|
222
227
|
self.breadcrumbs = breadcrumbs
|
|
223
228
|
self.breadcrumbsTruncated = breadcrumbsTruncated
|
|
@@ -307,6 +312,7 @@ public struct ActionAttributes: Codable, Equatable, Sendable {
|
|
|
307
312
|
|
|
308
313
|
public struct MetricAttributes: Codable, Equatable, Sendable {
|
|
309
314
|
public let name: String
|
|
315
|
+
public let description: String?
|
|
310
316
|
public let kind: MetricKind
|
|
311
317
|
public let value: Double
|
|
312
318
|
public let unit: String
|
|
@@ -322,8 +328,10 @@ public struct MetricAttributes: Codable, Equatable, Sendable {
|
|
|
322
328
|
temporality: MetricTemporality,
|
|
323
329
|
metadata: Metadata? = nil,
|
|
324
330
|
context: TelemetryContext? = nil,
|
|
331
|
+
description: String? = nil,
|
|
325
332
|
) {
|
|
326
333
|
self.name = name
|
|
334
|
+
self.description = description
|
|
327
335
|
self.kind = kind
|
|
328
336
|
self.value = value
|
|
329
337
|
self.unit = unit
|
|
@@ -121,6 +121,7 @@ func validateMetric(_ attributes: MetricAttributes) throws -> MetricAttributes {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
try validateMetadata(attributes.metadata, label: "metric metadata")
|
|
124
|
+
let description = try attributes.description.map(normalizeMetricDescription)
|
|
124
125
|
return try MetricAttributes(
|
|
125
126
|
name: attributes.name,
|
|
126
127
|
kind: attributes.kind,
|
|
@@ -129,9 +130,27 @@ func validateMetric(_ attributes: MetricAttributes) throws -> MetricAttributes {
|
|
|
129
130
|
temporality: attributes.temporality,
|
|
130
131
|
metadata: attributes.metadata,
|
|
131
132
|
context: attributes.context.map { try validateTelemetryContext($0) },
|
|
133
|
+
description: description,
|
|
132
134
|
)
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
private func normalizeMetricDescription(_ value: String) throws -> String {
|
|
138
|
+
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
139
|
+
let invalidCharacter = normalized.unicodeScalars.contains { scalar in
|
|
140
|
+
scalar.value <= 0x1F
|
|
141
|
+
|| (0x7F ... 0x9F).contains(scalar.value)
|
|
142
|
+
|| scalar.value == 0x2028
|
|
143
|
+
|| scalar.value == 0x2029
|
|
144
|
+
}
|
|
145
|
+
guard !normalized.isEmpty, normalized.unicodeScalars.count <= 1024, !invalidCharacter else {
|
|
146
|
+
throw SdkError(
|
|
147
|
+
code: "validation_error",
|
|
148
|
+
message: "metric description must be a non-blank string of at most 1024 non-control characters",
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
return normalized
|
|
152
|
+
}
|
|
153
|
+
|
|
135
154
|
func requireNonEmpty(_ label: String, _ value: String) throws {
|
|
136
155
|
if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
137
156
|
throw SdkError(code: "validation_error", message: "\(label) must be non-empty")
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
func nativeCrashExceptionChain(for exception: IssueException) -> IssueExceptionChain {
|
|
6
|
+
IssueExceptionChain(entries: [
|
|
7
|
+
IssueExceptionChainEntry(
|
|
8
|
+
id: 0,
|
|
9
|
+
relationship: .reported,
|
|
10
|
+
type: exception.type,
|
|
11
|
+
messageState: .notCaptured,
|
|
12
|
+
mechanism: exception.mechanism,
|
|
13
|
+
stackFramesState: .notCaptured,
|
|
14
|
+
),
|
|
15
|
+
])
|
|
16
|
+
}
|
|
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
|
+
}
|
|
@@ -234,13 +234,8 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
234
234
|
return IssueAttributes(
|
|
235
235
|
title: hangState == nil ? "Native application crash" : "Native application hang",
|
|
236
236
|
level: hangState == .recovered ? .error : .fatal,
|
|
237
|
-
exception:
|
|
238
|
-
|
|
239
|
-
mechanism: IssueExceptionMechanism(
|
|
240
|
-
type: mechanism.name,
|
|
241
|
-
handled: hangState == .recovered,
|
|
242
|
-
),
|
|
243
|
-
),
|
|
237
|
+
exception: issueException,
|
|
238
|
+
exceptionChain: nativeCrashExceptionChain(for: issueException),
|
|
244
239
|
metadata: metadata,
|
|
245
240
|
nativeStackFrames: nativeStackFrames,
|
|
246
241
|
)
|
|
@@ -263,6 +258,10 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
263
258
|
attributes["level"] as? String == issueAttributes.level.canonicalValue,
|
|
264
259
|
attributes["message"] == nil,
|
|
265
260
|
exceptionMatches(attributes["exception"]),
|
|
261
|
+
nativeCrashExceptionChainMatches(
|
|
262
|
+
attributes["exceptionChain"],
|
|
263
|
+
expected: issueException,
|
|
264
|
+
),
|
|
266
265
|
attributes["stackFrames"] == nil,
|
|
267
266
|
attributes["breadcrumbs"] == nil,
|
|
268
267
|
attributes["breadcrumbsTruncated"] == nil,
|
|
@@ -306,6 +305,16 @@ public final class NativeCrashRecord: NSObject, @unchecked Sendable {
|
|
|
306
305
|
] as NSDictionary
|
|
307
306
|
}
|
|
308
307
|
|
|
308
|
+
private var issueException: IssueException {
|
|
309
|
+
IssueException(
|
|
310
|
+
type: hangState == nil ? "AppleNativeCrash" : "AppleNativeHang",
|
|
311
|
+
mechanism: IssueExceptionMechanism(
|
|
312
|
+
type: mechanism.name,
|
|
313
|
+
handled: hangState == .recovered,
|
|
314
|
+
),
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
309
318
|
private func exceptionMatches(_ value: Any?) -> Bool {
|
|
310
319
|
guard let value else {
|
|
311
320
|
// Preserve exact retry identity for an event queued by an older SDK before typed exceptions existed.
|
|
@@ -10,28 +10,30 @@
|
|
|
10
10
|
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStore.swift": "2de6a08473722ebd99b2d1762ed3356fefc0181f5cd886367fbfd52c75c6644b",
|
|
11
11
|
"swift/logbrew-swift/Sources/LogBrew/DurableDeliveryStoreRecovery.swift": "a843f784e7645f916bb0f401e88fa7d49fa8c4351dc2f26c1fbf32be3f4aaa40",
|
|
12
12
|
"swift/logbrew-swift/Sources/LogBrew/EventEncoding.swift": "b88b6b2d17f7d0cee9ac1a1f34ef35b192cd33d213ef3c9e1c2d21cbcc304a97",
|
|
13
|
-
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnostics.swift": "
|
|
13
|
+
"swift/logbrew-swift/Sources/LogBrew/IssueDiagnostics.swift": "fc805effd3da6084e8cac2bbfe8fdc098e07d45d6f3ce2e7df02ebe0e445591f",
|
|
14
|
+
"swift/logbrew-swift/Sources/LogBrew/IssueExceptionChain.swift": "10f81cbce95b47da0a44ef9e515b150e60d71dfcc8b04be24b037b1222a373a5",
|
|
14
15
|
"swift/logbrew-swift/Sources/LogBrew/LifecycleTrace.swift": "738cd3f129fac821a892404d1d42ee92bd806b6583d3dbb5379e88b14ab23c16",
|
|
15
|
-
"swift/logbrew-swift/Sources/LogBrew/LogBrewClient.swift": "
|
|
16
|
+
"swift/logbrew-swift/Sources/LogBrew/LogBrewClient.swift": "0f7e23a21fd8f76af031205847cd0b8b2648a069abac8d0ddc8c16da017893b7",
|
|
16
17
|
"swift/logbrew-swift/Sources/LogBrew/LogBrewLogger.swift": "d3266353a5f5139a2a1ca1a86bacce793518eb1489bf67ed9e3720b57b1c29b2",
|
|
17
18
|
"swift/logbrew-swift/Sources/LogBrew/LogBrewTrace.swift": "f67edf4ca464e6a6eb1c7a22cd4997928cd27b6c2d07535855a18a053d8de924",
|
|
18
19
|
"swift/logbrew-swift/Sources/LogBrew/Metadata.swift": "65840ebb3f3b79fbfc1f5faa3026c2583319e89ff26061676dc552c03ee15dbe",
|
|
19
20
|
"swift/logbrew-swift/Sources/LogBrew/NativeStackFrame.swift": "ecbd418969ec4a31503d9916a8bf27f22e6863356c6fabe457c1e61027a8d805",
|
|
20
21
|
"swift/logbrew-swift/Sources/LogBrew/ProductTimeline.swift": "209906aa0e8f096a347e35832840522710eadfb530053253cd73c748518e362d",
|
|
21
|
-
"swift/logbrew-swift/Sources/LogBrew/PublicTypes.swift": "
|
|
22
|
+
"swift/logbrew-swift/Sources/LogBrew/PublicTypes.swift": "1151d5402a9f5441f5d5d998fde57c8df88cb3180d2daa3e0fc49e883ab274f2",
|
|
22
23
|
"swift/logbrew-swift/Sources/LogBrew/TelemetryContext.swift": "ce3ac93a62a345f8c9a8f4c69ce84efc991bf02a688f49bad5e68edcff2f6e08",
|
|
23
24
|
"swift/logbrew-swift/Sources/LogBrew/TelemetryContextValidation.swift": "f631beef20a8a86f975787b4092a9798fedbe2c9e3f0d2740f81831b4adef287",
|
|
24
25
|
"swift/logbrew-swift/Sources/LogBrew/TraceEvidence.swift": "40aaa42976164c53d9f4d9e9e30d236534238da43bbe90832a673c8809ad54b6",
|
|
25
26
|
"swift/logbrew-swift/Sources/LogBrew/Transport.swift": "73eec81aaebec4bc922b2a372429c88fcd3e85606a7862cd08e1795eb2a8e6cd",
|
|
26
27
|
"swift/logbrew-swift/Sources/LogBrew/URLSessionTrace.swift": "d0076c72671716c41bd7498d0509e453b8182c418156e169196fee9f26fc66b1",
|
|
27
28
|
"swift/logbrew-swift/Sources/LogBrew/URLSessionTracer.swift": "0cdf08f92d6111c0dc2883b87837b81f69b5c2f9a8e5501780a114c23a741af7",
|
|
28
|
-
"swift/logbrew-swift/Sources/LogBrew/Validation.swift": "
|
|
29
|
+
"swift/logbrew-swift/Sources/LogBrew/Validation.swift": "07ff5c2daeb829b1df4f50edb0b79727f9a81b1c9b5cde5f995f5ad6ce660aa7",
|
|
29
30
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashEngine.swift": "7a46035fcaa72dd7504b4e274a92070d73dd1c45fe20e008474340d6d7c44ede",
|
|
30
31
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashReportSanitizer.swift": "a872ad63532999e74af1e9685a9382d0e02b93c6f9b942b371d47f56f8b961c0",
|
|
31
32
|
"swift/logbrew-swift/Sources/LogBrewCrash/CrashStorageDirectory.swift": "7cd566703cbf704dc99155451ee93dcace12c35c805abc09a6dcf23975cf43f9",
|
|
32
33
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeArtifactIdentity.swift": "3d785ad717dcf7302451531c18b5e1183983270a6cd78dd543a407e3facc6ae0",
|
|
33
34
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashCapture.swift": "e9ab5f1850a096f40592a025d16c5ffa2fdff0222004a37056898cf1cb4b0dd9",
|
|
34
|
-
"swift/logbrew-swift/Sources/LogBrewCrash/
|
|
35
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashExceptionEvidence.swift": "08819652ce0796bee9f502de1202d236887afbcb7794d4db9adc5b6c2928ca2c",
|
|
36
|
+
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashPublic.swift": "fe0db58c4ad348a2601d984cdf097147f0b2093baad64861aef48cc052697d9c",
|
|
35
37
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeCrashReplay.swift": "3eb3d93b32cf14de416f56fa30d6d65e6f268b5ec9d1a67081d7525c4efd4f80",
|
|
36
38
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangIncidentStore.swift": "02413ca7e003917faadcc9aa612b5d98897c75b39f67c3c845948dd9349a9d8f",
|
|
37
39
|
"swift/logbrew-swift/Sources/LogBrewCrash/NativeHangWatchdog.swift": "37b25904cd6576e20c40500be2c153156403b57ad7b4186a1b6b35b414145ecb",
|
package/metadata.cjs
CHANGED
|
@@ -68,6 +68,22 @@ function sanitizeReactNativeIssueStackFrames(stackFrames) {
|
|
|
68
68
|
}));
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function sanitizeReactNativeIssueExceptionChain(exceptionChain) {
|
|
72
|
+
if (!exceptionChain || !Array.isArray(exceptionChain.entries)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
...exceptionChain,
|
|
77
|
+
entries: exceptionChain.entries.map((entry) => {
|
|
78
|
+
const stackFrames = sanitizeReactNativeIssueStackFrames(entry.stackFrames);
|
|
79
|
+
return {
|
|
80
|
+
...entry,
|
|
81
|
+
...(stackFrames ? { stackFrames } : {})
|
|
82
|
+
};
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
71
87
|
function runtimeReactNativeDebugIdMap() {
|
|
72
88
|
try {
|
|
73
89
|
const registry = globalThis?.[REACT_NATIVE_DEBUG_ID_REGISTRY];
|
|
@@ -170,6 +186,7 @@ module.exports = {
|
|
|
170
186
|
createSafeReactNativeMetadata,
|
|
171
187
|
runtimeReactNativeDebugIdMap,
|
|
172
188
|
safeReactNativeMetadataFactoryResult,
|
|
189
|
+
sanitizeReactNativeIssueExceptionChain,
|
|
173
190
|
sanitizeReactNativeIssueMetadata,
|
|
174
191
|
sanitizeReactNativeIssueStackFrames
|
|
175
192
|
};
|
package/metadata.js
CHANGED
|
@@ -66,6 +66,22 @@ export function sanitizeReactNativeIssueStackFrames(stackFrames) {
|
|
|
66
66
|
}));
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
export function sanitizeReactNativeIssueExceptionChain(exceptionChain) {
|
|
70
|
+
if (!exceptionChain || !Array.isArray(exceptionChain.entries)) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...exceptionChain,
|
|
75
|
+
entries: exceptionChain.entries.map((entry) => {
|
|
76
|
+
const stackFrames = sanitizeReactNativeIssueStackFrames(entry.stackFrames);
|
|
77
|
+
return {
|
|
78
|
+
...entry,
|
|
79
|
+
...(stackFrames ? { stackFrames } : {})
|
|
80
|
+
};
|
|
81
|
+
})
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
export function runtimeReactNativeDebugIdMap() {
|
|
70
86
|
try {
|
|
71
87
|
const registry = globalThis?.[REACT_NATIVE_DEBUG_ID_REGISTRY];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logbrew/react-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
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",
|