@logbrew/react-native 0.1.11 → 0.1.12

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.
Files changed (61) hide show
  1. package/LogBrewReactNative.podspec +25 -3
  2. package/README.md +106 -3
  3. package/apple-native-diagnostics.d.ts +52 -0
  4. package/apple-native-diagnostics.js +286 -0
  5. package/expo.cjs +46 -0
  6. package/expo.d.ts +16 -0
  7. package/expo.js +5 -0
  8. package/fatal-replay.cjs +12 -1
  9. package/global-errors.cjs +3 -1
  10. package/index.cjs +84 -2
  11. package/index.d.cts +4 -0
  12. package/index.d.ts +4 -0
  13. package/index.js +84 -2
  14. package/index.native.d.ts +4 -0
  15. package/index.native.js +14 -1
  16. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
  17. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
  18. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
  19. package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
  20. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
  21. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
  22. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
  23. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
  27. package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
  28. package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
  29. package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
  30. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
  31. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
  32. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
  33. package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
  34. package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
  35. package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
  36. package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
  37. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
  38. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
  39. package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
  40. package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
  41. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
  42. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
  43. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
  44. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
  45. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
  46. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
  47. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
  48. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
  49. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
  50. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
  51. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
  52. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
  53. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
  54. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
  55. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
  56. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
  57. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
  58. package/package.json +42 -4
  59. package/promise-rejections.cjs +3 -1
  60. package/scripts/sync-apple-native-sources.mjs +93 -0
  61. package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
