From 87829d7d590beaa2559d0e53c1f1eb793a267cb7 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 2 Apr 2020 10:06:41 +0200 Subject: [PATCH 1/4] RUMM-346 Create `ObjcExceptionHandler` --- .gitignore | 3 +++ Datadog/Datadog.xcodeproj/project.pbxproj | 20 ++++++++++++++++ Datadog/DatadogPrivate/ObjcExceptionHandler.h | 18 +++++++++++++++ Datadog/DatadogPrivate/ObjcExceptionHandler.m | 23 +++++++++++++++++++ Datadog/DatadogPrivate/include/SPMHeaders.h | 9 ++++++++ Datadog/DatadogPrivate/module.modulemap | 14 +++++++++++ DatadogSDK.podspec | 10 ++++++-- Package.swift | 6 ++++- .../Benchmark.xcodeproj/project.pbxproj | 2 ++ 9 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 Datadog/DatadogPrivate/ObjcExceptionHandler.h create mode 100644 Datadog/DatadogPrivate/ObjcExceptionHandler.m create mode 100644 Datadog/DatadogPrivate/include/SPMHeaders.h create mode 100644 Datadog/DatadogPrivate/module.modulemap diff --git a/.gitignore b/.gitignore index def066fe7a..614098c887 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ /.build /.swiftpm xcuserdata/ + +# SPM-generated .xcodedproj +Datadog.xcodeproj diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 9c30fc8ec8..fa230ff1f2 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -87,6 +87,8 @@ 61133C6E2423990D00786299 /* DatadogExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133C472423990D00786299 /* DatadogExtensions.swift */; }; 61133C702423993200786299 /* Datadog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61133B82242393DE00786299 /* Datadog.framework */; }; 61133C712423993200786299 /* Datadog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 61133B82242393DE00786299 /* Datadog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 616A62D024349BA700D1BE12 /* ObjcExceptionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 616A626924335D4900D1BE12 /* ObjcExceptionHandler.m */; }; + 616A62D62434A3C500D1BE12 /* ObjcExceptionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 616A626A24335D4900D1BE12 /* ObjcExceptionHandler.h */; }; 9E08587A242519FF001A3583 /* NetworkPathMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E085879242519FF001A3583 /* NetworkPathMonitor.swift */; }; /* End PBXBuildFile section */ @@ -212,6 +214,9 @@ 61133C452423990D00786299 /* SwiftExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftExtensions.swift; sourceTree = ""; }; 61133C462423990D00786299 /* TestsDirectory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestsDirectory.swift; sourceTree = ""; }; 61133C472423990D00786299 /* DatadogExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatadogExtensions.swift; sourceTree = ""; }; + 616A626924335D4900D1BE12 /* ObjcExceptionHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjcExceptionHandler.m; sourceTree = ""; }; + 616A626A24335D4900D1BE12 /* ObjcExceptionHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjcExceptionHandler.h; sourceTree = ""; }; + 616A62DF2434BEF200D1BE12 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 9E085879242519FF001A3583 /* NetworkPathMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPathMonitor.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -247,6 +252,7 @@ children = ( 61133B9C2423979B00786299 /* Datadog */, 61133C082423983800786299 /* DatadogObjc */, + 616A626824335D4900D1BE12 /* DatadogPrivate */, 61133C122423990D00786299 /* DatadogTests */, 61133C07242397F200786299 /* TargetSupport */, 61133B83242393DE00786299 /* Products */, @@ -615,6 +621,16 @@ name = Frameworks; sourceTree = ""; }; + 616A626824335D4900D1BE12 /* DatadogPrivate */ = { + isa = PBXGroup; + children = ( + 616A62DF2434BEF200D1BE12 /* module.modulemap */, + 616A626A24335D4900D1BE12 /* ObjcExceptionHandler.h */, + 616A626924335D4900D1BE12 /* ObjcExceptionHandler.m */, + ); + path = DatadogPrivate; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -623,6 +639,7 @@ buildActionMask = 2147483647; files = ( 61133B93242393DE00786299 /* Datadog.h in Headers */, + 616A62D62434A3C500D1BE12 /* ObjcExceptionHandler.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -795,6 +812,7 @@ 61133BCC2423979B00786299 /* MobileDevice.swift in Sources */, 9E08587A242519FF001A3583 /* NetworkPathMonitor.swift in Sources */, 61133BCA2423979B00786299 /* EncodableValue.swift in Sources */, + 616A62D024349BA700D1BE12 /* ObjcExceptionHandler.m in Sources */, 61133BE62423979B00786299 /* LogSanitizer.swift in Sources */, 61133BDF2423979B00786299 /* SwiftExtensions.swift in Sources */, 61133BEA2423979B00786299 /* LogConsoleOutput.swift in Sources */, @@ -1042,6 +1060,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/DatadogPrivate"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1066,6 +1085,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/DatadogPrivate"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/Datadog/DatadogPrivate/ObjcExceptionHandler.h b/Datadog/DatadogPrivate/ObjcExceptionHandler.h new file mode 100644 index 0000000000..b40f789110 --- /dev/null +++ b/Datadog/DatadogPrivate/ObjcExceptionHandler.h @@ -0,0 +1,18 @@ +/* +* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +* This product includes software developed at Datadog (https://www.datadoghq.com/). +* Copyright 2019-2020 Datadog, Inc. +*/ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ObjcExceptionHandler : NSObject + ++ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error + NS_SWIFT_NAME(rethrowToSwift(tryBlock:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Datadog/DatadogPrivate/ObjcExceptionHandler.m b/Datadog/DatadogPrivate/ObjcExceptionHandler.m new file mode 100644 index 0000000000..fc1b9d789d --- /dev/null +++ b/Datadog/DatadogPrivate/ObjcExceptionHandler.m @@ -0,0 +1,23 @@ +/* +* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +* This product includes software developed at Datadog (https://www.datadoghq.com/). +* Copyright 2019-2020 Datadog, Inc. +*/ + +#import +#import "ObjcExceptionHandler.h" + +@implementation ObjcExceptionHandler + ++ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { + @try { + tryBlock(); + return YES; + } + @catch (NSException *exception) { + *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo]; + return NO; + } +} + +@end diff --git a/Datadog/DatadogPrivate/include/SPMHeaders.h b/Datadog/DatadogPrivate/include/SPMHeaders.h new file mode 100644 index 0000000000..bc3e3ebc33 --- /dev/null +++ b/Datadog/DatadogPrivate/include/SPMHeaders.h @@ -0,0 +1,9 @@ +/* +* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +* This product includes software developed at Datadog (https://www.datadoghq.com/). +* Copyright 2019-2020 Datadog, Inc. +*/ + + +// List of headers for SPM to include for `_Datadog_Private` module. +#import "../ObjcExceptionHandler.h" diff --git a/Datadog/DatadogPrivate/module.modulemap b/Datadog/DatadogPrivate/module.modulemap new file mode 100644 index 0000000000..c3ae441654 --- /dev/null +++ b/Datadog/DatadogPrivate/module.modulemap @@ -0,0 +1,14 @@ +/* + Module Map Language reference: https://clang.llvm.org/docs/Modules.html#module-map-language + */ +module _Datadog_Private { + /* + Each header exported from here must be also exposed to each dependency manager separately: + - For Carthage, add it to "Datadog > Build Phases > Headers > Project". + - For SPM, import it from `include/SPMHeaders.h`. + - Cocoapods `.podspec` is already configured to read this `module.modulemap`. + */ + + header "ObjcExceptionHandler.h" + export * +} diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index 185dca817c..eaff35191d 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -16,7 +16,13 @@ Pod::Spec.new do |s| s.swift_version = '5.1' s.ios.deployment_target = '11.0' - s.source = { :git => 'https://github.com/DataDog/dd-sdk-ios.git', :tag => s.version.to_s } + s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } - s.source_files = "Sources/Datadog/**/*.swift" + s.source_files = "Sources/Datadog/**/*.swift", "Datadog/DatadogPrivate/*.{h,m}" + s.public_header_files = "Datadog/TargetSupport/**/*.h" + s.private_header_files = "Datadog/DatadogPrivate/*.h" + s.preserve_paths = "Datadog/DatadogPrivate/module.modulemap" + s.pod_target_xcconfig = { + "SWIFT_INCLUDE_PATHS" => "$(PODS_ROOT)/DatadogSDK/Datadog/DatadogPrivate/** $(PODS_TARGET_SRCROOT)/DatadogSDK/Datadog/DatadogPrivate/**" + } end diff --git a/Package.swift b/Package.swift index 6abe78fdaf..38502fb642 100644 --- a/Package.swift +++ b/Package.swift @@ -20,10 +20,14 @@ let package = Package( targets: [ .target( name: "Datadog", - dependencies: []), + dependencies: ["_Datadog_Private"]), .target( name: "DatadogObjc", dependencies: ["Datadog"]), + .target( + name: "_Datadog_Private", + path: "Datadog/DatadogPrivate" + ), .testTarget( name: "DatadogTests", dependencies: ["Datadog", "DatadogObjc"]), diff --git a/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj b/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj index e2da86bcdf..4476a93a4e 100644 --- a/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj +++ b/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj @@ -493,6 +493,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/../../Datadog/DatadogPrivate"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Benchmark.app/Benchmark"; @@ -520,6 +521,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/../../Datadog/DatadogPrivate"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Benchmark.app/Benchmark"; From eaead0b830d656209ce502acd56a10d670770d54 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 2 Apr 2020 16:35:16 +0200 Subject: [PATCH 2/4] RUMM-346 Catch `NSExceptions` in `FileWriter` --- Datadog/Datadog.xcodeproj/project.pbxproj | 20 ++++++++++++ Datadog/DatadogPrivate/ObjcExceptionHandler.h | 2 +- Datadog/DatadogPrivate/ObjcExceptionHandler.m | 2 +- .../Datadog/Core/Persistence/FileWriter.swift | 8 ++--- .../Datadog/Core/Persistence/Files/File.swift | 32 +++++++++---------- .../Logs/LogOutputs/LogConsoleOutput.swift | 5 --- Sources/Datadog/Utils/Globals.swift | 15 +++++++++ .../Core/Persistence/FileReaderTests.swift | 8 ++--- .../Core/Persistence/FileWriterTests.swift | 28 +++++++++++++++- .../Core/Persistence/Files/FileTests.swift | 12 +++---- .../Persistence/FilesOrchestratorTests.swift | 10 +++--- .../Datadog/Mocks/DatadogPrivateMocks.swift | 23 +++++++++++++ .../ObjcExceptionHandlerTests.swift | 32 +++++++++++++++++++ 13 files changed, 153 insertions(+), 44 deletions(-) create mode 100644 Sources/Datadog/Utils/Globals.swift create mode 100644 Tests/DatadogTests/Datadog/Mocks/DatadogPrivateMocks.swift create mode 100644 Tests/DatadogTests/DatadogPrivate/ObjcExceptionHandlerTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index fa230ff1f2..2c6316d17f 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -89,6 +89,9 @@ 61133C712423993200786299 /* Datadog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 61133B82242393DE00786299 /* Datadog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 616A62D024349BA700D1BE12 /* ObjcExceptionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 616A626924335D4900D1BE12 /* ObjcExceptionHandler.m */; }; 616A62D62434A3C500D1BE12 /* ObjcExceptionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 616A626A24335D4900D1BE12 /* ObjcExceptionHandler.h */; }; + 61C363802436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3637F2436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift */; }; + 61C3638324361BE200C4D4E6 /* DatadogPrivateMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3638224361BE200C4D4E6 /* DatadogPrivateMocks.swift */; }; + 61C3638524361E9200C4D4E6 /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3638424361E9200C4D4E6 /* Globals.swift */; }; 9E08587A242519FF001A3583 /* NetworkPathMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E085879242519FF001A3583 /* NetworkPathMonitor.swift */; }; /* End PBXBuildFile section */ @@ -217,6 +220,9 @@ 616A626924335D4900D1BE12 /* ObjcExceptionHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjcExceptionHandler.m; sourceTree = ""; }; 616A626A24335D4900D1BE12 /* ObjcExceptionHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjcExceptionHandler.h; sourceTree = ""; }; 616A62DF2434BEF200D1BE12 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 61C3637F2436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjcExceptionHandlerTests.swift; sourceTree = ""; }; + 61C3638224361BE200C4D4E6 /* DatadogPrivateMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogPrivateMocks.swift; sourceTree = ""; }; + 61C3638424361E9200C4D4E6 /* Globals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = ""; }; 9E085879242519FF001A3583 /* NetworkPathMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPathMonitor.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -369,6 +375,7 @@ 61133BB72423979B00786299 /* Utils */ = { isa = PBXGroup; children = ( + 61C3638424361E9200C4D4E6 /* Globals.swift */, 61133BB82423979B00786299 /* InternalLoggers.swift */, 61133BB92423979B00786299 /* CompilationConditions.swift */, 61133BBA2423979B00786299 /* SwiftExtensions.swift */, @@ -461,6 +468,7 @@ children = ( 61133C182423990D00786299 /* Datadog */, 61133C132423990D00786299 /* DatadogObjc */, + 61C3637E2436163400C4D4E6 /* DatadogPrivate */, 61133C422423990D00786299 /* Matchers */, 61133C442423990D00786299 /* Helpers */, ); @@ -501,6 +509,7 @@ 61133C1C2423990D00786299 /* UIKitMocks.swift */, 61133C1D2423990D00786299 /* DatadogObjcMocks.swift */, 61133C1F2423990D00786299 /* DatadogMocks.swift */, + 61C3638224361BE200C4D4E6 /* DatadogPrivateMocks.swift */, 61133C202423990D00786299 /* FoundationMocks.swift */, ); path = Mocks; @@ -631,6 +640,14 @@ path = DatadogPrivate; sourceTree = ""; }; + 61C3637E2436163400C4D4E6 /* DatadogPrivate */ = { + isa = PBXGroup; + children = ( + 61C3637F2436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift */, + ); + path = DatadogPrivate; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -812,6 +829,7 @@ 61133BCC2423979B00786299 /* MobileDevice.swift in Sources */, 9E08587A242519FF001A3583 /* NetworkPathMonitor.swift in Sources */, 61133BCA2423979B00786299 /* EncodableValue.swift in Sources */, + 61C3638524361E9200C4D4E6 /* Globals.swift in Sources */, 616A62D024349BA700D1BE12 /* ObjcExceptionHandler.m in Sources */, 61133BE62423979B00786299 /* LogSanitizer.swift in Sources */, 61133BDF2423979B00786299 /* SwiftExtensions.swift in Sources */, @@ -854,9 +872,11 @@ 61133C622423990D00786299 /* InternalLoggersTests.swift in Sources */, 61133C582423990D00786299 /* FileWriterTests.swift in Sources */, 61133C672423990D00786299 /* LogConsoleOutputTests.swift in Sources */, + 61C3638324361BE200C4D4E6 /* DatadogPrivateMocks.swift in Sources */, 61133C4C2423990D00786299 /* LogsMocks.swift in Sources */, 61133C542423990D00786299 /* NetworkConnectionInfoProviderTests.swift in Sources */, 61133C4A2423990D00786299 /* DDConfigurationTests.swift in Sources */, + 61C363802436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift in Sources */, 61133C602423990D00786299 /* HTTPHeadersTests.swift in Sources */, 61133C632423990D00786299 /* DatadogConfigurationTests.swift in Sources */, 61133C572423990D00786299 /* FileReaderTests.swift in Sources */, diff --git a/Datadog/DatadogPrivate/ObjcExceptionHandler.h b/Datadog/DatadogPrivate/ObjcExceptionHandler.h index b40f789110..bf5a9e3e75 100644 --- a/Datadog/DatadogPrivate/ObjcExceptionHandler.h +++ b/Datadog/DatadogPrivate/ObjcExceptionHandler.h @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ObjcExceptionHandler : NSObject -+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error +- (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error NS_SWIFT_NAME(rethrowToSwift(tryBlock:)); @end diff --git a/Datadog/DatadogPrivate/ObjcExceptionHandler.m b/Datadog/DatadogPrivate/ObjcExceptionHandler.m index fc1b9d789d..61a0c28ea7 100644 --- a/Datadog/DatadogPrivate/ObjcExceptionHandler.m +++ b/Datadog/DatadogPrivate/ObjcExceptionHandler.m @@ -9,7 +9,7 @@ @implementation ObjcExceptionHandler -+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { +- (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { @try { tryBlock(); return YES; diff --git a/Sources/Datadog/Core/Persistence/FileWriter.swift b/Sources/Datadog/Core/Persistence/FileWriter.swift index 03e3c24cc6..e20f11c2ea 100644 --- a/Sources/Datadog/Core/Persistence/FileWriter.swift +++ b/Sources/Datadog/Core/Persistence/FileWriter.swift @@ -51,13 +51,13 @@ internal final class FileWriter { let file = try orchestrator.getWritableFile(writeSize: UInt64(data.count)) if try file.size() == 0 { - try file.append { write in - write(data) + try file.append { (write: (Data) throws -> Void) in + try write(data) } } else { try file.append { write in - write(commaSeparatorData) - write(data) + try write(commaSeparatorData) + try write(data) } } } catch { diff --git a/Sources/Datadog/Core/Persistence/Files/File.swift b/Sources/Datadog/Core/Persistence/Files/File.swift index 3fcf2cc24b..70e05b9551 100644 --- a/Sources/Datadog/Core/Persistence/Files/File.swift +++ b/Sources/Datadog/Core/Persistence/Files/File.swift @@ -5,6 +5,7 @@ */ import Foundation +import _Datadog_Private /// Provides convenient interface for reading metadata and appending data to the file. internal protocol WritableFile { @@ -15,7 +16,7 @@ internal protocol WritableFile { func size() throws -> UInt64 /// Synchronously appends given data at the end of this file. - func append(transaction: ((Data) -> Void) -> Void) throws + func append(transaction: ((Data) throws -> Void) throws -> Void) throws } /// Provides convenient interface for reading contents and metadata of the file. @@ -42,27 +43,24 @@ internal struct File: WritableFile, ReadableFile { } /// Appends given data at the end of this file. - func append(transaction: ((Data) -> Void) -> Void) throws { + func append(transaction: ((Data) throws -> Void) throws -> Void) throws { let fileHandle = try FileHandle(forWritingTo: url) defer { fileHandle.closeFile() } - fileHandle.seekToEndOfFile() + + try objcExceptionHandler.rethrowToSwift { + fileHandle.seekToEndOfFile() + } // Writes given data at the end of the file. - func appendData(_ data: Data) { - /* - Apple documentation https://developer.apple.com/documentation/foundation/filehandle/1410936-write says - that `fileHandle.write()` raises an exception if no free space is left on the file system, - or if any other writing error occurs. Those are unchecked exceptions impossible to handle in Swift. - - It was already escalated to Apple in Swift open source project discussion: - https://forums.swift.org/t/pitch-replacement-for-filehandle/5177 - - Until better replacement is provided by Apple, the SDK sticks to this API. To mitigate the risk of - crashing client applications, other precautions are implemented carefuly. - */ - fileHandle.write(data) + func appendData(_ data: Data) throws { + try objcExceptionHandler.rethrowToSwift { + fileHandle.write(data) + } + } + + try transaction { chunkOfData in + try appendData(chunkOfData) } - transaction(appendData) } func read() throws -> Data { diff --git a/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift b/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift index f5a3802022..b54fefb1ec 100644 --- a/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift +++ b/Sources/Datadog/Logs/LogOutputs/LogConsoleOutput.swift @@ -11,11 +11,6 @@ internal protocol ConsoleLogFormatter { func format(log: Log) -> String } -/// Function printing `String` content to console. -internal var consolePrint: (String) -> Void = { content in - print(content) -} - /// `LogOutput` which prints logs to console. internal struct LogConsoleOutput: LogOutput { /// Time formatter used for `.short` output format. diff --git a/Sources/Datadog/Utils/Globals.swift b/Sources/Datadog/Utils/Globals.swift new file mode 100644 index 0000000000..a9f1e68bbd --- /dev/null +++ b/Sources/Datadog/Utils/Globals.swift @@ -0,0 +1,15 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import _Datadog_Private + +/// Function printing `String` content to console. +internal var consolePrint: (String) -> Void = { content in + print(content) +} + +/// Exception handler rethrowing `NSExceptions` to Swift `NSError`. +internal var objcExceptionHandler = ObjcExceptionHandler() diff --git a/Tests/DatadogTests/Datadog/Core/Persistence/FileReaderTests.swift b/Tests/DatadogTests/Datadog/Core/Persistence/FileReaderTests.swift index d5c859d60c..ab3ea0a9a3 100644 --- a/Tests/DatadogTests/Datadog/Core/Persistence/FileReaderTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Persistence/FileReaderTests.swift @@ -27,7 +27,7 @@ class FileReaderTests: XCTestCase { ) _ = try temporaryDirectory .createFile(named: .mockAnyFileName()) - .append { write in write("ABCD".utf8Data) } + .append { write in try write("ABCD".utf8Data) } let batch = reader.readNextBatch() @@ -46,13 +46,13 @@ class FileReaderTests: XCTestCase { queue: queue ) let file1 = try temporaryDirectory.createFile(named: dateProvider.currentDate().toFileName) - try file1.append { write in write("1".utf8Data) } + try file1.append { write in try write("1".utf8Data) } let file2 = try temporaryDirectory.createFile(named: dateProvider.currentDate().toFileName) - try file2.append { write in write("2".utf8Data) } + try file2.append { write in try write("2".utf8Data) } let file3 = try temporaryDirectory.createFile(named: dateProvider.currentDate().toFileName) - try file3.append { write in write("3".utf8Data) } + try file3.append { write in try write("3".utf8Data) } var batch: Batch batch = try reader.readNextBatch().unwrapOrThrow() diff --git a/Tests/DatadogTests/Datadog/Core/Persistence/FileWriterTests.swift b/Tests/DatadogTests/Datadog/Core/Persistence/FileWriterTests.swift index 7db124d515..a2d03f8f67 100644 --- a/Tests/DatadogTests/Datadog/Core/Persistence/FileWriterTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Persistence/FileWriterTests.swift @@ -63,7 +63,7 @@ class FileWriterTests: XCTestCase { XCTAssertEqual(try temporaryDirectory.files()[0].read(), #"{"key1":"value1"}"#.utf8Data) // same content as before } - func testGivenErrorVerbosity_whenLogCannotBeEncoded_itPrintsError() throws { + func testGivenErrorVerbosity_whenDataCannotBeEncoded_itPrintsError() throws { let expectation = self.expectation(description: "write completed") let previousUserLogger = userLogger defer { userLogger = previousUserLogger } @@ -86,6 +86,32 @@ class FileWriterTests: XCTestCase { XCTAssertEqual(output.recordedLog?.message, "🔥 Failed to write log: failed to encode `FailingEncodable`.") } + func testGivenErrorVerbosity_whenIOExceptionIsThrown_itPrintsError() throws { + let expectation = self.expectation(description: "write completed") + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + let previousObjcExceptionHandler = objcExceptionHandler + defer { objcExceptionHandler = previousObjcExceptionHandler } + + let output = LogOutputMock() + userLogger = Logger(logOutput: output, identifier: "sdk-user") + objcExceptionHandler = ObjcExceptionHandlerMock(throwingError: ErrorMock("I/O exception")) + + let writer = FileWriter( + orchestrator: .mockWriteToSingleFile(in: temporaryDirectory), + queue: queue, + maxWriteSize: .max + ) + + writer.write(value: ["whatever"]) + + waitForWritesCompletion(on: queue, thenFulfill: expectation) + waitForExpectations(timeout: 1, handler: nil) + + XCTAssertEqual(output.recordedLog?.level, .error) + XCTAssertEqual(output.recordedLog?.message, "🔥 Failed to write log: I/O exception") + } + private func waitForWritesCompletion(on queue: DispatchQueue, thenFulfill expectation: XCTestExpectation) { queue.async { expectation.fulfill() } } diff --git a/Tests/DatadogTests/Datadog/Core/Persistence/Files/FileTests.swift b/Tests/DatadogTests/Datadog/Core/Persistence/Files/FileTests.swift index 9d6d31f049..9f50b0a258 100644 --- a/Tests/DatadogTests/Datadog/Core/Persistence/Files/FileTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Persistence/Files/FileTests.swift @@ -24,7 +24,7 @@ class FileTests: XCTestCase { let file = try temporaryDirectory.createFile(named: "file") try file.append { write in - write(Data([0x41, 0x41, 0x41, 0x41, 0x41])) // 5 bytes + try write(Data([0x41, 0x41, 0x41, 0x41, 0x41])) // 5 bytes } XCTAssertEqual( @@ -33,8 +33,8 @@ class FileTests: XCTestCase { ) try file.append { write in - write(Data([0x42, 0x42, 0x42, 0x42, 0x42])) // 5 bytes - write(Data([0x41, 0x41, 0x41, 0x41, 0x41])) // 5 bytes + try write(Data([0x42, 0x42, 0x42, 0x42, 0x42])) // 5 bytes + try write(Data([0x41, 0x41, 0x41, 0x41, 0x41])) // 5 bytes } XCTAssertEqual( @@ -51,7 +51,7 @@ class FileTests: XCTestCase { func testItReadsDataFromFile() throws { let file = try temporaryDirectory.createFile(named: "file") - try file.append { write in write("Hello 👋".utf8Data) } + try file.append { write in try write("Hello 👋".utf8Data) } XCTAssertEqual(try file.read().utf8String, "Hello 👋") } @@ -68,10 +68,10 @@ class FileTests: XCTestCase { func testItReturnsFileSize() throws { let file = try temporaryDirectory.createFile(named: "file") - try file.append { write in write(.mock(ofSize: 5)) } + try file.append { write in try write(.mock(ofSize: 5)) } XCTAssertEqual(try file.size(), 5) - try file.append { write in write(.mock(ofSize: 10)) } + try file.append { write in try write(.mock(ofSize: 10)) } XCTAssertEqual(try file.size(), 15) } } diff --git a/Tests/DatadogTests/Datadog/Core/Persistence/FilesOrchestratorTests.swift b/Tests/DatadogTests/Datadog/Core/Persistence/FilesOrchestratorTests.swift index 4dc6f719fb..988621933c 100644 --- a/Tests/DatadogTests/Datadog/Core/Persistence/FilesOrchestratorTests.swift +++ b/Tests/DatadogTests/Datadog/Core/Persistence/FilesOrchestratorTests.swift @@ -73,7 +73,7 @@ class FilesOrchestratorTests: XCTestCase { let chunkedData: [Data] = .mockChunksOf(totalSize: defaultWriteConditions.maxFileSize) let file1 = try orchestrator.getWritableFile(writeSize: defaultWriteConditions.maxFileSize) - try file1.append { write in chunkedData.forEach { chunk in write(chunk) } } + try file1.append { write in try chunkedData.forEach { chunk in try write(chunk) } } let file2 = try orchestrator.getWritableFile(writeSize: 1) XCTAssertNotEqual(file1.name, file2.name) @@ -132,15 +132,15 @@ class FilesOrchestratorTests: XCTestCase { // write 1MB to first file (1MB of directory size in total) let file1 = try orchestrator.getWritableFile(writeSize: oneMB) - try file1.append { write in write(.mock(ofSize: oneMB)) } + try file1.append { write in try write(.mock(ofSize: oneMB)) } // write 1MB to second file (2MB of directory size in total) let file2 = try orchestrator.getWritableFile(writeSize: oneMB) - try file2.append { write in write(.mock(ofSize: oneMB)) } + try file2.append { write in try write(.mock(ofSize: oneMB)) } // write 1MB to third file (3MB of directory size in total) let file3 = try orchestrator.getWritableFile(writeSize: oneMB + 1) // +1 byte to exceed the limit - try file3.append { write in write(.mock(ofSize: oneMB + 1)) } + try file3.append { write in try write(.mock(ofSize: oneMB + 1)) } XCTAssertEqual(try temporaryDirectory.files().count, 3) @@ -149,7 +149,7 @@ class FilesOrchestratorTests: XCTestCase { let file4 = try orchestrator.getWritableFile(writeSize: oneMB) XCTAssertEqual(try temporaryDirectory.files().count, 3) XCTAssertNil(try? temporaryDirectory.file(named: file1.name)) - try file4.append { write in write(.mock(ofSize: oneMB + 1)) } + try file4.append { write in try write(.mock(ofSize: oneMB + 1)) } _ = try orchestrator.getWritableFile(writeSize: oneMB) XCTAssertEqual(try temporaryDirectory.files().count, 3) diff --git a/Tests/DatadogTests/Datadog/Mocks/DatadogPrivateMocks.swift b/Tests/DatadogTests/Datadog/Mocks/DatadogPrivateMocks.swift new file mode 100644 index 0000000000..6480151157 --- /dev/null +++ b/Tests/DatadogTests/Datadog/Mocks/DatadogPrivateMocks.swift @@ -0,0 +1,23 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import _Datadog_Private + +/* + A collection of mocks for `_Datadog_Private` module. + */ + +class ObjcExceptionHandlerMock: ObjcExceptionHandler { + let error: Error + + init(throwingError: Error) { + self.error = throwingError + } + + override func rethrowToSwift(tryBlock: @escaping () -> Void) throws { + throw error + } +} diff --git a/Tests/DatadogTests/DatadogPrivate/ObjcExceptionHandlerTests.swift b/Tests/DatadogTests/DatadogPrivate/ObjcExceptionHandlerTests.swift new file mode 100644 index 0000000000..dcbb1cc88b --- /dev/null +++ b/Tests/DatadogTests/DatadogPrivate/ObjcExceptionHandlerTests.swift @@ -0,0 +1,32 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +import _Datadog_Private + +class ObjcExceptionHandlerTests: XCTestCase { + private let exceptionHandler = ObjcExceptionHandler() + + func testGivenNonThrowingCode_itDoesNotThrow() throws { + var counter = 0 + try exceptionHandler.rethrowToSwift { counter += 1 } + XCTAssertEqual(counter, 1) + } + + func testGivenThrowingCode_itThrowsNSErrorToSwift() { + let nsException = NSException( + name: NSExceptionName(rawValue: "name"), + reason: "reason", + userInfo: ["user-info": "some"] + ) + + XCTAssertThrowsError(try exceptionHandler.rethrowToSwift { nsException.raise() }) { error in + XCTAssertEqual((error as NSError).domain, "name") + XCTAssertEqual((error as NSError).code, 0) + XCTAssertEqual((error as NSError).userInfo as? [String: String], ["user-info": "some"]) + } + } +} From f14b60a0bd11f22c9555f1627e40f604e614297b Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 2 Apr 2020 20:35:16 +0200 Subject: [PATCH 3/4] RUMM-346 Do fix for running Benchmarks on device --- .../Benchmark/Benchmark.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj b/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj index 4476a93a4e..2e39a28e60 100644 --- a/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj +++ b/instrumented-tests/Benchmark/Benchmark.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 61999EC924071A760094866C /* LoggingBenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61999EC824071A760094866C /* LoggingBenchmarkTests.swift */; }; 61AEBF062423D2AE00A8CB51 /* Datadog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61AEBF052423D2AE00A8CB51 /* Datadog.framework */; }; 61AEBF072423D2AE00A8CB51 /* Datadog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 61AEBF052423D2AE00A8CB51 /* Datadog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 61C363CB2436674B00C4D4E6 /* _Datadog_Private.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61C363C22436650C00C4D4E6 /* _Datadog_Private.framework */; }; + 61C363CC2436674B00C4D4E6 /* _Datadog_Private.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 61C363C22436650C00C4D4E6 /* _Datadog_Private.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -37,6 +39,7 @@ dstSubfolderSpec = 10; files = ( 61AEBF072423D2AE00A8CB51 /* Datadog.framework in Embed Frameworks */, + 61C363CC2436674B00C4D4E6 /* _Datadog_Private.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -57,6 +60,7 @@ 613D7F5C240938560013B7DF /* LoggingIOBenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingIOBenchmarkTests.swift; sourceTree = ""; }; 61999EC824071A760094866C /* LoggingBenchmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingBenchmarkTests.swift; sourceTree = ""; }; 61AEBF052423D2AE00A8CB51 /* Datadog.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Datadog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 61C363C22436650C00C4D4E6 /* _Datadog_Private.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = _Datadog_Private.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,6 +69,7 @@ buildActionMask = 2147483647; files = ( 61AEBF062423D2AE00A8CB51 /* Datadog.framework in Frameworks */, + 61C363CB2436674B00C4D4E6 /* _Datadog_Private.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -141,6 +146,7 @@ 61800F732405411400420E0D /* Frameworks */ = { isa = PBXGroup; children = ( + 61C363C22436650C00C4D4E6 /* _Datadog_Private.framework */, 61AEBF052423D2AE00A8CB51 /* Datadog.framework */, ); name = Frameworks; From cc00074fd59a73efcfa78652db8be969c9244cfe Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 3 Apr 2020 18:03:03 +0200 Subject: [PATCH 4/4] RUMM-346 DatadogPrivate module is hidden from public DDPrivate module is created to use ObjC from Swift It is not intended for public use We used private.modulemap to hide it from public --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ Datadog/DatadogPrivate/module.modulemap | 13 ------------- Datadog/DatadogPrivate/module.private.modulemap | 14 ++++++++++++++ DatadogSDK.podspec | 6 ++---- 4 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 Datadog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Datadog/DatadogPrivate/module.private.modulemap diff --git a/Datadog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Datadog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/Datadog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Datadog/DatadogPrivate/module.modulemap b/Datadog/DatadogPrivate/module.modulemap index c3ae441654..8b13789179 100644 --- a/Datadog/DatadogPrivate/module.modulemap +++ b/Datadog/DatadogPrivate/module.modulemap @@ -1,14 +1 @@ -/* - Module Map Language reference: https://clang.llvm.org/docs/Modules.html#module-map-language - */ -module _Datadog_Private { - /* - Each header exported from here must be also exposed to each dependency manager separately: - - For Carthage, add it to "Datadog > Build Phases > Headers > Project". - - For SPM, import it from `include/SPMHeaders.h`. - - Cocoapods `.podspec` is already configured to read this `module.modulemap`. - */ - header "ObjcExceptionHandler.h" - export * -} diff --git a/Datadog/DatadogPrivate/module.private.modulemap b/Datadog/DatadogPrivate/module.private.modulemap new file mode 100644 index 0000000000..c3ae441654 --- /dev/null +++ b/Datadog/DatadogPrivate/module.private.modulemap @@ -0,0 +1,14 @@ +/* + Module Map Language reference: https://clang.llvm.org/docs/Modules.html#module-map-language + */ +module _Datadog_Private { + /* + Each header exported from here must be also exposed to each dependency manager separately: + - For Carthage, add it to "Datadog > Build Phases > Headers > Project". + - For SPM, import it from `include/SPMHeaders.h`. + - Cocoapods `.podspec` is already configured to read this `module.modulemap`. + */ + + header "ObjcExceptionHandler.h" + export * +} diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index eaff35191d..92a82db894 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -18,10 +18,8 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } - s.source_files = "Sources/Datadog/**/*.swift", "Datadog/DatadogPrivate/*.{h,m}" - s.public_header_files = "Datadog/TargetSupport/**/*.h" - s.private_header_files = "Datadog/DatadogPrivate/*.h" - s.preserve_paths = "Datadog/DatadogPrivate/module.modulemap" + s.source_files = "Sources/Datadog/**/*.swift", "Datadog/DatadogPrivate/*.m" + s.preserve_paths = "Datadog/DatadogPrivate/*.{modulemap,h}" s.pod_target_xcconfig = { "SWIFT_INCLUDE_PATHS" => "$(PODS_ROOT)/DatadogSDK/Datadog/DatadogPrivate/** $(PODS_TARGET_SRCROOT)/DatadogSDK/Datadog/DatadogPrivate/**" }