Skip to content

Commit 914e7e1

Browse files
Merge pull request #2180 from DataDog/maxep/RUM-5176/sync-error
RUM-5176 [CP] Allow reporting RUM error in sync Co-authored-by: maxep <maxime.epain@datadoghq.com>
2 parents d73e907 + 91164fa commit 914e7e1

21 files changed

Lines changed: 322 additions & 49 deletions

File tree

Datadog/Datadog.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,8 @@
18621862
D2EFA869286DA85700F1FAA6 /* DatadogContextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EFA867286DA85700F1FAA6 /* DatadogContextProvider.swift */; };
18631863
D2EFA875286E011900F1FAA6 /* DatadogContextProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EFA874286E011900F1FAA6 /* DatadogContextProviderTests.swift */; };
18641864
D2EFA876286E011900F1FAA6 /* DatadogContextProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EFA874286E011900F1FAA6 /* DatadogContextProviderTests.swift */; };
1865+
D2F448E12D43A3DC007BB995 /* CompletionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F448E02D43A3DC007BB995 /* CompletionHandler.swift */; };
1866+
D2F448E22D43A3DC007BB995 /* CompletionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F448E02D43A3DC007BB995 /* CompletionHandler.swift */; };
18651867
D2F44FB8299AA1DA0074B0D9 /* DataCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213532F270CA722000315AD /* DataCompressionTests.swift */; };
18661868
D2F44FB9299AA1DB0074B0D9 /* DataCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213532F270CA722000315AD /* DataCompressionTests.swift */; };
18671869
D2F44FC2299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F44FC1299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift */; };
@@ -3313,6 +3315,7 @@
33133315
D2EFA874286E011900F1FAA6 /* DatadogContextProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogContextProviderTests.swift; sourceTree = "<group>"; };
33143316
D2EFF3D22731822A00D09F33 /* RUMViewsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewsHandler.swift; sourceTree = "<group>"; };
33153317
D2F1B81426D8E5FF009F3293 /* DDNoopTracerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDNoopTracerTests.swift; sourceTree = "<group>"; };
3318+
D2F448E02D43A3DC007BB995 /* CompletionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionHandler.swift; sourceTree = "<group>"; };
33163319
D2F44FC1299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+KeyboardControlling.swift"; sourceTree = "<group>"; };
33173320
D2F8235229915E12003C7E99 /* DatadogSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogSite.swift; sourceTree = "<group>"; };
33183321
D2FB1253292E0E92005B13F8 /* TrackingConsentPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingConsentPublisher.swift; sourceTree = "<group>"; };
@@ -6321,6 +6324,7 @@
63216324
D23039DB298D5235001A1FA3 /* ReadWriteLock.swift */,
63226325
618031F72D6DC430007027E3 /* Threading.swift */,
63236326
D2432CF829EDB22C00D93657 /* Flushable.swift */,
6327+
D2F448E02D43A3DC007BB995 /* CompletionHandler.swift */,
63246328
);
63256329
path = Concurrency;
63266330
sourceTree = "<group>";
@@ -8914,6 +8918,7 @@
89148918
614A708E2BF754D800D9AF42 /* ImmutableRequest.swift in Sources */,
89158919
D2160CF429C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */,
89168920
D23039E1298D5236001A1FA3 /* AppState.swift in Sources */,
8921+
D2F448E22D43A3DC007BB995 /* CompletionHandler.swift in Sources */,
89178922
D2DE63532A30A7CA00441A54 /* CoreRegistry.swift in Sources */,
89188923
D2D748402DC24F1100C61353 /* Crash.swift in Sources */,
89198924
E2AA55EA2C32C76A002FEF28 /* WatchKitExtensions.swift in Sources */,
@@ -10041,6 +10046,7 @@
1004110046
D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */,
1004210047
D2DA236C298D57AA00C6C7E6 /* AppState.swift in Sources */,
1004310048
D28FB6972DB7D3F000CD76D0 /* RUMDataModels.swift in Sources */,
10049+
D2F448E12D43A3DC007BB995 /* CompletionHandler.swift in Sources */,
1004410050
D2DE63542A30A7CA00441A54 /* CoreRegistry.swift in Sources */,
1004510051
E2AA55EC2C32C78B002FEF28 /* WatchKitExtensions.swift in Sources */,
1004610052
D2EBEE3629BA161100B15732 /* W3CHTTPHeadersWriter.swift in Sources */,