@@ -0,0 +1,358 @@
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
+ public final class LogBrewClient: @unchecked Sendable {
6
+ private let engine: DeliveryEngine
7
+ private let baseContext: TelemetryContext?
8
+ private let breadcrumbStore = IssueBreadcrumbStore()
9
+
10
+ public static func create(
11
+ apiKey: String,
12
+ sdkName: String,
13
+ sdkVersion: String,
14
+ maxRetries: Int = 2,
15
+ context: TelemetryContext? = nil,
16
+ includeAutomaticContext: Bool = true,
17
+ ) throws -> LogBrewClient {
18
+ try LogBrewClient(
19
+ config: ClientConfig(
20
+ apiKey: apiKey,
21
+ sdkName: sdkName,
22
+ sdkVersion: sdkVersion,
23
+ maxRetries: maxRetries,
24
+ context: context,
25
+ includeAutomaticContext: includeAutomaticContext,
26
+ ),
27
+ )
28
+ }
29
+
30
+ public init(config: ClientConfig) throws {
31
+ try requireNonEmpty("api_key", config.apiKey)
32
+ try requireNonEmpty("sdk_name", config.sdkName)
33
+ try requireNonEmpty("sdk_version", config.sdkVersion)
34
+ baseContext = try mergeTelemetryContexts(
35
+ config.includeAutomaticContext ? automaticTelemetryContext() : nil,
36
+ config.context,
37
+ )
38
+ engine = DeliveryEngine(
39
+ apiKey: config.apiKey,
40
+ sdk: SDKInfo(name: config.sdkName, language: "swift", version: config.sdkVersion),
41
+ maxRetries: config.maxRetries,
42
+ )
43
+ }
44
+
45
+ public func pendingEvents() -> Int {
46
+ engine.pendingEvents()
47
+ }
48
+
49
+ public func previewJSON() throws -> String {
50
+ try engine.previewJSON()
51
+ }
52
+
53
+ public func release(_ id: String, timestamp: String, attributes: ReleaseAttributes) throws {
54
+ let context = try resolvedContext(event: attributes.context)
55
+ try pushEvent(.release(validateRelease(attributes.withContext(context))), id: id, timestamp: timestamp)
56
+ }
57
+
58
+ public func environment(_ id: String, timestamp: String, attributes: EnvironmentAttributes) throws {
59
+ let context = try resolvedContext(event: attributes.context)
60
+ try pushEvent(.environment(validateEnvironment(attributes.withContext(context))), id: id, timestamp: timestamp)
61
+ }
62
+
63
+ public func issue(_ id: String, timestamp: String, attributes: IssueAttributes) throws {
64
+ let context = try resolvedContext(event: attributes.context)
65
+ let attributes = issueAttributes(attributes, adding: breadcrumbStore.snapshot())
66
+ .withContext(context)
67
+ .withCorrelationMetadata()
68
+ try pushEvent(.issue(validateIssue(attributes)), id: id, timestamp: timestamp)
69
+ }
70
+
71
+ @_spi(CrashReplay)
72
+ public func issueDetached(_ id: String, timestamp: String, attributes: IssueAttributes) throws {
73
+ try pushEvent(.issue(validateIssue(attributes)), id: id, timestamp: timestamp)
74
+ }
75
+
76
+ public func log(_ id: String, timestamp: String, attributes: LogAttributes) throws {
77
+ let context = try resolvedContext(event: attributes.context)
78
+ try pushEvent(
79
+ .log(validateLog(attributes.withContext(context).withCorrelationMetadata())),
80
+ id: id,
81
+ timestamp: timestamp,
82
+ )
83
+ }
84
+
85
+ public func span(_ id: String, timestamp: String, attributes: SpanAttributes) throws {
86
+ let inherited = try resolvedContext(event: attributes.context)
87
+ let sampled = inherited?.trace.flatMap { trace in
88
+ trace.traceId.caseInsensitiveCompare(attributes.traceId) == .orderedSame ? trace.sampled : nil
89
+ }
90
+ let spanTrace = telemetryTraceContext(
91
+ traceId: attributes.traceId,
92
+ spanId: attributes.spanId,
93
+ parentSpanId: attributes.parentSpanId,
94
+ sampled: sampled,
95
+ )
96
+ let context = try mergeTelemetryContexts(
97
+ contextWithoutTrace(inherited),
98
+ spanTrace.map { TelemetryContext(trace: $0) },
99
+ )
100
+ try pushEvent(.span(validateSpan(attributes.withContext(context))), id: id, timestamp: timestamp)
101
+ }
102
+
103
+ public func action(_ id: String, timestamp: String, attributes: ActionAttributes) throws {
104
+ let context = try resolvedContext(event: attributes.context)
105
+ try pushEvent(
106
+ .action(validateAction(attributes.withContext(context).withCorrelationMetadata())),
107
+ id: id,
108
+ timestamp: timestamp,
109
+ )
110
+ }
111
+
112
+ public func metric(_ id: String, timestamp: String, attributes: MetricAttributes) throws {
113
+ let context = try resolvedContext(event: attributes.context)
114
+ try pushEvent(
115
+ .metric(validateMetric(attributes.withContext(context).withCorrelationMetadata())),
116
+ id: id,
117
+ timestamp: timestamp,
118
+ )
119
+ }
120
+
121
+ /// Retain one validated breadcrumb for subsequent issues, evicting the oldest after 64 entries.
122
+ public func addBreadcrumb(_ breadcrumb: IssueBreadcrumb) throws {
123
+ try breadcrumbStore.add(breadcrumb)
124
+ }
125
+
126
+ /// Clear the client-owned breadcrumb history without changing already queued issues.
127
+ public func clearBreadcrumbs() {
128
+ breadcrumbStore.clear()
129
+ }
130
+
131
+ public func flush(transport: any Transport) throws -> TransportResponse {
132
+ try engine.flush(transport: transport)
133
+ }
134
+
135
+ @_spi(CrashReplay)
136
+ public func flushEvent(
137
+ _ eventID: String,
138
+ transport: any Transport,
139
+ ) throws -> Bool {
140
+ try requireNonEmpty("event id", eventID)
141
+ return try engine.flushEvent(eventID, transport: transport)
142
+ }
143
+
144
+ public func shutdown(transport: any Transport) throws -> TransportResponse {
145
+ try engine.shutdown(transport: transport)
146
+ }
147
+
148
+ public func startAutomaticDelivery(
149
+ transport: any Transport,
150
+ options: AutomaticDeliveryOptions = AutomaticDeliveryOptions(),
151
+ ) throws {
152
+ try engine.startAutomaticDelivery(transport: transport, options: options)
153
+ }
154
+
155
+ public func recoverAutomaticDelivery() throws {
156
+ try engine.recoverAutomaticDelivery()
157
+ }
158
+
159
+ public func stopAutomaticDelivery() {
160
+ engine.stopAutomaticDelivery()
161
+ }
162
+
163
+ public func deliveryHealth() -> DeliveryHealth {
164
+ engine.health()
165
+ }
166
+
167
+ public func enableDurableDelivery(options: DurableDeliveryOptions) throws {
168
+ try engine.enableDurableDelivery(options: options)
169
+ }
170
+
171
+ public func purgeDurableDelivery() throws {
172
+ try engine.purgeDurableDelivery()
173
+ }
174
+
175
+ public func flush() throws -> TransportResponse {
176
+ try engine.flushOwnedTransport()
177
+ }
178
+
179
+ public func shutdown() throws -> TransportResponse {
180
+ try engine.shutdownOwnedTransport()
181
+ }
182
+
183
+ private func pushEvent(_ attributes: EventAttributes, id: String, timestamp: String) throws {
184
+ try requireNonEmpty("event id", id)
185
+ try requireTimestamp(timestamp)
186
+ try engine.enqueue(Event(type: attributes.eventType, timestamp: timestamp, id: id, attributes: attributes))
187
+ }
188
+
189
+ private func resolvedContext(event: TelemetryContext?) throws -> TelemetryContext? {
190
+ var context = try mergeTelemetryContexts(baseContext, LogBrewTelemetry.current)
191
+ if let activeTrace = LogBrewTrace.current {
192
+ context = try mergeTelemetryContexts(
193
+ context,
194
+ TelemetryContext(trace: telemetryTraceContext(activeTrace)),
195
+ )
196
+ }
197
+ return try mergeTelemetryContexts(context, event)
198
+ }
199
+ }
200
+
201
+ private extension IssueAttributes {
202
+ func withContext(_ context: TelemetryContext?) -> IssueAttributes {
203
+ IssueAttributes(
204
+ title: title,
205
+ level: level,
206
+ message: message,
207
+ exception: exception,
208
+ stackFrames: stackFrames,
209
+ breadcrumbs: breadcrumbs,
210
+ breadcrumbsTruncated: breadcrumbsTruncated,
211
+ metadata: metadata,
212
+ context: context,
213
+ nativeStackFrames: nativeStackFrames,
214
+ )
215
+ }
216
+
217
+ func withCorrelationMetadata() -> IssueAttributes {
218
+ IssueAttributes(
219
+ title: title,
220
+ level: level,
221
+ message: message,
222
+ exception: exception,
223
+ stackFrames: stackFrames,
224
+ breadcrumbs: breadcrumbs,
225
+ breadcrumbsTruncated: breadcrumbsTruncated,
226
+ metadata: correlationMetadata(metadata, context: context),
227
+ context: context,
228
+ nativeStackFrames: nativeStackFrames,
229
+ )
230
+ }
231
+ }
232
+
233
+ private extension LogAttributes {
234
+ func withContext(_ context: TelemetryContext?) -> LogAttributes {
235
+ LogAttributes(
236
+ message: message,
237
+ level: level,
238
+ logger: logger,
239
+ metadata: metadata,
240
+ context: context,
241
+ )
242
+ }
243
+
244
+ func withCorrelationMetadata() -> LogAttributes {
245
+ LogAttributes(
246
+ message: message,
247
+ level: level,
248
+ logger: logger,
249
+ metadata: correlationMetadata(metadata, context: context),
250
+ context: context,
251
+ )
252
+ }
253
+ }
254
+
255
+ private extension ActionAttributes {
256
+ func withContext(_ context: TelemetryContext?) -> ActionAttributes {
257
+ ActionAttributes(name: name, status: status, metadata: metadata, context: context)
258
+ }
259
+
260
+ func withCorrelationMetadata() -> ActionAttributes {
261
+ ActionAttributes(
262
+ name: name,
263
+ status: status,
264
+ metadata: correlationMetadata(metadata, context: context),
265
+ context: context,
266
+ )
267
+ }
268
+ }
269
+
270
+ private extension MetricAttributes {
271
+ func withContext(_ context: TelemetryContext?) -> MetricAttributes {
272
+ MetricAttributes(
273
+ name: name,
274
+ kind: kind,
275
+ value: value,
276
+ unit: unit,
277
+ temporality: temporality,
278
+ metadata: metadata,
279
+ context: context,
280
+ )
281
+ }
282
+
283
+ func withCorrelationMetadata() -> MetricAttributes {
284
+ MetricAttributes(
285
+ name: name,
286
+ kind: kind,
287
+ value: value,
288
+ unit: unit,
289
+ temporality: temporality,
290
+ metadata: correlationMetadata(metadata, context: context),
291
+ context: context,
292
+ )
293
+ }
294
+ }
295
+
296
+ private extension ReleaseAttributes {
297
+ func withContext(_ context: TelemetryContext?) -> ReleaseAttributes {
298
+ ReleaseAttributes(version: version, commit: commit, notes: notes, metadata: metadata, context: context)
299
+ }
300
+ }
301
+
302
+ private extension EnvironmentAttributes {
303
+ func withContext(_ context: TelemetryContext?) -> EnvironmentAttributes {
304
+ EnvironmentAttributes(name: name, region: region, metadata: metadata, context: context)
305
+ }
306
+ }
307
+
308
+ private extension SpanAttributes {
309
+ func withContext(_ context: TelemetryContext?) -> SpanAttributes {
310
+ SpanAttributes(
311
+ name: name,
312
+ traceId: traceId,
313
+ spanId: spanId,
314
+ parentSpanId: parentSpanId,
315
+ status: status,
316
+ durationMs: durationMs,
317
+ events: events,
318
+ links: links,
319
+ metadata: metadata,
320
+ context: context,
321
+ )
322
+ }
323
+ }
324
+
325
+ private func correlationMetadata(_ metadata: Metadata?, context: TelemetryContext?) -> Metadata? {
326
+ guard let trace = context?.trace else {
327
+ return metadata
328
+ }
329
+ var output = metadata ?? [:]
330
+ output["traceId"] = .string(trace.traceId)
331
+ if let spanId = trace.spanId {
332
+ output["spanId"] = .string(spanId)
333
+ }
334
+ if let parentSpanId = trace.parentSpanId {
335
+ output["parentSpanId"] = .string(parentSpanId)
336
+ }
337
+ if let sampled = trace.sampled {
338
+ output["traceSampled"] = .bool(sampled)
339
+ }
340
+ if let active = LogBrewTrace.current, active.traceId == trace.traceId, active.spanId == trace.spanId {
341
+ output["traceFlags"] = .string(active.traceFlags)
342
+ }
343
+ return output
344
+ }
345
+
346
+ private func contextWithoutTrace(_ context: TelemetryContext?) -> TelemetryContext? {
347
+ guard let context,
348
+ context.resource != nil || context.session != nil || context.subject != nil || context.tags != nil
349
+ else {
350
+ return nil
351
+ }
352
+ return TelemetryContext(
353
+ resource: context.resource,
354
+ session: context.session,
355
+ subject: context.subject,
356
+ tags: context.tags,
357
+ )
358
+ }
@@ -0,0 +1,262 @@
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
+ public enum LogBrewLoggerLevel: String, Sendable {
6
+ case trace
7
+ case debug
8
+ case info
9
+ case notice
10
+ case warning
11
+ case error
12
+ case fault
13
+ case critical
14
+
15
+ var logBrewLevel: LogLevel {
16
+ switch self {
17
+ case .trace, .debug:
18
+ .info
19
+ case .info, .notice:
20
+ .info
21
+ case .warning:
22
+ .warning
23
+ case .error, .fault:
24
+ .error
25
+ case .critical:
26
+ .critical
27
+ }
28
+ }
29
+ }
30
+
31
+ public final class LogBrewLogger {
32
+ private let client: LogBrewClient
33
+ private let loggerName: String
34
+ private let subsystem: String?
35
+ private let category: String?
36
+ private let eventIDPrefix: String
37
+ private let baseMetadata: Metadata
38
+ private let transport: (any Transport)?
39
+ private let flushOnLog: Bool
40
+ private let timestampProvider: () -> String
41
+ private let onError: ((any Error) -> Void)?
42
+ private let lock = NSLock()
43
+ private var nextEventNumber = 0
44
+
45
+ public init(
46
+ client: LogBrewClient,
47
+ loggerName: String? = nil,
48
+ subsystem: String? = nil,
49
+ category: String? = nil,
50
+ eventIDPrefix: String = "swift_log",
51
+ metadata: Metadata? = nil,
52
+ transport: (any Transport)? = nil,
53
+ flushOnLog: Bool = false,
54
+ timestampProvider: (() -> String)? = nil,
55
+ onError: ((any Error) -> Void)? = nil,
56
+ ) throws {
57
+ if let loggerName {
58
+ try requireNonEmpty("logger name", loggerName)
59
+ }
60
+ if let subsystem {
61
+ try requireNonEmpty("logger subsystem", subsystem)
62
+ }
63
+ if let category {
64
+ try requireNonEmpty("logger category", category)
65
+ }
66
+ try requireNonEmpty("event id prefix", eventIDPrefix)
67
+
68
+ self.client = client
69
+ self.loggerName = loggerName ?? category ?? subsystem ?? "swift-logger"
70
+ self.subsystem = subsystem
71
+ self.category = category
72
+ self.eventIDPrefix = eventIDPrefix
73
+ baseMetadata = metadata ?? [:]
74
+ self.transport = transport
75
+ self.flushOnLog = flushOnLog
76
+ self.timestampProvider = timestampProvider ?? Self.defaultTimestamp
77
+ self.onError = onError
78
+ }
79
+
80
+ public func log(
81
+ _ level: LogBrewLoggerLevel,
82
+ _ message: @autoclosure () -> String,
83
+ id: String? = nil,
84
+ timestamp: String? = nil,
85
+ metadata: Metadata? = nil,
86
+ error: (any Error)? = nil,
87
+ ) {
88
+ capture(
89
+ level,
90
+ message(),
91
+ call: LogBrewLoggerCall(id: id, timestamp: timestamp, metadata: metadata, error: error),
92
+ )
93
+ }
94
+
95
+ public func trace(
96
+ _ message: @autoclosure () -> String,
97
+ id: String? = nil,
98
+ timestamp: String? = nil,
99
+ metadata: Metadata? = nil,
100
+ ) {
101
+ log(.trace, message(), id: id, timestamp: timestamp, metadata: metadata)
102
+ }
103
+
104
+ public func debug(
105
+ _ message: @autoclosure () -> String,
106
+ id: String? = nil,
107
+ timestamp: String? = nil,
108
+ metadata: Metadata? = nil,
109
+ ) {
110
+ log(.debug, message(), id: id, timestamp: timestamp, metadata: metadata)
111
+ }
112
+
113
+ public func info(
114
+ _ message: @autoclosure () -> String,
115
+ id: String? = nil,
116
+ timestamp: String? = nil,
117
+ metadata: Metadata? = nil,
118
+ ) {
119
+ log(.info, message(), id: id, timestamp: timestamp, metadata: metadata)
120
+ }
121
+
122
+ public func notice(
123
+ _ message: @autoclosure () -> String,
124
+ id: String? = nil,
125
+ timestamp: String? = nil,
126
+ metadata: Metadata? = nil,
127
+ ) {
128
+ log(.notice, message(), id: id, timestamp: timestamp, metadata: metadata)
129
+ }
130
+
131
+ public func warning(
132
+ _ message: @autoclosure () -> String,
133
+ id: String? = nil,
134
+ timestamp: String? = nil,
135
+ metadata: Metadata? = nil,
136
+ ) {
137
+ log(.warning, message(), id: id, timestamp: timestamp, metadata: metadata)
138
+ }
139
+
140
+ public func warn(
141
+ _ message: @autoclosure () -> String,
142
+ id: String? = nil,
143
+ timestamp: String? = nil,
144
+ metadata: Metadata? = nil,
145
+ ) {
146
+ warning(message(), id: id, timestamp: timestamp, metadata: metadata)
147
+ }
148
+
149
+ public func error(
150
+ _ message: @autoclosure () -> String,
151
+ id: String? = nil,
152
+ timestamp: String? = nil,
153
+ metadata: Metadata? = nil,
154
+ error: (any Error)? = nil,
155
+ ) {
156
+ log(.error, message(), id: id, timestamp: timestamp, metadata: metadata, error: error)
157
+ }
158
+
159
+ public func fault(
160
+ _ message: @autoclosure () -> String,
161
+ id: String? = nil,
162
+ timestamp: String? = nil,
163
+ metadata: Metadata? = nil,
164
+ error: (any Error)? = nil,
165
+ ) {
166
+ log(.fault, message(), id: id, timestamp: timestamp, metadata: metadata, error: error)
167
+ }
168
+
169
+ public func critical(
170
+ _ message: @autoclosure () -> String,
171
+ id: String? = nil,
172
+ timestamp: String? = nil,
173
+ metadata: Metadata? = nil,
174
+ error: (any Error)? = nil,
175
+ ) {
176
+ log(.critical, message(), id: id, timestamp: timestamp, metadata: metadata, error: error)
177
+ }
178
+
179
+ public func flush() {
180
+ guard let transport, client.pendingEvents() > 0 else {
181
+ return
182
+ }
183
+
184
+ lock.lock()
185
+ defer {
186
+ lock.unlock()
187
+ }
188
+
189
+ do {
190
+ _ = try client.flush(transport: transport)
191
+ } catch {
192
+ onError?(error)
193
+ }
194
+ }
195
+
196
+ private func capture(_ level: LogBrewLoggerLevel, _ message: String, call: LogBrewLoggerCall) {
197
+ lock.lock()
198
+ defer {
199
+ lock.unlock()
200
+ }
201
+
202
+ do {
203
+ try client.log(
204
+ call.id ?? nextEventID(),
205
+ timestamp: call.timestamp ?? timestampProvider(),
206
+ attributes: LogAttributes(
207
+ message: message,
208
+ level: level.logBrewLevel,
209
+ logger: loggerName,
210
+ metadata: logMetadata(level: level, metadata: call.metadata, error: call.error),
211
+ ),
212
+ )
213
+ if flushOnLog, let transport {
214
+ _ = try client.flush(transport: transport)
215
+ }
216
+ } catch {
217
+ onError?(error)
218
+ }
219
+ }
220
+
221
+ private func nextEventID() -> String {
222
+ nextEventNumber += 1
223
+ return "\(eventIDPrefix)_\(nextEventNumber)"
224
+ }
225
+
226
+ private func logMetadata(
227
+ level: LogBrewLoggerLevel,
228
+ metadata: Metadata?,
229
+ error: (any Error)?,
230
+ ) -> Metadata {
231
+ var merged = baseMetadata
232
+ metadata?.forEach { key, value in
233
+ merged[key] = value
234
+ }
235
+ merged["source"] = "swift"
236
+ merged["swiftLogLevel"] = .string(level.rawValue)
237
+ if let subsystem {
238
+ merged["swiftSubsystem"] = .string(subsystem)
239
+ }
240
+ if let category {
241
+ merged["swiftCategory"] = .string(category)
242
+ }
243
+ if let error {
244
+ merged["swiftErrorType"] = .string(String(reflecting: type(of: error)))
245
+ merged["swiftErrorDescription"] = .string(error.localizedDescription)
246
+ }
247
+ return merged
248
+ }
249
+
250
+ private static func defaultTimestamp() -> String {
251
+ let formatter = ISO8601DateFormatter()
252
+ formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
253
+ return formatter.string(from: Date())
254
+ }
255
+ }
256
+
257
+ private struct LogBrewLoggerCall {
258
+ let id: String?
259
+ let timestamp: String?
260
+ let metadata: Metadata?
261
+ let error: (any Error)?
262
+ }