Skip to content

Commit eb09954

Browse files
committed
refactor item batcher
1 parent e89eab0 commit eb09954

File tree

8 files changed

+439
-289
lines changed

8 files changed

+439
-289
lines changed

SentryTestUtils/Sources/TestCurrentDateProvider.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Foundation
1212
public var driftTimeInterval = 0.1
1313

1414
private var _systemUptime: TimeInterval = 0
15+
private var _absoluteTime: UInt64 = 0
1516

1617
// NSLock isn't reentrant, so we use NSRecursiveLock.
1718
private let lock = NSRecursiveLock()
@@ -101,4 +102,10 @@ import Foundation
101102
return _timezoneOffsetValue
102103
}
103104
}
105+
106+
public func getAbsoluteTime() -> UInt64 {
107+
return lock.synchronized {
108+
return _absoluteTime
109+
}
110+
}
104111
}

Sources/Sentry/SentryClient.m

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ - (_Nullable instancetype)initWithOptions:(SentryOptions *)options
8585
[[SentryDefaultThreadInspector alloc] initWithOptions:options];
8686

8787
return [self initWithOptions:options
88+
dateProvider:SentryDependencyContainer.sharedInstance.dateProvider
8889
transportAdapter:transportAdapter
8990
fileManager:fileManager
9091
threadInspector:threadInspector
@@ -95,6 +96,7 @@ - (_Nullable instancetype)initWithOptions:(SentryOptions *)options
9596
}
9697

9798
- (instancetype)initWithOptions:(SentryOptions *)options
99+
dateProvider:(id<SentryCurrentDateProvider>)dateProvider
98100
transportAdapter:(SentryTransportAdapter *)transportAdapter
99101
fileManager:(SentryFileManager *)fileManager
100102
threadInspector:(SentryDefaultThreadInspector *)threadInspector
@@ -115,7 +117,9 @@ - (instancetype)initWithOptions:(SentryOptions *)options
115117
self.timezone = timezone;
116118
self.attachmentProcessors = [[NSMutableArray alloc] init];
117119

118-
self.logBatcher = [[SentryLogBatcher alloc] initWithOptions:options delegate:self];
120+
self.logBatcher = [[SentryLogBatcher alloc] initWithOptions:options
121+
dateProvider:dateProvider
122+
delegate:self];
119123

120124
// The SDK stores the installationID in a file. The first call requires file IO. To avoid
121125
// executing this on the main thread, we cache the installationID async here.

Sources/Swift/Core/Helper/SentryCurrentDateProvider.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import Foundation
1010
func timezoneOffset() -> Int
1111
func systemTime() -> UInt64
1212
func systemUptime() -> TimeInterval
13+
func getAbsoluteTime() -> UInt64
14+
}
15+
16+
extension SentryCurrentDateProvider {
17+
public func getAbsoluteTime() -> UInt64 {
18+
clock_gettime_nsec_np(CLOCK_UPTIME_RAW)
19+
}
1320
}
1421

1522
@objcMembers
@@ -34,6 +41,10 @@ import Foundation
3441
ProcessInfo.processInfo.systemUptime
3542
}
3643

44+
public func getAbsoluteTime() -> UInt64 {
45+
SentryDefaultCurrentDateProvider.getAbsoluteTime()
46+
}
47+
3748
public static func getAbsoluteTime() -> UInt64 {
3849
clock_gettime_nsec_np(CLOCK_UPTIME_RAW)
3950
}

Sources/Swift/Tools/SentryItemBatcher.swift

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,55 @@
11
@_implementationOnly import _SentryPrivate
22
import Foundation
33

4-
protocol SentryItemBatcherDelegate: AnyObject {
5-
func capture(itemBatcherData: Data, count: Int)
6-
}
7-
84
protocol SentryItemBatcherItem: Encodable {
95
var attributes: [String: SentryAttribute] { get set }
106
var traceId: SentryId { get set }
117
var body: String { get }
128
}
139

