Skip to content

Commit 391bd71

Browse files
authored
Merge 4446d9f into d14bd20
2 parents d14bd20 + 4446d9f commit 391bd71

22 files changed

+1291
-752
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
- Add SentryDistribution as Swift Package Manager target (#6149)
2929
- Add option `enablePropagateTraceparent` to support OTel/W3C trace propagation (#6356)
30+
- Structured Logs: Add `captureLog` to `Hub` and `Client` (#6518)
3031

3132
### Fixes
3233

Sentry.xcodeproj/project.pbxproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,8 @@
718718
92235CAE2E15549C00865983 /* SentryLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAD2E15549C00865983 /* SentryLogger.swift */; };
719719
92235CB02E155B2600865983 /* SentryLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAF2E155B2600865983 /* SentryLoggerTests.swift */; };
720720
925824C22CB5897700C9B20B /* SentrySessionReplayIntegration-Hybrid.h in Headers */ = {isa = PBXBuildFile; fileRef = D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */; settings = {ATTRIBUTES = (Private, ); }; };
721+
92622E092EABB71000ABE7FF /* SentryLogSPMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92622E082EABB71000ABE7FF /* SentryLogSPMTests.swift */; };
722+
92622E142EABBDA900ABE7FF /* SentryLog+SPM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92622E132EABBDA900ABE7FF /* SentryLog+SPM.swift */; };
721723
9264E1EB2E2E385E00B077CF /* SentryLogMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */; };
722724
9264E1ED2E2E397C00B077CF /* SentryLogMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EC2E2E397400B077CF /* SentryLogMessageTests.swift */; };
723725
92672BB629C9A2A9006B021C /* SentryBreadcrumb+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -732,7 +734,6 @@
732734
92D957732E05A44600E20E66 /* SentryAsyncLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 92D957722E05A44600E20E66 /* SentryAsyncLog.m */; };
733735
92D957772E05A4F300E20E66 /* SentryAsyncLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 92D957762E05A4F300E20E66 /* SentryAsyncLog.h */; };
734736
92E5F3D62CDBB3BF00B7AD98 /* SentrySampling.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8C57A525EEFC42001CEEFA /* SentrySampling.h */; };
735-
92EC54CE2E1EB54B00A10AC2 /* SentryClient+Logs.h in Headers */ = {isa = PBXBuildFile; fileRef = 92EC54CD2E1EB54B00A10AC2 /* SentryClient+Logs.h */; };
736737
92ECD7202E05A7DF0063EC10 /* SentryLogC.h in Headers */ = {isa = PBXBuildFile; fileRef = D8AE48B12C5786AA0092A2A6 /* SentryLogC.h */; settings = {ATTRIBUTES = (Private, ); }; };
737738
92ECD73C2E05ACE00063EC10 /* SentryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92ECD73B2E05ACDE0063EC10 /* SentryLog.swift */; };
738739
92ECD73E2E05AD320063EC10 /* SentryLogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92ECD73D2E05AD2B0063EC10 /* SentryLogLevel.swift */; };
@@ -2078,6 +2079,8 @@
20782079
92235CAB2E15369900865983 /* SentryLogBatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogBatcher.swift; sourceTree = "<group>"; };
20792080
92235CAD2E15549C00865983 /* SentryLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogger.swift; sourceTree = "<group>"; };
20802081
92235CAF2E155B2600865983 /* SentryLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLoggerTests.swift; sourceTree = "<group>"; };
2082+
92622E082EABB71000ABE7FF /* SentryLogSPMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogSPMTests.swift; sourceTree = "<group>"; };
2083+
92622E132EABBDA900ABE7FF /* SentryLog+SPM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryLog+SPM.swift"; sourceTree = "<group>"; };
20812084
9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogMessage.swift; sourceTree = "<group>"; };
20822085
9264E1EC2E2E397400B077CF /* SentryLogMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogMessageTests.swift; sourceTree = "<group>"; };
20832086
92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryBreadcrumb+Private.h"; path = "include/HybridPublic/SentryBreadcrumb+Private.h"; sourceTree = "<group>"; };
@@ -2091,7 +2094,6 @@
20912094
92B6BDAC2E05B9F700D538B3 /* SentryLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogTests.swift; sourceTree = "<group>"; };
20922095
92D957722E05A44600E20E66 /* SentryAsyncLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryAsyncLog.m; sourceTree = "<group>"; };
20932096
92D957762E05A4F300E20E66 /* SentryAsyncLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryAsyncLog.h; path = include/SentryAsyncLog.h; sourceTree = "<group>"; };
2094-
92EC54CD2E1EB54B00A10AC2 /* SentryClient+Logs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryClient+Logs.h"; path = "include/SentryClient+Logs.h"; sourceTree = "<group>"; };
20952097
92ECD73B2E05ACDE0063EC10 /* SentryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLog.swift; sourceTree = "<group>"; };
20962098
92ECD73D2E05AD2B0063EC10 /* SentryLogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogLevel.swift; sourceTree = "<group>"; };
20972099
92ECD73F2E05AD500063EC10 /* SentryLogAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogAttribute.swift; sourceTree = "<group>"; };
@@ -2685,6 +2687,7 @@
26852687
84B0E0062CD963F9007FB332 /* SentryIconography.swift */,
26862688
621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */,
26872689
F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */,
2690+
92622E132EABBDA900ABE7FF /* SentryLog+SPM.swift */,
26882691
F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */,
26892692
);
26902693
path = Helper;
@@ -3112,7 +3115,6 @@
31123115
63AA76941EB9C1C200D153DE /* SentryClient.h */,
31133116
63AA75ED1EB8B3C400D153DE /* SentryClient.m */,
31143117
7B85DC1C24EFAFCD007D01D2 /* SentryClient+Private.h */,
3115-
92EC54CD2E1EB54B00A10AC2 /* SentryClient+Logs.h */,
31163118
7B610D5E2512390E00B0B5D9 /* SentrySDK+Private.h */,
31173119
FA6555132E30181B009917BC /* SentrySDKInternal.h */,
31183120
FA6555152E30182B009917BC /* SentrySDKInternal.m */,
@@ -3607,6 +3609,7 @@
36073609
F4A930242E661856006DA6EF /* SentryMobileProvisionParserTests.swift */,
36083610
D4F7BD7C2E4373BB004A2D77 /* SentryLevelMapperTests.swift */,
36093611
D8AE48BE2C578D540092A2A6 /* SentrySDKLog.swift */,
3612+
92622E082EABB71000ABE7FF /* SentryLogSPMTests.swift */,
36103613
849AC3FF29E0C1FF00889C16 /* SentryFormatterTests.swift */,
36113614
7B88F30324BC8E6500ADF90A /* SentrySerializationTests.swift */,
36123615
62F4DDA02C04CB9700588890 /* SentryBaggageSerializationTests.swift */,
@@ -5147,7 +5150,6 @@
51475150
8E7C98312693E1CC00E6336C /* SentryTraceHeader.h in Headers */,
51485151
62C316812B1F2E93000D7031 /* SentryDelayedFramesTracker.h in Headers */,
51495152
92D957772E05A4F300E20E66 /* SentryAsyncLog.h in Headers */,
5150-
92EC54CE2E1EB54B00A10AC2 /* SentryClient+Logs.h in Headers */,
51515153
7B8713AE26415ADF006D6004 /* SentryAppStartTrackingIntegration.h in Headers */,
51525154
7B7D873224864BB900D2ECFF /* SentryCrashMachineContextWrapper.h in Headers */,
51535155
861265F92404EC1500C4AFDE /* SentryArray.h in Headers */,
@@ -6026,6 +6028,7 @@
60266028
7B14089824878F950035403D /* SentryCrashStackEntryMapper.m in Sources */,
60276029
D8BC28C82BFF5EBB0054DA4D /* SentryTouchTracker.swift in Sources */,
60286030
63FE711720DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.c in Sources */,
6031+
92622E142EABBDA900ABE7FF /* SentryLog+SPM.swift in Sources */,
60296032
FA3A42722E1C5F9B00A08C39 /* SentryNSNotificationCenterWrapper.swift in Sources */,
60306033
63FE70CB20DA4C1000CDBAE8 /* SentryCrashReportFixer.c in Sources */,
60316034
F4A930232E65FDBF006DA6EF /* SentryMobileProvisionParser.swift in Sources */,
@@ -6134,6 +6137,7 @@
61346137
7BE3C78724472E9800A38442 /* TestRequestManager.swift in Sources */,
61356138
63FE722220DA66EC00CDBAE8 /* SentryCrashJSONCodec_Tests.m in Sources */,
61366139
7B0A5452252311CE00A71716 /* SentryBreadcrumbTests.swift in Sources */,
6140+
92622E092EABB71000ABE7FF /* SentryLogSPMTests.swift in Sources */,
61376141
7BE3C7752445C82300A38442 /* SentryCurrentDateTests.swift in Sources */,
61386142
7B3398672459C4AE00BD9C96 /* SentryEnvelopeRateLimitTests.swift in Sources */,
61396143
8EA9AF492665AC48002771B4 /* SentryPerformanceTrackerTests.swift in Sources */,

