@logbrew/react-native 0.1.11 → 0.1.13
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 +25 -3
- package/README.md +106 -3
- package/apple-native-diagnostics.d.ts +52 -0
- package/apple-native-diagnostics.js +286 -0
- package/expo.cjs +46 -0
- package/expo.d.ts +16 -0
- package/expo.js +5 -0
- package/fatal-replay.cjs +12 -1
- package/global-errors.cjs +3 -1
- package/index.cjs +84 -2
- package/index.d.cts +4 -0
- package/index.d.ts +4 -0
- package/index.js +84 -2
- package/index.native.d.ts +4 -0
- package/index.native.js +14 -1
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
- package/package.json +43 -5
- package/promise-rejections.cjs +3 -1
- package/scripts/sync-apple-native-sources.mjs +93 -0
- package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
|
@@ -0,0 +1,337 @@
|
|
|
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 mergeTelemetryContexts(
|
|
6
|
+
_ base: TelemetryContext?,
|
|
7
|
+
_ override: TelemetryContext?,
|
|
8
|
+
) throws -> TelemetryContext? {
|
|
9
|
+
let base = try base.map { try validateTelemetryContext($0, label: "base telemetry context") }
|
|
10
|
+
let override = try override.map { try validateTelemetryContext($0, label: "event telemetry context") }
|
|
11
|
+
guard let base else {
|
|
12
|
+
return override
|
|
13
|
+
}
|
|
14
|
+
guard let override else {
|
|
15
|
+
return base
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let context = TelemetryContext(
|
|
19
|
+
resource: mergeResources(base.resource, override.resource),
|
|
20
|
+
trace: override.trace ?? base.trace,
|
|
21
|
+
session: override.session ?? base.session,
|
|
22
|
+
subject: override.subject ?? base.subject,
|
|
23
|
+
tags: mergeTags(base.tags, override.tags),
|
|
24
|
+
)
|
|
25
|
+
return try validateTelemetryContext(context, label: "merged telemetry context")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func validateTelemetryContext(
|
|
29
|
+
_ value: TelemetryContext,
|
|
30
|
+
label: String = "telemetry context",
|
|
31
|
+
) throws -> TelemetryContext {
|
|
32
|
+
guard value.schemaVersion == 1 else {
|
|
33
|
+
throw contextValidationError("\(label) schemaVersion must be 1")
|
|
34
|
+
}
|
|
35
|
+
let resource = try value.resource.map { try validateResource($0, label: "\(label) resource") }
|
|
36
|
+
let trace = try value.trace.map { try validateTelemetryTraceContext($0, label: "\(label) trace") }
|
|
37
|
+
let session = try value.session.map { try validateSession($0, label: "\(label) session") }
|
|
38
|
+
let subject = try value.subject.map { try validateSubject($0, label: "\(label) subject") }
|
|
39
|
+
let tags = try value.tags.map { try validateTags($0, label: "\(label) tags") }
|
|
40
|
+
guard resource != nil || trace != nil || session != nil || subject != nil || tags != nil else {
|
|
41
|
+
throw contextValidationError("\(label) must include resource, trace, session, subject, or tags")
|
|
42
|
+
}
|
|
43
|
+
return TelemetryContext(resource: resource, trace: trace, session: session, subject: subject, tags: tags)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func telemetryTraceContext(_ context: LogBrewTraceContext) -> TelemetryTraceContext {
|
|
47
|
+
TelemetryTraceContext(
|
|
48
|
+
traceId: context.traceId,
|
|
49
|
+
spanId: context.spanId,
|
|
50
|
+
parentSpanId: context.parentSpanId,
|
|
51
|
+
sampled: context.sampled,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func telemetryTraceContext(
|
|
56
|
+
traceId: String,
|
|
57
|
+
spanId: String,
|
|
58
|
+
parentSpanId: String?,
|
|
59
|
+
sampled: Bool?,
|
|
60
|
+
) -> TelemetryTraceContext? {
|
|
61
|
+
let candidate = TelemetryTraceContext(
|
|
62
|
+
traceId: traceId,
|
|
63
|
+
spanId: spanId,
|
|
64
|
+
parentSpanId: parentSpanId,
|
|
65
|
+
sampled: sampled,
|
|
66
|
+
)
|
|
67
|
+
return try? validateTelemetryTraceContext(candidate, label: "span telemetry context trace")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func validateTelemetryTraceContext(
|
|
71
|
+
_ value: TelemetryTraceContext,
|
|
72
|
+
label: String,
|
|
73
|
+
) throws -> TelemetryTraceContext {
|
|
74
|
+
try TelemetryTraceContext(
|
|
75
|
+
traceId: contextHexId(value.traceId, length: 32, label: "\(label) traceId"),
|
|
76
|
+
spanId: value.spanId.map { try contextHexId($0, length: 16, label: "\(label) spanId") },
|
|
77
|
+
parentSpanId: value.parentSpanId.map {
|
|
78
|
+
try contextHexId($0, length: 16, label: "\(label) parentSpanId")
|
|
79
|
+
},
|
|
80
|
+
sampled: value.sampled,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func validMachineKey(_ value: String, maximum: Int, separators: Set<Character>) -> Bool {
|
|
85
|
+
guard !value.isEmpty, value.count <= maximum, value.first?.isASCII == true,
|
|
86
|
+
value.first?.isLetter == true
|
|
87
|
+
else {
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
return value.allSatisfy { character in
|
|
91
|
+
character.isASCII && (character.isLetter || character.isNumber || separators.contains(character))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func containsForbiddenControl(_ value: String) -> Bool {
|
|
96
|
+
value.unicodeScalars.contains { scalar in
|
|
97
|
+
scalar.value <= 0x1F || (0x7F ... 0x9F).contains(scalar.value)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func requireTelemetryContext(_ value: TelemetryContext?) throws -> TelemetryContext {
|
|
102
|
+
guard let value else {
|
|
103
|
+
throw contextValidationError("merged telemetry context must not be empty")
|
|
104
|
+
}
|
|
105
|
+
return value
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private func validateResource(_ value: TelemetryResource, label: String) throws -> TelemetryResource {
|
|
109
|
+
let service = try value.service.map { try validateNamedVersion($0, label: "\(label) service") }
|
|
110
|
+
let deployment = try value.deployment.map { try validateDeployment($0, label: "\(label) deployment") }
|
|
111
|
+
let runtime = try value.runtime.map { try validateNamedVersion($0, label: "\(label) runtime") }
|
|
112
|
+
let framework = try value.framework.map { try validateNamedVersion($0, label: "\(label) framework") }
|
|
113
|
+
let operatingSystem = try value.operatingSystem.map {
|
|
114
|
+
try validateOperatingSystem($0, label: "\(label) operatingSystem")
|
|
115
|
+
}
|
|
116
|
+
let device = try value.device.map { try validateDevice($0, label: "\(label) device") }
|
|
117
|
+
let application = try value.application.map { try validateApplication($0, label: "\(label) application") }
|
|
118
|
+
guard service != nil || deployment != nil || runtime != nil || framework != nil || operatingSystem != nil
|
|
119
|
+
|| device != nil || application != nil
|
|
120
|
+
else {
|
|
121
|
+
throw contextValidationError("\(label) must not be empty")
|
|
122
|
+
}
|
|
123
|
+
return TelemetryResource(
|
|
124
|
+
service: service,
|
|
125
|
+
deployment: deployment,
|
|
126
|
+
runtime: runtime,
|
|
127
|
+
framework: framework,
|
|
128
|
+
operatingSystem: operatingSystem,
|
|
129
|
+
device: device,
|
|
130
|
+
application: application,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func validateNamedVersion(_ value: TelemetryNamedVersion, label: String) throws -> TelemetryNamedVersion {
|
|
135
|
+
try TelemetryNamedVersion(
|
|
136
|
+
name: contextString(value.name, label: "\(label) name"),
|
|
137
|
+
version: value.version.map { try contextString($0, label: "\(label) version") },
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private func validateDeployment(_ value: TelemetryDeployment, label: String) throws -> TelemetryDeployment {
|
|
142
|
+
let environment = try value.environment.map { try contextString($0, label: "\(label) environment") }
|
|
143
|
+
let release = try value.release.map { try contextString($0, label: "\(label) release") }
|
|
144
|
+
guard environment != nil || release != nil else {
|
|
145
|
+
throw contextValidationError("\(label) must not be empty")
|
|
146
|
+
}
|
|
147
|
+
return TelemetryDeployment(environment: environment, release: release)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private func validateOperatingSystem(
|
|
151
|
+
_ value: TelemetryOperatingSystem,
|
|
152
|
+
label: String,
|
|
153
|
+
) throws -> TelemetryOperatingSystem {
|
|
154
|
+
try TelemetryOperatingSystem(
|
|
155
|
+
name: contextString(value.name, label: "\(label) name"),
|
|
156
|
+
version: value.version.map { try contextString($0, label: "\(label) version") },
|
|
157
|
+
build: value.build.map { try contextString($0, label: "\(label) build") },
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private func validateDevice(_ value: TelemetryDevice, label: String) throws -> TelemetryDevice {
|
|
162
|
+
let family = try value.family.map { try contextString($0, label: "\(label) family") }
|
|
163
|
+
let model = try value.model.map { try contextString($0, label: "\(label) model") }
|
|
164
|
+
let architecture = try value.architecture.map { try contextString($0, label: "\(label) architecture") }
|
|
165
|
+
guard family != nil || model != nil || architecture != nil else {
|
|
166
|
+
throw contextValidationError("\(label) must not be empty")
|
|
167
|
+
}
|
|
168
|
+
return TelemetryDevice(family: family, model: model, architecture: architecture)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private func validateApplication(_ value: TelemetryApplication, label: String) throws -> TelemetryApplication {
|
|
172
|
+
let name = try value.name.map { try contextString($0, label: "\(label) name") }
|
|
173
|
+
let version = try value.version.map { try contextString($0, label: "\(label) version") }
|
|
174
|
+
let build = try value.build.map { try contextString($0, label: "\(label) build") }
|
|
175
|
+
guard name != nil || version != nil || build != nil else {
|
|
176
|
+
throw contextValidationError("\(label) must not be empty")
|
|
177
|
+
}
|
|
178
|
+
return TelemetryApplication(name: name, version: version, build: build)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private func validateSession(_ value: TelemetrySessionContext, label: String) throws -> TelemetrySessionContext {
|
|
182
|
+
let id = try contextString(value.id, label: "\(label) id", maximum: 200)
|
|
183
|
+
let previousId = try value.previousId.map {
|
|
184
|
+
try contextString($0, label: "\(label) previousId", maximum: 200)
|
|
185
|
+
}
|
|
186
|
+
guard previousId != id else {
|
|
187
|
+
throw contextValidationError("\(label) previousId must differ from id")
|
|
188
|
+
}
|
|
189
|
+
return TelemetrySessionContext(id: id, previousId: previousId)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private func validateSubject(_ value: TelemetrySubjectContext, label: String) throws -> TelemetrySubjectContext {
|
|
193
|
+
try TelemetrySubjectContext(
|
|
194
|
+
id: contextString(value.id, label: "\(label) id", maximum: 200),
|
|
195
|
+
kind: value.kind,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private func validateTags(_ value: [String: String], label: String) throws -> [String: String] {
|
|
200
|
+
guard (1 ... 32).contains(value.count) else {
|
|
201
|
+
throw contextValidationError("\(label) must contain 1-32 entries")
|
|
202
|
+
}
|
|
203
|
+
var output: [String: String] = [:]
|
|
204
|
+
for (key, rawValue) in value {
|
|
205
|
+
guard validMachineKey(key, maximum: 64, separators: ["_", ".", "-"]) else {
|
|
206
|
+
throw contextValidationError("\(label) key is invalid")
|
|
207
|
+
}
|
|
208
|
+
output[key] = try contextString(rawValue, label: "\(label) value for \(key)")
|
|
209
|
+
}
|
|
210
|
+
return output
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private func mergeResources(_ base: TelemetryResource?, _ override: TelemetryResource?) -> TelemetryResource? {
|
|
214
|
+
guard base != nil || override != nil else {
|
|
215
|
+
return nil
|
|
216
|
+
}
|
|
217
|
+
return TelemetryResource(
|
|
218
|
+
service: mergeNamedVersion(base?.service, override?.service),
|
|
219
|
+
deployment: mergeDeployment(base?.deployment, override?.deployment),
|
|
220
|
+
runtime: mergeNamedVersion(base?.runtime, override?.runtime),
|
|
221
|
+
framework: mergeNamedVersion(base?.framework, override?.framework),
|
|
222
|
+
operatingSystem: mergeOperatingSystem(base?.operatingSystem, override?.operatingSystem),
|
|
223
|
+
device: mergeDevice(base?.device, override?.device),
|
|
224
|
+
application: mergeApplication(base?.application, override?.application),
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private func mergeNamedVersion(
|
|
229
|
+
_ base: TelemetryNamedVersion?,
|
|
230
|
+
_ override: TelemetryNamedVersion?,
|
|
231
|
+
) -> TelemetryNamedVersion? {
|
|
232
|
+
guard let base else {
|
|
233
|
+
return override
|
|
234
|
+
}
|
|
235
|
+
guard let override else {
|
|
236
|
+
return base
|
|
237
|
+
}
|
|
238
|
+
return TelemetryNamedVersion(name: override.name, version: override.version ?? base.version)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private func mergeDeployment(_ base: TelemetryDeployment?, _ override: TelemetryDeployment?) -> TelemetryDeployment? {
|
|
242
|
+
guard let base else {
|
|
243
|
+
return override
|
|
244
|
+
}
|
|
245
|
+
guard let override else {
|
|
246
|
+
return base
|
|
247
|
+
}
|
|
248
|
+
return TelemetryDeployment(
|
|
249
|
+
environment: override.environment ?? base.environment,
|
|
250
|
+
release: override.release ?? base.release,
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private func mergeOperatingSystem(
|
|
255
|
+
_ base: TelemetryOperatingSystem?,
|
|
256
|
+
_ override: TelemetryOperatingSystem?,
|
|
257
|
+
) -> TelemetryOperatingSystem? {
|
|
258
|
+
guard let base else {
|
|
259
|
+
return override
|
|
260
|
+
}
|
|
261
|
+
guard let override else {
|
|
262
|
+
return base
|
|
263
|
+
}
|
|
264
|
+
return TelemetryOperatingSystem(
|
|
265
|
+
name: override.name,
|
|
266
|
+
version: override.version ?? base.version,
|
|
267
|
+
build: override.build ?? base.build,
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private func mergeDevice(_ base: TelemetryDevice?, _ override: TelemetryDevice?) -> TelemetryDevice? {
|
|
272
|
+
guard let base else {
|
|
273
|
+
return override
|
|
274
|
+
}
|
|
275
|
+
guard let override else {
|
|
276
|
+
return base
|
|
277
|
+
}
|
|
278
|
+
return TelemetryDevice(
|
|
279
|
+
family: override.family ?? base.family,
|
|
280
|
+
model: override.model ?? base.model,
|
|
281
|
+
architecture: override.architecture ?? base.architecture,
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private func mergeApplication(
|
|
286
|
+
_ base: TelemetryApplication?,
|
|
287
|
+
_ override: TelemetryApplication?,
|
|
288
|
+
) -> TelemetryApplication? {
|
|
289
|
+
guard let base else {
|
|
290
|
+
return override
|
|
291
|
+
}
|
|
292
|
+
guard let override else {
|
|
293
|
+
return base
|
|
294
|
+
}
|
|
295
|
+
return TelemetryApplication(
|
|
296
|
+
name: override.name ?? base.name,
|
|
297
|
+
version: override.version ?? base.version,
|
|
298
|
+
build: override.build ?? base.build,
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func mergeTags(_ base: [String: String]?, _ override: [String: String]?) -> [String: String]? {
|
|
303
|
+
guard base != nil || override != nil else {
|
|
304
|
+
return nil
|
|
305
|
+
}
|
|
306
|
+
var output = base ?? [:]
|
|
307
|
+
for (key, value) in override ?? [:] {
|
|
308
|
+
output[key] = value
|
|
309
|
+
}
|
|
310
|
+
return output
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private func contextString(_ value: String, label: String, maximum: Int = 256) throws -> String {
|
|
314
|
+
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
315
|
+
guard !normalized.isEmpty, normalized.count <= maximum, !containsForbiddenControl(normalized) else {
|
|
316
|
+
throw contextValidationError("\(label) is invalid")
|
|
317
|
+
}
|
|
318
|
+
return normalized
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private func contextHexId(_ value: String, length: Int, label: String) throws -> String {
|
|
322
|
+
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
323
|
+
guard normalized.count == length, normalized.allSatisfy(isASCIIHex),
|
|
324
|
+
normalized.contains(where: { $0 != "0" })
|
|
325
|
+
else {
|
|
326
|
+
throw contextValidationError("\(label) must be a non-zero \(length)-character hex value")
|
|
327
|
+
}
|
|
328
|
+
return normalized
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private func isASCIIHex(_ value: Character) -> Bool {
|
|
332
|
+
value.isASCII && (value.isNumber || ("a" ... "f").contains(value))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private func contextValidationError(_ message: String) -> SdkError {
|
|
336
|
+
SdkError(code: "validation_error", message: message)
|
|
337
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
/// One bounded milestone that happened during a span.
|
|
6
|
+
public struct SpanEventSummary: Codable, Equatable, Sendable {
|
|
7
|
+
public let name: String
|
|
8
|
+
public let timestamp: String?
|
|
9
|
+
public let metadata: Metadata?
|
|
10
|
+
|
|
11
|
+
public init(name: String, timestamp: String? = nil, metadata: Metadata? = nil) {
|
|
12
|
+
self.name = name
|
|
13
|
+
self.timestamp = timestamp
|
|
14
|
+
self.metadata = metadata
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// One bounded relationship from this span to another trace or span.
|
|
19
|
+
public struct SpanLinkSummary: Codable, Equatable, Sendable {
|
|
20
|
+
public let traceId: String
|
|
21
|
+
public let spanId: String
|
|
22
|
+
public let sampled: Bool?
|
|
23
|
+
public let metadata: Metadata?
|
|
24
|
+
|
|
25
|
+
public init(
|
|
26
|
+
traceId: String,
|
|
27
|
+
spanId: String,
|
|
28
|
+
sampled: Bool? = nil,
|
|
29
|
+
metadata: Metadata? = nil,
|
|
30
|
+
) {
|
|
31
|
+
self.traceId = traceId
|
|
32
|
+
self.spanId = spanId
|
|
33
|
+
self.sampled = sampled
|
|
34
|
+
self.metadata = metadata
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func validateSpanEvents(_ values: [SpanEventSummary]?) throws -> [SpanEventSummary]? {
|
|
39
|
+
guard let values else {
|
|
40
|
+
return nil
|
|
41
|
+
}
|
|
42
|
+
guard values.count <= 8 else {
|
|
43
|
+
throw traceEvidenceError("span events must contain at most 8 entries")
|
|
44
|
+
}
|
|
45
|
+
return try values.map { value in
|
|
46
|
+
try requireNonEmpty("span event name", value.name)
|
|
47
|
+
if let timestamp = value.timestamp {
|
|
48
|
+
try requireTimestamp(timestamp)
|
|
49
|
+
}
|
|
50
|
+
try validateMetadata(value.metadata, label: "span event metadata")
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func validateSpanLinks(_ values: [SpanLinkSummary]?) throws -> [SpanLinkSummary]? {
|
|
56
|
+
guard let values else {
|
|
57
|
+
return nil
|
|
58
|
+
}
|
|
59
|
+
guard values.count <= 8 else {
|
|
60
|
+
throw traceEvidenceError("span links must contain at most 8 entries")
|
|
61
|
+
}
|
|
62
|
+
return try values.map { value in
|
|
63
|
+
let trace = try validateTelemetryTraceContext(
|
|
64
|
+
TelemetryTraceContext(traceId: value.traceId, spanId: value.spanId, sampled: value.sampled),
|
|
65
|
+
label: "span link",
|
|
66
|
+
)
|
|
67
|
+
try validateMetadata(value.metadata, label: "span link metadata")
|
|
68
|
+
return SpanLinkSummary(
|
|
69
|
+
traceId: trace.traceId,
|
|
70
|
+
spanId: trace.spanId ?? value.spanId,
|
|
71
|
+
sampled: trace.sampled,
|
|
72
|
+
metadata: value.metadata,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private func traceEvidenceError(_ message: String) -> SdkError {
|
|
78
|
+
SdkError(code: "validation_error", message: message)
|
|
79
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import Foundation
|
|
4
|
+
#if canImport(FoundationNetworking)
|
|
5
|
+
import FoundationNetworking
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
public struct SdkError: Error, Equatable, Sendable, CustomStringConvertible {
|
|
9
|
+
public let code: String
|
|
10
|
+
public let message: String
|
|
11
|
+
|
|
12
|
+
public init(code: String, message: String) {
|
|
13
|
+
self.code = code
|
|
14
|
+
self.message = message
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public var description: String {
|
|
18
|
+
"\(code): \(message)"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public struct TransportError: Error, Equatable, Sendable, CustomStringConvertible {
|
|
23
|
+
public let code: String
|
|
24
|
+
public let message: String
|
|
25
|
+
public let retryable: Bool
|
|
26
|
+
|
|
27
|
+
public init(code: String, message: String, retryable: Bool = false) {
|
|
28
|
+
self.code = code
|
|
29
|
+
self.message = message
|
|
30
|
+
self.retryable = retryable
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public static func network(_ message: String) -> TransportError {
|
|
34
|
+
TransportError(code: "network_failure", message: message, retryable: true)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public var description: String {
|
|
38
|
+
message
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public struct TransportResponse: Codable, Equatable, Sendable {
|
|
43
|
+
public let statusCode: Int
|
|
44
|
+
public let attempts: Int
|
|
45
|
+
|
|
46
|
+
public init(statusCode: Int, attempts: Int) {
|
|
47
|
+
self.statusCode = statusCode
|
|
48
|
+
self.attempts = attempts
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public protocol Transport: AnyObject {
|
|
53
|
+
func send(apiKey: String, body: Data) throws -> TransportResponse
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public typealias HTTPTransportRequester = (URLRequest) throws -> Int
|
|
57
|
+
|
|
58
|
+
public final class HTTPTransport: Transport {
|
|
59
|
+
public static let defaultEndpoint = URL(string: "https://api.logbrew.co/v1/events")!
|
|
60
|
+
public static let defaultTimeout: TimeInterval = 10
|
|
61
|
+
|
|
62
|
+
public let endpoint: URL
|
|
63
|
+
public let headers: [String: String]
|
|
64
|
+
public let timeout: TimeInterval
|
|
65
|
+
|
|
66
|
+
private let requester: HTTPTransportRequester?
|
|
67
|
+
private let session: URLSession
|
|
68
|
+
|
|
69
|
+
public init(
|
|
70
|
+
endpoint: URL = HTTPTransport.defaultEndpoint,
|
|
71
|
+
headers: [String: String] = [:],
|
|
72
|
+
timeout: TimeInterval = HTTPTransport.defaultTimeout,
|
|
73
|
+
session: URLSession = .shared,
|
|
74
|
+
requester: HTTPTransportRequester? = nil,
|
|
75
|
+
) throws {
|
|
76
|
+
self.endpoint = try Self.validateEndpoint(endpoint)
|
|
77
|
+
self.headers = try Self.copyHeaders(headers)
|
|
78
|
+
self.timeout = try Self.validateTimeout(timeout)
|
|
79
|
+
self.session = session
|
|
80
|
+
self.requester = requester
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public func send(apiKey: String, body: Data) throws -> TransportResponse {
|
|
84
|
+
try requireNonEmpty("api_key", apiKey)
|
|
85
|
+
var request = URLRequest(url: endpoint, timeoutInterval: timeout)
|
|
86
|
+
request.httpMethod = "POST"
|
|
87
|
+
request.httpBody = body
|
|
88
|
+
request.setValue("application/json", forHTTPHeaderField: "content-type")
|
|
89
|
+
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "authorization")
|
|
90
|
+
for (name, value) in headers {
|
|
91
|
+
request.setValue(value, forHTTPHeaderField: name)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if let requester {
|
|
95
|
+
return try TransportResponse(statusCode: requester(request), attempts: 1)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return try TransportResponse(statusCode: sendWithURLSession(request), attempts: 1)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private func sendWithURLSession(_ request: URLRequest) throws -> Int {
|
|
102
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
103
|
+
let result = HTTPTransportResultBox()
|
|
104
|
+
let task = session.dataTask(with: request) { _, response, error in
|
|
105
|
+
defer {
|
|
106
|
+
semaphore.signal()
|
|
107
|
+
}
|
|
108
|
+
if let error {
|
|
109
|
+
result.value = .failure(TransportError.network("http transport failed: \(error.localizedDescription)"))
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
113
|
+
result.value = .failure(TransportError.network("http transport did not return an HTTP response"))
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
result.value = .success(httpResponse.statusCode)
|
|
117
|
+
}
|
|
118
|
+
task.resume()
|
|
119
|
+
|
|
120
|
+
if semaphore.wait(timeout: .now() + timeout) == .timedOut {
|
|
121
|
+
task.cancel()
|
|
122
|
+
throw TransportError.network("http transport timed out")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
guard let value = result.value else {
|
|
126
|
+
throw TransportError.network("http transport completed without a response")
|
|
127
|
+
}
|
|
128
|
+
return try value.get()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private static func validateEndpoint(_ endpoint: URL) throws -> URL {
|
|
132
|
+
guard let scheme = endpoint.scheme?.lowercased(), scheme == "http" || scheme == "https" else {
|
|
133
|
+
throw SdkError(code: "configuration_error", message: "HTTP transport endpoint must use http or https")
|
|
134
|
+
}
|
|
135
|
+
guard endpoint.host != nil else {
|
|
136
|
+
throw SdkError(code: "configuration_error", message: "HTTP transport endpoint must include a host")
|
|
137
|
+
}
|
|
138
|
+
return endpoint
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private static func validateTimeout(_ timeout: TimeInterval) throws -> TimeInterval {
|
|
142
|
+
if timeout <= 0 {
|
|
143
|
+
throw SdkError(code: "configuration_error", message: "HTTP transport timeout must be positive")
|
|
144
|
+
}
|
|
145
|
+
return timeout
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private static func copyHeaders(_ headers: [String: String]) throws -> [String: String] {
|
|
149
|
+
var safeHeaders: [String: String] = [:]
|
|
150
|
+
for (name, value) in headers {
|
|
151
|
+
try requireNonEmpty("HTTP header name", name)
|
|
152
|
+
try requireNonEmpty("HTTP header value", value)
|
|
153
|
+
safeHeaders[name] = value
|
|
154
|
+
}
|
|
155
|
+
return safeHeaders
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private final class HTTPTransportResultBox: @unchecked Sendable {
|
|
160
|
+
var value: Result<Int, Error>?
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public enum ScriptedTransportResponse: Equatable, Sendable {
|
|
164
|
+
case status(Int)
|
|
165
|
+
case failure(TransportError)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public final class RecordingTransport: Transport {
|
|
169
|
+
private var scriptedResponses: [ScriptedTransportResponse]
|
|
170
|
+
public private(set) var sentBodies: [String] = []
|
|
171
|
+
|
|
172
|
+
public init(scriptedResponses: [ScriptedTransportResponse] = [.status(202)]) {
|
|
173
|
+
self.scriptedResponses = scriptedResponses.isEmpty ? [.status(202)] : scriptedResponses
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public static func alwaysAccept() -> RecordingTransport {
|
|
177
|
+
RecordingTransport(scriptedResponses: [.status(202)])
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public func lastBody() -> String? {
|
|
181
|
+
sentBodies.last
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public func send(apiKey: String, body: Data) throws -> TransportResponse {
|
|
185
|
+
try requireNonEmpty("api_key", apiKey)
|
|
186
|
+
guard let bodyText = String(data: body, encoding: .utf8) else {
|
|
187
|
+
throw TransportError(code: "invalid_request_body", message: "request body was not valid UTF-8")
|
|
188
|
+
}
|
|
189
|
+
sentBodies.append(bodyText)
|
|
190
|
+
|
|
191
|
+
let next: ScriptedTransportResponse = if scriptedResponses.isEmpty {
|
|
192
|
+
.status(202)
|
|
193
|
+
} else {
|
|
194
|
+
scriptedResponses.removeFirst()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
switch next {
|
|
198
|
+
case let .status(statusCode):
|
|
199
|
+
return TransportResponse(statusCode: statusCode, attempts: 1)
|
|
200
|
+
case let .failure(error):
|
|
201
|
+
throw error
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|