DatadogCore/Sources/Core/Storage/Writing/AsyncWriter.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ internal struct AsyncWriter: Writer {
1717
self.queue = queue
1818
}
1919

20-
func write<T: Encodable, M: Encodable>(value: T, metadata: M?) {
21-
queue.async { writer.write(value: value, metadata: metadata) }
20+
func write<T: Encodable, M: Encodable>(value: T, metadata: M?, completion: @escaping CompletionHandler) {
21+
queue.async { writer.write(value: value, metadata: metadata, completion: completion) }
2222
}
2323
}
2424

2525
internal struct NOPWriter: Writer {
26-
func write<T: Encodable, M: Encodable>(value: T, metadata: M?) {
26+
func write<T: Encodable, M: Encodable>(value: T, metadata: M?, completion: @escaping CompletionHandler) {
27+
completion()
2728
}
2829
}

DatadogCore/Sources/Core/Storage/Writing/FileWriter.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ internal struct FileWriter: Writer {
3636
/// - Parameters:
3737
/// - value: Encodable value to write.
3838
/// - metadata: Encodable metadata to write.
39-
func write<T: Encodable, M: Encodable>(value: T, metadata: M?) {
39+
func write<T: Encodable, M: Encodable>(value: T, metadata: M?, completion: @escaping CompletionHandler) {
40+
defer { completion() }
41+
4042
var encoded: Data = .init()
4143
if let metadata = metadata {
4244
do {

DatadogCore/Tests/Datadog/Core/Persistence/Writing/FileWriterTests.swift

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class FileWriterTests: XCTestCase {
2424
}
2525

2626
func testItWritesDataWithMetadataToSingleFileInTLVFormat() throws {
27+
let expectation = expectation(description: "Writes complete")
28+
expectation.expectedFulfillmentCount = 3
29+
2730
let writer = FileWriter(
2831
orchestrator: FilesOrchestrator(
2932
directory: directory,
@@ -35,9 +38,9 @@ class FileWriterTests: XCTestCase {
3538
telemetry: NOPTelemetry()
3639
)
3740

38-
writer.write(value: ["key1": "value1"], metadata: ["meta1": "metaValue1"])
39-
writer.write(value: ["key2": "value2"]) // skipped metadata here
40-
writer.write(value: ["key3": "value3"], metadata: ["meta3": "metaValue3"])
41+
writer.write(value: ["key1": "value1"], metadata: ["meta1": "metaValue1"], completion: expectation.fulfill)
42+
writer.write(value: ["key2": "value2"], completion: expectation.fulfill) // skipped metadata here
43+
writer.write(value: ["key3": "value3"], metadata: ["meta3": "metaValue3"], completion: expectation.fulfill)
4144

4245
XCTAssertEqual(try directory.files().count, 1)
4346
let stream = try directory.files()[0].stream()
@@ -58,6 +61,8 @@ class FileWriterTests: XCTestCase {
5861
block = try reader.next()
5962
XCTAssertEqual(block?.type, .event)
6063
XCTAssertEqual(block?.data, #"{"key3":"value3"}"#.utf8Data)
64+
65+
wait(for: [expectation], timeout: 0)
6166
}
6267

6368
func testItWritesEncryptedDataWithMetadataToSingleFileInTLVFormat() throws {
@@ -102,6 +107,9 @@ class FileWriterTests: XCTestCase {
102107
}
103108

104109
func testItWritesDataToSingleFileInTLVFormat() throws {
110+
let expectation = expectation(description: "Writes complete")
111+
expectation.expectedFulfillmentCount = 3
112+
105113
let writer = FileWriter(
106114
orchestrator: FilesOrchestrator(
107115
directory: directory,
@@ -113,9 +121,9 @@ class FileWriterTests: XCTestCase {
113121
telemetry: NOPTelemetry()
114122
)
115123

116-
writer.write(value: ["key1": "value1"])
117-
writer.write(value: ["key2": "value2"])
118-
writer.write(value: ["key3": "value3"])
124+
writer.write(value: ["key1": "value1"], completion: expectation.fulfill)
125+
writer.write(value: ["key2": "value2"], completion: expectation.fulfill)
126+
writer.write(value: ["key3": "value3"], completion: expectation.fulfill)
119127

120128
XCTAssertEqual(try directory.files().count, 1)
121129
let stream = try directory.files()[0].stream()
@@ -130,9 +138,14 @@ class FileWriterTests: XCTestCase {
130138
block = try reader.next()
131139
XCTAssertEqual(block?.type, .event)
132140
XCTAssertEqual(block?.data, #"{"key3":"value3"}"#.utf8Data)
141+
142+
wait(for: [expectation], timeout: 0)
133143
}
134144

135145
func testGivenErrorVerbosity_whenIndividualDataExceedsMaxWriteSize_itDropsDataAndPrintsError() throws {
146+
let expectation = expectation(description: "Writes complete")
147+
expectation.expectedFulfillmentCount = 2
148+
136149
let dd = DD.mockWith(logger: CoreLoggerMock())
137150
defer { dd.reset() }
138151

@@ -161,24 +174,28 @@ class FileWriterTests: XCTestCase {
161174
telemetry: NOPTelemetry()
162175
)
163176

164-
writer.write(value: ["key1": "value1"]) // will be written
177+
writer.write(value: ["key1": "value1"], completion: expectation.fulfill) // will be written
165178

166179
XCTAssertEqual(try directory.files().count, 1)
167180
var reader = try BatchDataBlockReader(input: directory.files()[0].stream())
168181
var blocks = try XCTUnwrap(reader.all())
169182
XCTAssertEqual(blocks.count, 1)
170183
XCTAssertEqual(blocks[0].data, #"{"key1":"value1"}"#.utf8Data)
171184

172-
writer.write(value: ["key2": "value3 that makes it exceed 23 bytes"]) // will be dropped
185+
writer.write(value: ["key2": "value3 that makes it exceed 23 bytes"], completion: expectation.fulfill) // will be dropped
173186

174187
reader = try BatchDataBlockReader(input: directory.files()[0].stream())
175188
blocks = try XCTUnwrap(reader.all())
176189
XCTAssertEqual(blocks.count, 1) // same content as before
177190
XCTAssertEqual(dd.logger.errorLog?.message, "(rum) Failed to encode value")
178191
XCTAssertEqual(dd.logger.errorLog?.error?.message, "DataBlock with \(47) bytes exceeds limit of \(23) bytes")
192+
193+
wait(for: [expectation], timeout: 0)
179194
}
180195

181196
func testGivenErrorVerbosity_whenDataCannotBeEncoded_itPrintsError() throws {
197+
let expectation = expectation(description: "Writes complete")
198+
182199
let dd = DD.mockWith(logger: CoreLoggerMock())
183200
defer { dd.reset() }
184201

@@ -199,13 +216,18 @@ class FileWriterTests: XCTestCase {
199216
telemetry: NOPTelemetry()
200217
)
201218

202-
writer.write(value: FailingEncodableMock(errorMessage: "failed to encode `FailingEncodable`."))
219+
writer.write(value: FailingEncodableMock(errorMessage: "failed to encode `FailingEncodable`."), completion: expectation.fulfill)
203220

204221
XCTAssertEqual(dd.logger.errorLog?.message, "(rum) Failed to encode value")
205222
XCTAssertEqual(dd.logger.errorLog?.error?.message, "failed to encode `FailingEncodable`.")
223+
224+
wait(for: [expectation], timeout: 0)
206225
}
207226

208227
func testGivenErrorVerbosity_whenIOExceptionIsThrown_itPrintsError() throws {
228+
let expectation = expectation(description: "Writes complete")
229+
expectation.expectedFulfillmentCount = 2
230+
209231
let dd = DD.mockWith(logger: CoreLoggerMock())
210232
defer { dd.reset() }
211233

@@ -226,13 +248,15 @@ class FileWriterTests: XCTestCase {
226248
telemetry: NOPTelemetry()
227249
)
228250

229-
writer.write(value: ["ok"]) // will create the file
251+
writer.write(value: ["ok"], completion: expectation.fulfill) // will create the file
230252
try? directory.files()[0].makeReadonly()
231-
writer.write(value: ["won't be written"])
253+
writer.write(value: ["won't be written"], completion: expectation.fulfill)
232254
try? directory.files()[0].makeReadWrite()
233255

234256
XCTAssertEqual(dd.logger.errorLog?.message, "(rum) Failed to write 26 bytes to file")
235257
XCTAssertTrue(dd.logger.errorLog!.error!.message.contains("You don’t have permission"))
258+
259+
wait(for: [expectation], timeout: 0)
236260
}
237261

238262
/// NOTE: Test added after incident-4797

DatadogCore/Tests/Datadog/Mocks/RUM/RUMFeatureMocks.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,15 @@ extension RUMAddCurrentViewErrorCommand: AnyMockable, RandomMockable {
275275
error: Error = ErrorMock(),
276276
source: RUMInternalErrorSource = .source,
277277
globalAttributes: [AttributeKey: AttributeValue] = [:],
278-
attributes: [AttributeKey: AttributeValue] = [:]
278+
attributes: [AttributeKey: AttributeValue] = [:],
279+
completionHandler: @escaping CompletionHandler = NOPCompletionHandler
279280
) -> RUMAddCurrentViewErrorCommand {
280281
return RUMAddCurrentViewErrorCommand(
281282
time: time,
282283
error: error,
283284
source: source,
284-
attributes: attributes
285+
attributes: attributes,
286+
completionHandler: completionHandler
285287
)
286288
}
287289

@@ -291,15 +293,17 @@ extension RUMAddCurrentViewErrorCommand: AnyMockable, RandomMockable {
291293
type: String? = .mockAny(),
292294
source: RUMInternalErrorSource = .source,
293295
stack: String? = "Foo.swift:10",
294-
attributes: [AttributeKey: AttributeValue] = [:]
296+
attributes: [AttributeKey: AttributeValue] = [:],
297+
completionHandler: @escaping CompletionHandler = NOPCompletionHandler
295298
) -> RUMAddCurrentViewErrorCommand {
296299
return RUMAddCurrentViewErrorCommand(
297300
time: time,
298301
message: message,
299302
type: type,
300303
stack: stack,
301304
source: source,
302-
attributes: attributes
305+
attributes: attributes,
306+
completionHandler: completionHandler
303307
)
304308
}
305309
}

DatadogCore/Tests/Objc/DDRUMMonitorTests.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,12 @@ class DDRUMMonitorTests: XCTestCase {
388388
objcRUMMonitor.addError(error: error, source: .custom, attributes: ["event-attribute1": "foo1"])
389389
objcRUMMonitor.addError(message: "error message", stack: "error stack", source: .source, attributes: [:])
390390

391+
objcRUMMonitor._internal_sync_addError(NSError.mockAny(), source: .custom, attributes: [:])
392+
391393
let rumEventMatchers = try core.waitAndReturnRUMEventMatchers()
392394

393395
let errorEvents = rumEventMatchers.filterRUMEvents(ofType: RUMErrorEvent.self)
394-
XCTAssertEqual(errorEvents.count, 4)
396+
XCTAssertEqual(errorEvents.count, 5)
395397

396398
let event1Matcher = errorEvents[0]
397399
let event1: RUMErrorEvent = try event1Matcher.model()
@@ -426,6 +428,12 @@ class DDRUMMonitorTests: XCTestCase {
426428
XCTAssertEqual(event4.error.message, "error message")
427429
XCTAssertEqual(event4.error.source, .source)
428430
XCTAssertEqual(event4.error.stack, "error stack")
431+
432+
let event5Matcher = errorEvents[4]
433+
let event5: RUMErrorEvent = try event5Matcher.model()
434+
XCTAssertEqual(event5.error.type, "abc - 0")
435+
XCTAssertEqual(event5.error.source, .custom)
436+
XCTAssertEqual(event5.error.message, #"Error Domain=abc Code=0 "(null)""#)
429437
}
430438

431439
func testSendingActionEvents() throws {

DatadogCore/Tests/Objc/ObjcAPITests/DDRUMMonitor+apiTests.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ - (void)testDDRUMMonitorAPI {
8282
[monitor removeAttributesForKeys:@[@"string",@"integer",@"boolean"]];
8383
[monitor addFeatureFlagEvaluationWithName: @"name" value: @"value"];
8484

85+
[monitor _internal_sync_addError:[NSError errorWithDomain:NSCocoaErrorDomain code:-100 userInfo:nil]
86+
source:DDRUMErrorSourceCustom attributes:@{}];
87+
8588
[monitor setDebug:YES];
8689
[monitor setDebug:NO];
8790
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2019-Present Datadog, Inc.
5+
*/
6+
7+
import Foundation
8+
9+
/// Alias from completion closure with no parameter.
10+
public typealias CompletionHandler = () -> Void
11+
12+
/// No-op completion function.
13+
///
14+
/// Using a function prevent allocating a closure when applying a placeholder.
15+
public func NOPCompletionHandler() {}

DatadogInternal/Sources/Storage/Writer.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,38 @@ import Foundation
99
/// A type, writing data.
1010
public protocol Writer {
1111
/// Encodes given encodable value and metadata, and writes to the destination.
12-
/// - Parameter value: Encodable value to write.
13-
/// - Parameter metadata: Encodable metadata to write.
14-
func write<T: Encodable, M: Encodable>(value: T, metadata: M?)
12+
/// - Parameters:
13+
/// - value: Encodable value to write.
14+
/// - metadata: Encodable metadata to write.
15+
/// - completion: The block to execute after the write task is completed.
16+
func write<T: Encodable, M: Encodable>(value: T, metadata: M?, completion: @escaping CompletionHandler)
1517
}
1618

1719
extension Writer {
1820
/// Encodes given encodable value and writes to the destination.
1921
/// Uses `write(value:metadata:)` with `nil` metadata.
22+
///
2023
/// - Parameter value: Encodable value to write.
2124
public func write<T: Encodable>(value: T) {
25+
write(value: value, completion: {})
26+
}
27+
28+
/// Encodes given encodable value and writes to the destination.
29+
/// Uses `write(value:metadata:)` with `nil` metadata.
30+
///
31+
/// - Parameters:
32+
/// - value: Encodable value to write.
33+
/// - completion: The block to execute after the write task is completed.
34+
public func write<T: Encodable>(value: T, completion: @escaping CompletionHandler) {
2235
let metadata: Data? = nil
23-
write(value: value, metadata: metadata)
36+
write(value: value, metadata: metadata, completion: completion)
37+
}
38+
39+
/// Encodes given encodable value and metadata, and writes to the destination.
40+
/// - Parameters:
41+
/// - value: Encodable value to write.
42+
/// - metadata: Encodable metadata to write.
43+
public func write<T: Encodable, M: Encodable>(value: T, metadata: M?) {
44+
write(value: value, metadata: metadata, completion: {})
2445
}
2546
}

DatadogRUM/Sources/RUM+objc.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,3 +690,35 @@ public class objc_RUMMonitor: NSObject {
690690
get { swiftRUMMonitor.debug }
691691
}
692692
}
693+
694+
extension objc_RUMMonitor {
695+
/// **For Datadog internal use only. Subject to changes.**
696+
///
697+
/// Adds RUM error to current RUM view in sync.
698+
///
699+
/// **This method will block the caller thread for maximum 2 seconds.**
700+
///
701+
/// - Parameters:
702+
/// - error: the `Error` object. It will be used to infer error details.
703+
/// - source: the origin of the error.
704+
/// - attributes: custom attributes to attach to this error.
705+
@objc
706+
public func _internal_sync_addError(
707+
_ error: Error,
708+
source: objc_RUMErrorSource,
709+
attributes: [String: Any]
710+
) {
711+
let semaphore = DispatchSemaphore(value: 0)
712+
713+
swiftRUMMonitor.addError(
714+
error: error,
715+
source: source.swiftType,
716+
attributes: attributes.dd.swiftAttributes,
717+
completionHandler: {
718+
semaphore.signal()
719+
}
720+
)
721+
722+
_ = semaphore.wait(timeout: .now() + .seconds(2))
723+
}
724+
}

0 commit comments

Comments
 (0)