SentryTestUtils/TestClient.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ public class TestClient: SentryClient {
166166
flushInvocations.record(timeout)
167167
}
168168

169-
public var captureLogsDataInvocations = Invocations<(data: Data, count: NSNumber)>()
170-
public override func captureLogsData(_ data: Data, with count: NSNumber) {
171-
captureLogsDataInvocations.record((data, count))
169+
public var captureLogInvocations = Invocations<(log: SentryLog, scope: Scope)>()
170+
public override func capture(log: SentryLog, scope: Scope) {
171+
captureLogInvocations.record((log, scope))
172172
}
173173
}

Sources/Sentry/Public/SentryClient.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@class SentryOptions;
1313
@class SentryScope;
1414
@class SentryTransaction;
15+
@class SentryLog;
1516

1617
NS_ASSUME_NONNULL_BEGIN
1718

@@ -101,6 +102,14 @@ SENTRY_NO_INIT
101102
- (void)captureFeedback:(SentryFeedback *)feedback
102103
withScope:(SentryScope *)scope NS_SWIFT_NAME(capture(feedback:scope:));
103104

105+
/**
106+
* Captures a log entry and sends it to Sentry.
107+
* @param log The log entry to send to Sentry.
108+
* @param scope The current scope from which to gather contextual information.
109+
*/
110+
- (void)captureLog:(SentryLog *)log
111+
withScope:(SentryScope *)scope NS_SWIFT_NAME(capture(log:scope:));
112+
104113
/**
105114
* Waits synchronously for the SDK to flush out all queued and cached items for up to the specified
106115
* timeout in seconds. If there is no internet connection, the function returns immediately. The SDK

Sources/Sentry/Public/SentryHub.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
@class SentryScope;
1616
@class SentryTransactionContext;
1717
@class SentryUser;
18+
@class SentryLog;
1819

1920
NS_ASSUME_NONNULL_BEGIN
2021
@interface SentryHub : NSObject
@@ -175,6 +176,20 @@ SENTRY_NO_INIT
175176
*/
176177
- (void)captureFeedback:(SentryFeedback *)feedback;
177178