14-
final class SentryItemBatcher<Item: SentryItemBatcherItem> {
10+
protocol SentryItemBatcherScope {
11+
var replayId: String? { get }
12+
var propagationContextTraceIdString: String { get }
13+
var span: Span? { get }
14+
var userObject: User? { get }
15+
func getContextForKey(_ key: String) -> [String: Any]?
16+
var attributes: [String: Any] { get }
17+
}
18+
19+
protocol SentryItemBatcherProtocol<Item, Scope>: AnyObject {
20+
associatedtype Item: SentryItemBatcherItem
21+
associatedtype Scope: SentryItemBatcherScope
22+
23+
func add(_ item: Item, scope: Scope)
24+
func capture() -> TimeInterval
25+
}
26+
27+
extension SentryItemBatcherProtocol {}
28+
29+
final class SentryItemBatcher<Item: SentryItemBatcherItem, Scope: SentryItemBatcherScope>: SentryItemBatcherProtocol {
1530
struct Config {
16-
let beforeSendItem: ((Item) -> Item?)?
1731
let environment: String
1832
let releaseName: String?
1933

2034
let flushTimeout: TimeInterval
2135
let maxItemCount: Int
2236
let maxBufferSizeBytes: Int
2337

38+
let beforeSendItem: ((Item) -> Item?)?
2439
let getInstallationId: () -> String?
40+
41+
var capturedDataCallback: (_ data: Data, _ count: Int) -> Void = { _, _ in }
2542
}
2643

2744
private let config: Config
45+
private let dateProvider: SentryCurrentDateProvider
2846
private let dispatchQueue: SentryDispatchQueueWrapperProtocol
2947

3048
// Every items data is added sepratley. They are flushed together in an envelope.
3149
private var encodedItems: [Data] = []
3250
private var encodedItemsSize: Int = 0
3351
private var timerWorkItem: DispatchWorkItem?
3452

35-
/// The delegate to handle captured item batches
36-
weak var delegate: SentryItemBatcherDelegate?
37-
3853
/// Initializes a new SentryItemBatcher.
3954
/// - Parameters:
4055
/// - options: The Sentry configuration options
@@ -49,13 +64,15 @@ final class SentryItemBatcher<Item: SentryItemBatcherItem> {
4964
/// - Note: Items are flushed when either `maxItemCount` or `maxBufferSizeBytes` limit is reached.
5065
@_spi(Private) public init(
5166
config: Config,
67+
dateProvider: SentryCurrentDateProvider,
5268
dispatchQueue: SentryDispatchQueueWrapperProtocol
5369
) {
5470
self.config = config
71+
self.dateProvider = dateProvider
5572
self.dispatchQueue = dispatchQueue
5673
}
57-
58-
func addItem(_ item: Item, scope: Scope) {
74+
75+
func add(_ item: Item, scope: Scope) {
5976
var item = item
6077
addDefaultAttributes(to: &item.attributes, scope: scope)
6178
addOSAttributes(to: &item.attributes, scope: scope)
@@ -83,13 +100,12 @@ final class SentryItemBatcher<Item: SentryItemBatcherItem> {
83100
}
84101

85102
// Captures batched items sync and returns the duration.
86-
@discardableResult
87-
@_spi(Private) @objc public func captureItems() -> TimeInterval {
88-
let startTimeNs = SentryDefaultCurrentDateProvider.getAbsoluteTime()
103+
@discardableResult func capture() -> TimeInterval {
104+
let startTimeNs = dateProvider.getAbsoluteTime()
89105
dispatchQueue.dispatchSync { [weak self] in
90106
self?.performCaptureItems()
91107
}
92-
let endTimeNs = SentryDefaultCurrentDateProvider.getAbsoluteTime()
108+
let endTimeNs = dateProvider.getAbsoluteTime()
93109
return TimeInterval(endTimeNs - startTimeNs) / 1_000_000_000.0 // Convert nanoseconds to seconds
94110
}
95111

@@ -231,11 +247,6 @@ final class SentryItemBatcher<Item: SentryItemBatcherItem> {
231247
let payloadData = Data("{\"items\":[".utf8) + encodedItems.joined(separator: Data(",".utf8)) + Data("]}".utf8)
232248

233249
// Send the payload.
234-
235-
guard let delegate else {
236-
SentrySDKLog.debug("Delegate not set, not capturing items data.")
237-
return
238-
}
239-
delegate.capture(itemBatcherData: payloadData, count: encodedItems.count)
250+
config.capturedDataCallback(payloadData, encodedItems.count)
240251
}
241252
}

Sources/Swift/Tools/SentryLogBatcher.swift

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ import Foundation
66
func capture(logsData: NSData, count: NSNumber)
77
}
88

9-
extension SentryLog: SentryItemBatcherItem {}
10-
119
@objc
1210
@objcMembers
1311
@_spi(Private) public class SentryLogBatcher: NSObject {
1412
private let options: Options
15-
private let batcher: SentryItemBatcher<SentryLog>
13+
private let batcher: any SentryItemBatcherProtocol<SentryLog, Scope>
1614
private weak var delegate: SentryLogBatcherDelegate?
1715

1816
/// Convenience initializer with default flush timeout, max log count (100), and buffer size.
@@ -26,6 +24,7 @@ extension SentryLog: SentryItemBatcherItem {}
2624
/// - Note: Setting `maxLogCount` to 100. While Replay hard limit is 1000, we keep this lower, as it's hard to lower once released.
2725
@_spi(Private) public convenience init(
2826
options: Options,
27+
dateProvider: SentryCurrentDateProvider,
2928
delegate: SentryLogBatcherDelegate
3029
) {
3130
let dispatchQueue = SentryDispatchQueueWrapper(name: "io.sentry.log-batcher")
@@ -34,6 +33,7 @@ extension SentryLog: SentryItemBatcherItem {}
3433
flushTimeout: 5,
3534
maxLogCount: 100, // Maximum 100 logs per batch
3635
maxBufferSizeBytes: 1_024 * 1_024, // 1MB buffer size
36+
dateProvider: dateProvider,
3737
dispatchQueue: dispatchQueue,
3838
delegate: delegate
3939
)
@@ -57,27 +57,35 @@ extension SentryLog: SentryItemBatcherItem {}
5757
flushTimeout: TimeInterval,
5858
maxLogCount: Int,
5959
maxBufferSizeBytes: Int,
60+
dateProvider: SentryCurrentDateProvider,
6061
dispatchQueue: SentryDispatchQueueWrapper,
6162
delegate: SentryLogBatcherDelegate
6263
) {
6364
self.batcher = SentryItemBatcher(
6465
config: .init(
65-
beforeSendItem: options.beforeSendLog,
6666
environment: options.environment,
6767
releaseName: options.releaseName,
6868
flushTimeout: flushTimeout,
6969
maxItemCount: maxLogCount,
7070
maxBufferSizeBytes: maxBufferSizeBytes,
71+
beforeSendItem: options.beforeSendLog,
7172
getInstallationId: {
7273
SentryInstallation.cachedId(withCacheDirectoryPath: options.cacheDirectoryPath)
74+
},
75+
capturedDataCallback: { [weak delegate] data, count in
76+
guard let delegate else {
77+
SentrySDKLog.debug("SentryLogBatcher: Delegate not set, not capturing logs data.")
78+
return
79+
}
80+
delegate.capture(logsData: data as NSData, count: NSNumber(value: count))
7381
}
7482
),
83+
dateProvider: dateProvider,
7584
dispatchQueue: dispatchQueue
7685
)
7786
self.options = options
7887
self.delegate = delegate
7988
super.init()
80-
self.batcher.delegate = self
8189
}
8290

8391
/// Adds a log to the batcher.
@@ -89,22 +97,15 @@ extension SentryLog: SentryItemBatcherItem {}
8997
return
9098
}
9199

92-
batcher.addItem(log, scope: scope)
100+
batcher.add(log, scope: scope)
93101
}
94102

95103
/// Captures batched logs sync and returns the duration.
96104
@discardableResult
97105
@_spi(Private) @objc public func captureLogs() -> TimeInterval {
98-
return batcher.captureItems()
106+
return batcher.capture()
99107
}
100108
}
101109

102-
extension SentryLogBatcher: SentryItemBatcherDelegate {
103-
func capture(itemBatcherData: Data, count: Int) {
104-
guard let delegate else {
105-
SentrySDKLog.debug("SentryLogBatcher: Delegate not set, not capturing logs data.")
106-
return
107-
}
108-
delegate.capture(logsData: itemBatcherData as NSData, count: NSNumber(value: count))
109-
}
110-
}
110+
extension SentryLog: SentryItemBatcherItem {}
111+
extension Scope: SentryItemBatcherScope {}

Tests/SentryTests/SentryClientTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,7 @@ class SentryClientTests: XCTestCase {
24012401
flushTimeout: 5,
24022402
maxLogCount: 100,
24032403
maxBufferSizeBytes: 1_024 * 1_024,
2404+
dateProvider: TestCurrentDateProvider(),
24042405
dispatchQueue: TestSentryDispatchQueueWrapper(),
24052406
delegate: testDelegate
24062407
)
@@ -2432,6 +2433,7 @@ class SentryClientTests: XCTestCase {
24322433
flushTimeout: 5,
24332434
maxLogCount: 100,
24342435
maxBufferSizeBytes: 1_024 * 1_024,
2436+
dateProvider: TestCurrentDateProvider(),
24352437
dispatchQueue: TestSentryDispatchQueueWrapper(),
24362438
delegate: testDelegate
24372439
)
@@ -2453,6 +2455,7 @@ class SentryClientTests: XCTestCase {
24532455
flushTimeout: 5,
24542456
maxLogCount: 100,
24552457
maxBufferSizeBytes: 1_024 * 1_024,
2458+
dateProvider: TestCurrentDateProvider(),
24562459
dispatchQueue: TestSentryDispatchQueueWrapper(),
24572460
delegate: testDelegate
24582461
)

0 commit comments

Comments
 (0)