179+
/**
180+
* Captures a log entry and sends it to Sentry.
181+
* @param log The log entry to send to Sentry.
182+
*/
183+
- (void)captureLog:(SentryLog *)log NS_SWIFT_NAME(capture(log:));
184+
185+
/**
186+
* Captures a log entry and sends it to Sentry.
187+
* @param log The log entry to send to Sentry.
188+
* @param scope The scope containing event metadata.
189+
*/
190+
- (void)captureLog:(SentryLog *)log
191+
withScope:(SentryScope *)scope NS_SWIFT_NAME(capture(log:scope:));
192+
178193
/**
179194
* Use this method to modify the Scope of the Hub. The SDK uses the Scope to attach
180195
* contextual data to events.

Sources/Sentry/SentryClient.m

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@
4646

4747
NS_ASSUME_NONNULL_BEGIN
4848

49-
@interface SentryClient ()
49+
@interface SentryClient () <SentryLogBatcherDelegate>
5050

5151
@property (nonatomic, strong) SentryTransportAdapter *transportAdapter;
5252
@property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider;
5353
@property (nonatomic, strong) id<SentryRandomProtocol> random;
5454
@property (nonatomic, strong) NSLocale *locale;
5555
@property (nonatomic, strong) NSTimeZone *timezone;
56+
@property (nonatomic, strong) SentryLogBatcher *logBatcher;
5657

5758
@end
5859

@@ -149,6 +150,10 @@ - (instancetype)initWithOptions:(SentryOptions *)options
149150
self.locale = locale;
150151
self.timezone = timezone;
151152
self.attachmentProcessors = [[NSMutableArray alloc] init];
153+
self.logBatcher = [[SentryLogBatcher alloc]
154+
initWithOptions:options
155+
dispatchQueue:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper];
156+
self.logBatcher.delegate = self;
152157

153158
// The SDK stores the installationID in a file. The first call requires file IO. To avoid
154159
// executing this on the main thread, we cache the installationID async here.
@@ -654,7 +659,11 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event
654659

655660
- (void)flush:(NSTimeInterval)timeout
656661
{
657-
[self.transportAdapter flush:timeout];
662+
NSTimeInterval captureLogsDuration = [self.logBatcher captureLogs];
663+
// Capturing batched logs should never take long, but we need to fall back to a sane value.
664+
// This is a workaround for experimental logs, until we'll write batched logs to disk,
665+
// to avoid data loss due to crashes. This is a trade-off until then.
666+
[self.transportAdapter flush:fmax(timeout / 2, timeout - captureLogsDuration)];
658667
}
659668

660669
- (void)close
@@ -1111,7 +1120,12 @@ - (void)removeAttachmentProcessor:(id<SentryClientAttachmentProcessor>)attachmen
11111120
return processedAttachments;
11121121
}
11131122

1114-
- (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount;
1123+
- (void)captureLog:(SentryLog *)log withScope:(SentryScope *)scope
1124+
{
1125+
[self.logBatcher addLog:log scope:scope];
1126+
}
1127+
1128+
- (void)captureLogsData:(NSData *)data with:(NSNumber *)itemCount
11151129
{
11161130
SentryEnvelopeItemHeader *header =
11171131
[[SentryEnvelopeItemHeader alloc] initWithType:SentryEnvelopeItemTypes.log

Sources/Sentry/SentryHub.m

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,20 @@ - (void)captureFeedback:(SentryFeedback *)feedback
531531
}
532532
}
533533

534+
- (void)captureLog:(SentryLog *)log NS_SWIFT_NAME(capture(log:))
535+
{
536+
[self captureLog:log withScope:self.scope];
537+
}
538+
539+
- (void)captureLog:(SentryLog *)log
540+
withScope:(SentryScope *)scope NS_SWIFT_NAME(capture(log:scope:))
541+
{
542+
SentryClient *client = self.client;
543+
if (client != nil) {
544+
[client captureLog:log withScope:scope];
545+
}
546+
}
547+
534548
- (void)captureSerializedFeedback:(NSDictionary *)serializedFeedback
535549
withEventId:(NSString *)feedbackEventId
536550
attachments:(NSArray<SentryAttachment *> *)feedbackAttachments

Sources/Sentry/include/SentryClient+Logs.h

Lines changed: 0 additions & 14 deletions
This file was deleted.

Sources/Sentry/include/SentryPrivate.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#import "SentryANRTrackerV1.h"
2828
#import "SentryANRTrackerV2.h"
2929
#import "SentryAsyncLog.h"
30-
#import "SentryClient+Logs.h"
3130
#import "SentryContinuousProfiler.h"
3231
#import "SentryCrash.h"
3332
#import "SentryCrashDebug.h"
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
@_implementationOnly import _SentryPrivate
2+
import Foundation
3+
4+
// Swift extensions to provide properly typed log-related APIs for SPM builds.
5+
// In SPM builds, SentryLog is only forward declared in the Objective-C headers,
6+
// causing Swift-to-Objective-C bridging issues. These extensions work around that
7+
// by providing Swift-native methods and properties that use dynamic dispatch internally.
8+
9+
@objc
10+
protocol CaptureLogSelectors {
11+
func captureLog(_ log: SentryLog)
12+
func captureLog(_ log: SentryLog, withScope: Scope)
13+
}
14+
15+
/// Helper class to handle dynamic dispatch for log capture.
16+
/// This is used in SPM builds to work around Swift-to-Objective-C bridging issues.
17+
@objc
18+
class CaptureLogDispatcher: NSObject {
19+
20+
/// Captures a log using dynamic dispatch on the target object
21+
/// - Parameters:
22+
/// - log: The log to capture
23+
/// - target: The object that should handle the log capture (typically SentryHub)
24+
/// - Returns: true if the log was captured, false if the selector was not available
25+
@discardableResult
26+
static func captureLog(_ log: SentryLog, on target: NSObject) -> Bool {
27+
let selector = #selector(CaptureLogSelectors.captureLog(_:))
28+
guard target.responds(to: selector) else {
29+
SentrySDKLog.error("Target \(type(of: target)) does not respond to captureLog(_:). The log will not be captured.")
30+
return false
31+
}
32+
target.perform(selector, with: log)
33+
return true
34+
}
35+
36+
/// Captures a log with a scope using dynamic dispatch on the target object
37+
/// - Parameters:
38+
/// - log: The log to capture
39+
/// - scope: The scope containing event metadata
40+
/// - target: The object that should handle the log capture (typically SentryHub or SentryClient)
41+
/// - Returns: true if the log was captured, false if the selector was not available
42+
@discardableResult
43+
static func captureLog(_ log: SentryLog, withScope scope: Scope, on target: NSObject) -> Bool {
44+
let selector = #selector(CaptureLogSelectors.captureLog(_:withScope:))
45+
guard target.responds(to: selector) else {
46+
SentrySDKLog.error("Target \(type(of: target)) does not respond to captureLog(_:withScope:). The log will not be captured.")
47+
return false
48+
}
49+
target.perform(selector, with: log, with: scope)
50+
return true
51+
}
52+
}
53+
54+
#if SWIFT_PACKAGE
55+
56+
/**
57+
* Use this callback to drop or modify a log before the SDK sends it to Sentry. Return `nil` to
58+
* drop the log.
59+
*/
60+
public typealias SentryBeforeSendLogCallback = (SentryLog) -> SentryLog?
61+
62+
@objc
63+
public extension Options {
64+
/**
65+
* Use this callback to drop or modify a log before the SDK sends it to Sentry. Return `nil` to
66+
* drop the log.
67+
*/
68+
@objc
69+
var beforeSendLog: SentryBeforeSendLogCallback? {
70+
get { return value(forKey: "beforeSendLogDynamic") as? SentryBeforeSendLogCallback }
71+
set { setValue(newValue, forKey: "beforeSendLogDynamic") }
72+
}
73+
}
74+
75+
@objc
76+
public extension SentryHub {
77+
/// Captures a log entry and sends it to Sentry.
78+
/// - Parameter log: The log entry to send to Sentry.
79+
///
80+
/// This method is provided for SPM builds where the Objective-C `captureLog:` method
81+
/// may not be properly bridged due to `SentryLog` being defined in Swift.
82+
func capture(log: SentryLog) {
83+
CaptureLogDispatcher.captureLog(log, on: self)
84+
}
85+
86+
/// Captures a log entry and sends it to Sentry with a specific scope.
87+
/// - Parameters:
88+
/// - log: The log entry to send to Sentry.
89+
/// - scope: The scope containing event metadata.
90+
///
91+
/// This method is provided for SPM builds where the Objective-C `captureLog:withScope:` method
92+
/// may not be properly bridged due to `SentryLog` being defined in Swift.
93+
func capture(log: SentryLog, scope: Scope) {
94+
CaptureLogDispatcher.captureLog(log, withScope: scope, on: self)
95+
}
96+
}
97+
98+
/// Extension to provide log capture methods for SPM builds.
99+
@objc
100+
public extension SentryClient {
101+
/// Captures a log entry and sends it to Sentry.
102+
/// - Parameters:
103+
/// - log: The log entry to send to Sentry.
104+
/// - scope: The scope containing event metadata.
105+
func captureLog(_ log: SentryLog, withScope scope: Scope) {
106+
CaptureLogDispatcher.captureLog(log, withScope: scope, on: self)
107+
}
108+
}
109+
110+
#endif // SWIFT_PACKAGE

0 commit comments

Comments
 (0)