diff --git a/.circleci/config.yml b/.circleci/config.yml index ecede2a..fe54b81 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,7 +38,7 @@ jobs: executors: xcode-12: macos: - xcode: "12.1.0" + xcode: "12.4.0" environment: LC_ALL: en_US.UTF-8 LANG: en_US.UTF-8 diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index 17ba111..c970432 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 397E02E6382D99530A20018AF2BAF3FC /* Pods-Swiftilities_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 166EF195010DBED3D5BF735C68ECF01A /* Pods-Swiftilities_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F0E7B73527F867A4E81F5D6B4781469 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6048260CE2D8CBE1A27F5AFF2E9F35A3 /* AppInfo.swift */; }; 486F5FA2696987EC834BAB29382DADF7 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18130A29D09588D7718637104EE824A /* AboutView.swift */; }; + 520674CF26604BC600EAA222 /* LocalLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520674CE26604BC600EAA222 /* LocalLogHandler.swift */; }; 5321C27A1F15734C0E017DE896907A71 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2C44C9970BCFBB1F0BEF505CEB7549 /* Protocols.swift */; }; 535F0D41A7DB390EC666C9BD99B28157 /* DefaultBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBDD8FCD2D689C7C2E6006A419BE1D6 /* DefaultBehaviors.swift */; }; 560CED4671AF248B7729D0012942E8B2 /* UIViewController+Lifecycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1148E48D3AB07F75F4F5AD40EA1D8C38 /* UIViewController+Lifecycle.swift */; }; @@ -86,7 +87,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 03B938D6A87672D349267DF11FC04A7B /* Pods_Swiftilities_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_Swiftilities_Tests.framework; path = "Pods-Swiftilities_Tests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03B938D6A87672D349267DF11FC04A7B /* Pods_Swiftilities_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Swiftilities_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E287AF2E68AC98B8E1B5709BC6B3F4E /* Swiftilities.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Swiftilities.modulemap; sourceTree = ""; }; 1148E48D3AB07F75F4F5AD40EA1D8C38 /* UIViewController+Lifecycle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIViewController+Lifecycle.swift"; sourceTree = ""; }; 166EF195010DBED3D5BF735C68ECF01A /* Pods-Swiftilities_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-Swiftilities_Example-umbrella.h"; sourceTree = ""; }; @@ -108,6 +109,7 @@ 43BA454FE9F241D3FB678B4EC764B713 /* FormattedTextField.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FormattedTextField.swift; path = Pod/Classes/FormattedTextField/FormattedTextField.swift; sourceTree = ""; }; 44F919EA6F1F48F2E2431ADE8D494C61 /* Pods-Swiftilities_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-Swiftilities_Example.modulemap"; sourceTree = ""; }; 4B13DA625539A2639C7EAA0B66CF3A49 /* Log.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Log.swift; path = Pod/Classes/Logging/Log.swift; sourceTree = ""; }; + 520674CE26604BC600EAA222 /* LocalLogHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LocalLogHandler.swift; path = Pod/Classes/Logging/LocalLogHandler.swift; sourceTree = ""; }; 5252F5FAAC0B863DBD1BC0A1EEADB1A2 /* Pods-Swiftilities_Tests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Swiftilities_Tests-Info.plist"; sourceTree = ""; }; 57641AB3A9035D9A66CA6C68B4529D55 /* UIView+KeyboardLayoutGuide.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIView+KeyboardLayoutGuide.swift"; path = "Pod/Classes/Keyboard/UIView+KeyboardLayoutGuide.swift"; sourceTree = ""; }; 5B23D7B1D60BD17AC95D042946019871 /* Swiftilities-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Swiftilities-dummy.m"; sourceTree = ""; }; @@ -136,14 +138,14 @@ 99DE6859670DB2EFE2200BA0C9A3A411 /* TableSection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableSection.swift; path = Pod/Classes/TableViewHelpers/TableSection.swift; sourceTree = ""; }; 9A31420FBA08D9036C8C13B6FD8508D3 /* AcknowledgementViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AcknowledgementViewController.swift; path = Pod/Classes/Acknowledgements/AcknowledgementViewController.swift; sourceTree = ""; }; 9AD4FDADFF2BC7B6584002194925D2AE /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 9DED24D4064918095C65AE80CDF3C060 /* ViewControllerLifecycleBehavior.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViewControllerLifecycleBehavior.swift; sourceTree = ""; }; A3EE6E3A4CF8A4E726343A91EBAB8B92 /* Compatibility.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Compatibility.swift; path = Pod/Classes/Compatibility/Compatibility.swift; sourceTree = ""; }; AF95934FCF61F96C022A92752FDCBE73 /* Pods-Swiftilities_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Swiftilities_Example.debug.xcconfig"; sourceTree = ""; }; BCD06A3F5B74A50EDE27CAE35D2115D7 /* UIWindow+RootViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIWindow+RootViewController.swift"; path = "Pod/Classes/RootViewController/UIWindow+RootViewController.swift"; sourceTree = ""; }; BE723CD9F77C0B7154099265E076C1A0 /* Pods-Swiftilities_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Swiftilities_Example-frameworks.sh"; sourceTree = ""; }; - C6438C26C37ECC06B845B77FF2BDBC94 /* Swiftilities.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = Swiftilities.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - CB2600C9FF687EC628877EDF6E9DE382 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = ""; }; + C6438C26C37ECC06B845B77FF2BDBC94 /* Swiftilities.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; path = Swiftilities.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + CB2600C9FF687EC628877EDF6E9DE382 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; CB2C44C9970BCFBB1F0BEF505CEB7549 /* Protocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; CFBDD8FCD2D689C7C2E6006A419BE1D6 /* DefaultBehaviors.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DefaultBehaviors.swift; sourceTree = ""; }; D12894800697D66E450685A1DCC79CEF /* CGFloat+DeviceSize.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "CGFloat+DeviceSize.swift"; path = "Pod/Classes/DeviceSize/CGFloat+DeviceSize.swift"; sourceTree = ""; }; @@ -157,14 +159,14 @@ E19AB73216CA6618389C1EAAE58C8D84 /* Pods-Swiftilities_Tests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Swiftilities_Tests-acknowledgements.markdown"; sourceTree = ""; }; E424F656B7668B597E1C7A5FA23131E8 /* PlaceholderTextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PlaceholderTextView.swift; sourceTree = ""; }; E7D8F97E26DE2941D9A3D59A1CDCE815 /* DeviceSize.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DeviceSize.swift; path = Pod/Classes/DeviceSize/DeviceSize.swift; sourceTree = ""; }; - EA9CC1B6BE8F47F797E6316F2BA2ADBF /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; + EA9CC1B6BE8F47F797E6316F2BA2ADBF /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; EB67A5286147499BC789AE69584BDF58 /* FeedbackPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FeedbackPresenter.swift; path = Pod/Classes/AboutView/FeedbackPresenter.swift; sourceTree = ""; }; EB80E6430D4591128E15151C76AB576F /* CubicBezier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CubicBezier.swift; path = Pod/Classes/Math/CubicBezier.swift; sourceTree = ""; }; EC71EBACE982419221CB0062A4BE188B /* Swiftilities-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Swiftilities-umbrella.h"; sourceTree = ""; }; ECC72D8BE4A9168DB84B8C192F2518DC /* UIImage+Tinting.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIImage+Tinting.swift"; path = "Pod/Classes/ImageHelpers/UIImage+Tinting.swift"; sourceTree = ""; }; F18130A29D09588D7718637104EE824A /* AboutView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AboutView.swift; path = Pod/Classes/AboutView/AboutView.swift; sourceTree = ""; }; - F2319378E3E463116760E8513D01EC70 /* Pods_Swiftilities_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_Swiftilities_Example.framework; path = "Pods-Swiftilities_Example.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; - F7696B90A3B3D1C34C7E852254DD74F1 /* Swiftilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Swiftilities.framework; path = Swiftilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F2319378E3E463116760E8513D01EC70 /* Pods_Swiftilities_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Swiftilities_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F7696B90A3B3D1C34C7E852254DD74F1 /* Swiftilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Swiftilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F874DF1B089E2D942A8D9D7F2653B2B1 /* BetterButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BetterButton.swift; path = Pod/Classes/BetterButton/BetterButton.swift; sourceTree = ""; }; F97FF072F4772478C004504FA5DAFD8C /* Pods-Swiftilities_Tests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Swiftilities_Tests-dummy.m"; sourceTree = ""; }; FD0743048B36A7DDA4C6453BFFB3F8EB /* IndexPath+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IndexPath+Extensions.swift"; path = "Pod/Classes/TableViewHelpers/IndexPath+Extensions.swift"; sourceTree = ""; }; @@ -393,7 +395,6 @@ children = ( 8D5B54EA2C071D74497ACDCA8E37611C /* NavBarHairlineFadeBehavior.swift */, ); - name = "Nav-Bar-Hairline-Fade"; path = "Nav-Bar-Hairline-Fade"; sourceTree = ""; }; @@ -518,7 +519,6 @@ children = ( 3A5D4679456C601B638441D354352AB5 /* NavTitleTransitionBehavior.swift */, ); - name = "Nav-Bar-Title-Transition"; path = "Nav-Bar-Title-Transition"; sourceTree = ""; }; @@ -542,6 +542,7 @@ isa = PBXGroup; children = ( 4B13DA625539A2639C7EAA0B66CF3A49 /* Log.swift */, + 520674CE26604BC600EAA222 /* LocalLogHandler.swift */, ); name = Logging; sourceTree = ""; @@ -807,6 +808,7 @@ 85EFC41D452763AF84CB440990493133 /* Keyboard.swift in Sources */, 6B14B40E8CF32FBD3D13115C4EEF7621 /* LicenseFormatter.swift in Sources */, 20151C0F2B6935BF46BD24A8244487A4 /* LifecycleBehaviorViewController.swift in Sources */, + 520674CF26604BC600EAA222 /* LocalLogHandler.swift in Sources */, 36FF01D33AF85D362DB4435E3E7E833D /* Log.swift in Sources */, 182E0585D1CE3BE8288783A045417336 /* NavBarHairlineFadeBehavior.swift in Sources */, 23B5DFBF66D57BAA1104CF40A2D270EF /* NavTitleTransitionBehavior.swift in Sources */, diff --git a/Example/Swiftilities.xcodeproj/project.pbxproj b/Example/Swiftilities.xcodeproj/project.pbxproj index bf9bcc2..5aed9d1 100644 --- a/Example/Swiftilities.xcodeproj/project.pbxproj +++ b/Example/Swiftilities.xcodeproj/project.pbxproj @@ -12,6 +12,15 @@ 0050C1C61EDDA02700665F74 /* LifecycleBehaviorsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0050C1C51EDDA02700665F74 /* LifecycleBehaviorsListViewController.swift */; }; 04CD173B1AB7C4F6286F8FA1 /* Pods_Swiftilities_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 849AC9425E95C2FF6F694A7F /* Pods_Swiftilities_Example.framework */; }; 2340A5BD1E8CC67A00572417 /* TestModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 2340A5BA1E8CC3B500572417 /* TestModel.xcdatamodeld */; }; + 521859AF265EF84E00C819D3 /* ValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD74F57D1E2FC4D3005E59FF /* ValidatorTests.swift */; }; + 521859B0265EF84E00C819D3 /* LicenseFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF471521DDFB20100D4D5A2 /* LicenseFormatterTests.swift */; }; + 521859B1265EF84E00C819D3 /* TestModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 2340A5BA1E8CC3B500572417 /* TestModel.xcdatamodeld */; }; + 521859B2265EF84E00C819D3 /* RootViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72E281A61E5DDE59000D0C9D /* RootViewControllerTests.swift */; }; + 521859B3265EF84E00C819D3 /* TableViewHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCBBA9C1EA7D95D00610F47 /* TableViewHelperTests.swift */; }; + 521859B4265EF84E00C819D3 /* LogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF3429D1EA02B5100E6328B /* LogTests.swift */; }; + 521859B5265EF84E00C819D3 /* MathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD32DE731CEA1EC600FC0C7E /* MathTests.swift */; }; + 521859B6265EF84E00C819D3 /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D98569CA23E8C54D007EF367 /* CoreDataStackTests.swift */; }; + 521859B8265EF84E00C819D3 /* Pods_Swiftilities_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F294762325B4901AA48D27B1 /* Pods_Swiftilities_Tests.framework */; }; 722099011E5F46EE0098229D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 722098FF1E5F46EE0098229D /* Main.storyboard */; }; 722099061E5F470D0098229D /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 722099031E5F470D0098229D /* LaunchScreen.xib */; }; 722099071E5F470D0098229D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 722099051E5F470D0098229D /* Images.xcassets */; }; @@ -37,6 +46,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 521859AC265EF84E00C819D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = Swiftilities_Example; + }; D909A58020DAE3E7007BCA07 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 607FACC81AFB9204008FA782 /* Project object */; @@ -52,6 +68,7 @@ 0050C1C51EDDA02700665F74 /* LifecycleBehaviorsListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LifecycleBehaviorsListViewController.swift; sourceTree = ""; }; 2340A5BB1E8CC3B500572417 /* TestModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TestModel.xcdatamodel; sourceTree = ""; }; 3ED95507725D28046957F181 /* Pods-Swiftilities_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Swiftilities_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Swiftilities_Tests/Pods-Swiftilities_Tests.debug.xcconfig"; sourceTree = ""; }; + 521859BE265EF84E00C819D3 /* Swiftilities_Tests_Latest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Swiftilities_Tests_Latest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5524B820B7943401CC253A04 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 563BE12C99CEF6CB9AB591A4 /* Swiftilities.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Swiftilities.podspec; path = ../Swiftilities.podspec; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* Swiftilities_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Swiftilities_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -88,6 +105,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 521859B7265EF84E00C819D3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 521859B8265EF84E00C819D3 /* Pods_Swiftilities_Tests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 607FACCD1AFB9204008FA782 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -144,6 +169,7 @@ children = ( 607FACD01AFB9204008FA782 /* Swiftilities_Example.app */, 607FACE51AFB9204008FA782 /* Swiftilities_Tests.xctest */, + 521859BE265EF84E00C819D3 /* Swiftilities_Tests_Latest.xctest */, ); name = Products; sourceTree = ""; @@ -324,6 +350,26 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 521859AA265EF84E00C819D3 /* Swiftilities_Tests_Latest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 521859BB265EF84E00C819D3 /* Build configuration list for PBXNativeTarget "Swiftilities_Tests_Latest" */; + buildPhases = ( + 521859AD265EF84E00C819D3 /* [CP] Check Pods Manifest.lock */, + 521859AE265EF84E00C819D3 /* Sources */, + 521859B7265EF84E00C819D3 /* Frameworks */, + 521859B9265EF84E00C819D3 /* Resources */, + 521859BA265EF84E00C819D3 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 521859AB265EF84E00C819D3 /* PBXTargetDependency */, + ); + name = Swiftilities_Tests_Latest; + productName = Tests; + productReference = 521859BE265EF84E00C819D3 /* Swiftilities_Tests_Latest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 607FACCF1AFB9204008FA782 /* Swiftilities_Example */ = { isa = PBXNativeTarget; buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "Swiftilities_Example" */; @@ -402,11 +448,19 @@ targets = ( 607FACCF1AFB9204008FA782 /* Swiftilities_Example */, 607FACE41AFB9204008FA782 /* Swiftilities_Tests */, + 521859AA265EF84E00C819D3 /* Swiftilities_Tests_Latest */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 521859B9265EF84E00C819D3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 607FACCE1AFB9204008FA782 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -445,6 +499,41 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Swiftilities_Example/Pods-Swiftilities_Example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 521859AD265EF84E00C819D3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Swiftilities_Tests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 521859BA265EF84E00C819D3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Swiftilities_Tests/Pods-Swiftilities_Tests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Swiftilities_Tests/Pods-Swiftilities_Tests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Swiftilities_Tests/Pods-Swiftilities_Tests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 662F65D4D4E8DC340A9997D2 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -515,6 +604,21 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 521859AE265EF84E00C819D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 521859AF265EF84E00C819D3 /* ValidatorTests.swift in Sources */, + 521859B0265EF84E00C819D3 /* LicenseFormatterTests.swift in Sources */, + 521859B1265EF84E00C819D3 /* TestModel.xcdatamodeld in Sources */, + 521859B2265EF84E00C819D3 /* RootViewControllerTests.swift in Sources */, + 521859B3265EF84E00C819D3 /* TableViewHelperTests.swift in Sources */, + 521859B4265EF84E00C819D3 /* LogTests.swift in Sources */, + 521859B5265EF84E00C819D3 /* MathTests.swift in Sources */, + 521859B6265EF84E00C819D3 /* CoreDataStackTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 607FACCC1AFB9204008FA782 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -553,6 +657,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 521859AB265EF84E00C819D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* Swiftilities_Example */; + targetProxy = 521859AC265EF84E00C819D3 /* PBXContainerItemProxy */; + }; D909A58120DAE3E7007BCA07 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 607FACCF1AFB9204008FA782 /* Swiftilities_Example */; @@ -580,6 +689,52 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 521859BC265EF84E00C819D3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3ED95507725D28046957F181 /* Pods-Swiftilities_Tests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 521859BD265EF84E00C819D3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7396494F9BA8221A6823B9D4 /* Pods-Swiftilities_Tests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 607FACED1AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -776,6 +931,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 521859BB265EF84E00C819D3 /* Build configuration list for PBXNativeTarget "Swiftilities_Tests_Latest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 521859BC265EF84E00C819D3 /* Debug */, + 521859BD265EF84E00C819D3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "Swiftilities" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Example/Swiftilities.xcodeproj/xcshareddata/xcschemes/Swiftilities-Example.xcscheme b/Example/Swiftilities.xcodeproj/xcshareddata/xcschemes/Swiftilities-Example.xcscheme index 0430f75..d7b5554 100644 --- a/Example/Swiftilities.xcodeproj/xcshareddata/xcschemes/Swiftilities-Example.xcscheme +++ b/Example/Swiftilities.xcodeproj/xcshareddata/xcschemes/Swiftilities-Example.xcscheme @@ -41,6 +41,15 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -52,18 +61,17 @@ ReferencedContainer = "container:Swiftilities.xcodeproj"> + + + + - - - - - - - - Bool { + Log.trace("DefaultBehaviors", type: .begin) DefaultBehaviors(behaviors: [LogAppearanceBehavior()]).inject() + Log.trace("DefaultBehaviors", type: .end) return true } } diff --git a/Example/Swiftilities/Views/Lifecycle/LogAppearanceBehavior.swift b/Example/Swiftilities/Views/Lifecycle/LogAppearanceBehavior.swift index d735d5e..e298072 100644 --- a/Example/Swiftilities/Views/Lifecycle/LogAppearanceBehavior.swift +++ b/Example/Swiftilities/Views/Lifecycle/LogAppearanceBehavior.swift @@ -14,6 +14,7 @@ struct LogAppearanceBehavior: ViewControllerLifecycleBehavior { public func beforeAppearing(_ viewController: UIViewController, animated: Bool) { // Logger + Log.trace("beforeAppearing", type: .event) Log.logLevel = .info Log.info("\(type(of: viewController))") } diff --git a/Example/Swiftilities/Views/Lifecycle/NavBarTitleTransitionDemoViewController.swift b/Example/Swiftilities/Views/Lifecycle/NavBarTitleTransitionDemoViewController.swift index 194a5d5..c814ef2 100644 --- a/Example/Swiftilities/Views/Lifecycle/NavBarTitleTransitionDemoViewController.swift +++ b/Example/Swiftilities/Views/Lifecycle/NavBarTitleTransitionDemoViewController.swift @@ -29,6 +29,7 @@ class NavBarTitleTransitionDemoViewController: UIViewController { extension NavBarTitleTransitionDemoViewController { override func loadView() { + Log.trace("NavBarTransition", type: .begin) view = UIView() view.addSubview(scrollView) scrollView.addSubview(contentView) @@ -52,6 +53,7 @@ extension NavBarTitleTransitionDemoViewController { titleLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor), titleLabel.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 200), ]) + Log.trace("NavBarTransition", type: .end) } } diff --git a/Example/Tests/LogTests.swift b/Example/Tests/LogTests.swift index 9392db6..fe9a051 100644 --- a/Example/Tests/LogTests.swift +++ b/Example/Tests/LogTests.swift @@ -11,23 +11,67 @@ import XCTest class LogTests: XCTestCase { + final class Loggers { + static let userInterface = Log("UI", logLevel: .verbose) + static let app = Log("App") + } + + class NetworkLog: Log { + static var networkLog = Log("Network") + + override class var instance: Log { + return networkLog + } + } + func testLogHandler() { + let handler = LocalLogHandler() + var loggedStrings: [(level: Log.Level, string: String)] = [] - Log.handler = { (level, string) in + Log.globalHandler = { (log, level, string) in + handler.addEntry(log: log, level: level, string: string) loggedStrings.append((level: level, string: string)) } - Log.logLevel = .warn + var networkStrings: [(level: Log.Level, string: String)] = [] + NetworkLog.handler = { (level, string) in + networkStrings.append((level: level, string: string)) + } + + let localInstance = Log("Local Instance", logLevel: .verbose, useEmoji: true) + + Log.logLevel = .trace + Log.useEmoji = true + Log.trace("tests", type: .begin) Log.verbose("Ignore me") Log.error("Don't ignore me!") + NetworkLog.logLevel = .info + NetworkLog.verbose("network verbose") + NetworkLog.debug("network debug") + NetworkLog.info("network info") + NetworkLog.warn("Network warning") + NetworkLog.error("network ERROR!") + + localInstance.warn("Local Instance Warning") + Loggers.userInterface.info("Button Pressed") + Loggers.userInterface.info("Button Released") + Loggers.app.warn("Out of Memory") + + Log.trace("tests", type: .end) + // Log messages include the date, which is not conducive to testing, // so we just check the end of the logged string. - XCTAssertEqual(loggedStrings.count, 1) - XCTAssertEqual(loggedStrings[0].level, .error) - XCTAssertTrue(loggedStrings[0].string.hasSuffix("Don't ignore me!")) - } + XCTAssertEqual(loggedStrings.count, 9) + XCTAssertEqual(loggedStrings[1].level, .error) + XCTAssertTrue(loggedStrings[1].string.hasSuffix("Don't ignore me!")) + XCTAssertTrue(loggedStrings[2].string.hasSuffix("network info")) + XCTAssertEqual(networkStrings.count, 3) + XCTAssertEqual(handler.getEntries(level: .error).count, 2) + XCTAssertEqual(handler.getEntries("Network", level: .error).count, 1) + XCTAssertEqual(handler.search("Button").count, 2) + } } diff --git a/Pod/Classes/Logging/LocalLogHandler.swift b/Pod/Classes/Logging/LocalLogHandler.swift new file mode 100644 index 0000000..755408d --- /dev/null +++ b/Pod/Classes/Logging/LocalLogHandler.swift @@ -0,0 +1,139 @@ +// +// LocalLogHandler.swift +// Swiftilities +// +// Created by Justin Magnini on 5/27/21. +// + +import Foundation + +public protocol LogEntry { + /// Category of the Log where this entry was originated from + var category: String {get} + + /// Log level of entry. + var level: Log.Level {get} + + /// Message sent by Log. + var message: String {get} + + /// When Log entry was received + var timestamp: Date {get} +} + +extension LogEntry { + public func matches(query: String) -> Bool { + (message.range(of: query, options: .regularExpression)?.isEmpty ?? true) == false + } +} + +/// A handler to collect Log messages locally, and allow for searching and filtering. +open class LocalLogHandler { + + /// Basic implementation of the LogEntry protocol + struct Entry: LogEntry { + /// Category of the Log where this entry was originated from + var category: String + + /// Log level of entry. + var level: Log.Level + + /// Message sent by Log. + var message: String + + /// When Log entry was received + var timestamp: Date + } + + /// Maximum number of Log entries that may be collected. + public var size: UInt = 1000 + + private var entries: [LogEntry] = [] + + // MARK: Initializer + + public init(size: UInt = 1000) { + self.size = size + } + + // MARK: Public Methods + + /// Intended to be called from the Log.globalHandler to record Log messages. + public func addEntry(log: Log, level: Log.Level, string: String) { + addEntry(Entry(category: log.name, level: level, message: string, timestamp: Date())) + } + + /// Intended to be called from the Log.globalHandler to record Log messages. + public func addEntry(_ entry: LogEntry) { + entries.append(entry) + while entries.count > size { + entries.remove(at: 0) + } + } + + /// Remove all entries + public func removeAllEntries() { + entries.removeAll() + } + + /// Returns all entries captured by the LocalLogHandler raw, and unfiltered + public func allEntries() -> [LogEntry] { + return entries + } + + /** + Filters all log entries, only returning those that match the given criteria + + - parameter category: If non-nill, entry must have come from a Log with this category. + - parameter level: If non-nill, minimum level for a log entry. + + - returns: A filtered listed of `LogEntry` elements that match the criteria. + */ + public func getEntries(_ category: String, level: Log.Level? = nil) -> [LogEntry] { + return getEntries(categories: [category], level: level) + } + + /** + Filters all log entries, only returning those that match the given criteria + + - parameter categories: If non-nill, entry must have come from a Log with one of these category. + - parameter level: If non-nill, minimum level for a log entry. + + - returns: A filtered listed of `LogEntry` elements that match the criteria. + */ + public func getEntries(categories: [String]? = nil, level: Log.Level? = nil) -> [LogEntry] { + return entries.filter { + if !(categories?.contains($0.category) ?? true) { + return false + } + + if $0.level.rawValue < level?.rawValue ?? 0 { + return false + } + + return true + } + } + + /** + Searches for log entries that match the provided query. + + - parameter query: Regular Expression search query. + + - returns: A filtered listed of `LogEntry` elements that match the query. + */ + public func search(_ query: String) -> [LogEntry] { + return entries.filter { + ($0.message.range(of: query, options: .regularExpression)?.isEmpty ?? true) == false + } + } + + /// Exports Data containing a UTF8 summary of all collected Log entries. + public func export() -> Data { + var log: String = "" + entries.forEach { + log += $0.message + "\n" + } + return Data(log.utf8) + } +} diff --git a/Pod/Classes/Logging/Log.swift b/Pod/Classes/Logging/Log.swift index bab164b..ec876b5 100644 --- a/Pod/Classes/Logging/Log.swift +++ b/Pod/Classes/Logging/Log.swift @@ -7,28 +7,34 @@ // import Foundation +import os /** -* A simple log that outputs to the console via ```print()```` +* A simple log that outputs to the console via `print()` and also logs to the console with OSLog (if available) */ open class Log { - // MARK: Configuration + // MARK: Types + + public typealias InstanceLogHandler = ((Level, String) -> Void) + public typealias GlobalLogHandler = ((Log, Level, String) -> Void) /** Represents a level of detail to be logged. */ - public enum Level: Int { + public enum Level: Int, CaseIterable { case verbose + case trace case debug case info case warn case error case off - var name: String { + public var name: String { switch self { case .verbose: return "Verbose" + case .trace: return "Trace" case .debug: return "Debug" case .info: return "Info" case .warn: return "Warn" @@ -37,9 +43,10 @@ open class Log { } } - var emoji: String { + public var emoji: String { switch self { case .verbose: return "📖" + case .trace: return "🪧" case .debug: return "🐝" case .info: return "✏️" case .warn: return "⚠️" @@ -49,11 +56,45 @@ open class Log { } } + /// Represents the OSSignpostType when sending trace messages to instruments + public enum TraceType { + case begin + case end + case event + } + + public static private(set) var shared = Log("Log", logLevel: .off) + + /// Static instance used for helper methods. + open class var instance: Log { + return shared + } + + /// Subsystem of the OSLog message when running on iOS 14 or later. + open var subsystem: String { + let bundleName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + return (bundleName ?? "com.rightpoint.swiftilities") + ".log" + } + + /// If this is non-nil, we will call it with the same string that we + /// are going to print to the console. You can use this to pass log + /// messages along to your crash reporter, analytics service, etc. + /// - warning: Be mindful of private user data that might end up in + /// your log statements! Use log levels appropriately + /// to keep private data out of logs that are sent over + /// the Internet. + public static var globalHandler: GlobalLogHandler? + + // MARK: Configuration + + /// Displayed name of the Log instance, also used as the Category in OSLog messages + public var name: String + /// The log level, defaults to .Off - public static var logLevel: Level = .off + public var logLevel: Level = .off /// If true, prints emojis to signify log type, defaults to off - public static var useEmoji: Bool = false + public var useEmoji: Bool = false /// If this is non-nil, we will call it with the same string that we /// are going to print to the console. You can use this to pass log @@ -62,7 +103,15 @@ open class Log { /// your log statements! Use log levels appropriately /// to keep private data out of logs that are sent over /// the Internet. - public static var handler: ((Level, String) -> Void)? + public var handler: InstanceLogHandler? + + // MARK: Initializer + + required public init(_ name: String, logLevel: Level = .off, useEmoji: Bool = false) { + self.name = name + self.logLevel = logLevel + self.useEmoji = useEmoji + } // MARK: Private @@ -74,37 +123,129 @@ open class Log { }() /// Generic log method - fileprivate static func log(_ object: @autoclosure () -> T, level: Log.Level, _ fileName: String, _ functionName: String, _ line: Int) { - if logLevel.rawValue <= level.rawValue { - let date = Log.dateformatter.string(from: Date()) - let components: [String] = fileName.components(separatedBy: "/") - let objectName = components.last ?? "Unknown Object" - let levelString = Log.useEmoji ? level.emoji : "|" + level.name.uppercased() + "|" - let logString = "\(levelString)\(date) \(objectName) \(functionName) line \(line):\n\(object())" - print(logString + "\n") - handler?(level, logString) + internal func log(_ object: @autoclosure () -> T, level: Log.Level, _ fileName: String, _ functionName: String, _ line: Int) { + guard logLevel.rawValue <= level.rawValue else {return} + + let date = Log.dateformatter.string(from: Date()) + let components: [String] = fileName.components(separatedBy: "/") + let objectName = components.last ?? "Unknown Object" + let levelString = useEmoji ? level.emoji : "|" + level.name.uppercased() + "|" + let logString = "\(levelString)\(date) \(objectName) \(functionName) line \(line):\n\(object())" + + if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { + let objectString = "\(object())" + let logMessage = "\(objectName) \(functionName) line \(line)" + osLog(logMessage, objectString: objectString, level: level) + } + else { + let nameString = name.count > 0 ? "[\(name)]" : "" + print(nameString + logString + "\n") } + self.handler?(level, logString) + Log.globalHandler?(self, level, logString) } - // MARK: Log Methods + @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) + internal func osLog(_ logMessage: String, objectString: String, level: Level) { + let logger = Logger(subsystem: subsystem, category: name) + switch level { + case .trace, .verbose: + logger.trace("\(logMessage):\n\(objectString)") + case .debug: + logger.debug("\(logMessage):\n\(objectString)") + case .info: + logger.info("\(logMessage):\n\(objectString)") + case .warn: + logger.warning("\(logMessage):\n\(objectString)") + case .error: + logger.error("\(logMessage):\n\(objectString)") + case .off: + break + } + } - public static func error(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) + internal func osSignpost(_ name: StaticString, type: TraceType) { + let log = OSLog(subsystem: subsystem, category: self.name) + switch type { + case .begin: + os_signpost(.begin, log: log, name: name) + case .end: + os_signpost(.end, log: log, name: name) + case .event: + os_signpost(.event, log: log, name: name) + } + } + + // MARK: Public + + public func error(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { log(object(), level:.error, fileName, functionName, line) } - public static func warn(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + public func warn(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { log(object(), level:.warn, fileName, functionName, line) } - public static func info(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + public func info(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { log(object(), level:.info, fileName, functionName, line) } - public static func debug(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + public func debug(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { log(object(), level:.debug, fileName, functionName, line) } - public static func verbose(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + public func verbose(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { log(object(), level:.verbose, fileName, functionName, line) } + + public func trace(_ name: StaticString, type: TraceType = .event, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + log("Trace: \(name)", level: .trace, fileName, functionName, line) + if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) { + osSignpost(name, type: type) + } + } +} + +// MARK: Static Helper Methods +extension Log { + + public static var logLevel: Level { + get { instance.logLevel } + set { instance.logLevel = newValue } + } + + public static var useEmoji: Bool { + get { instance.useEmoji } + set { instance.useEmoji = newValue } + } + + public static var handler: InstanceLogHandler? { + get { instance.handler } + set { instance.handler = newValue } + } + + public static func error(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + instance.log(object(), level:.error, fileName, functionName, line) + } + + public static func warn(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + instance.log(object(), level:.warn, fileName, functionName, line) + } + + public static func info(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + instance.log(object(), level:.info, fileName, functionName, line) + } + + public static func debug(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + instance.log(object(), level:.debug, fileName, functionName, line) + } + + public static func verbose(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + instance.log(object(), level:.verbose, fileName, functionName, line) + } + + public static func trace(_ name: StaticString, type: TraceType = .event, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { + instance.trace(name, type: type, fileName, functionName, line) + } } diff --git a/Pod/Classes/Logging/README.md b/Pod/Classes/Logging/README.md index f1c906f..3e46a48 100644 --- a/Pod/Classes/Logging/README.md +++ b/Pod/Classes/Logging/README.md @@ -23,12 +23,36 @@ Log levels (from highest to lowest priority): - error - off +When running on iOS 14 or later, log messages will also be reported to OSLog. + ## Advanced functionality -You can include one custom handler that will get called for any string being logged. Assigning another handler will replace the first. +You can include one global custom handler that will get called for any string being logged across all Log instances. Assigning another handler will replace the first. + +```swift +Log.globalHandler = { (log, level, string) in + sendToAnalytics((key: level, string: string)) +} +``` + +Log messages may be categorized by defining separate instances of the Log class. + +```swift +final class Loggers { + static let userInterface = Log("UI", logLevel: .verbose) + static let app = Log("App") + static let network = Log("Network", logLevel: .error) +} + +Loggers.userInterface.info("Button Pressed") +Loggers.network.error("Token Expired") +Loggers.app.warn("Out of Memory") +``` + +You may also include one custom handler per Log instance. Messages will still be reported to the global handler as well. ```swift -Log.handler = { (level, string) in +Loggers.network.handler = { (level, string) in sendToAnalytics((key: level, string: string)) } ```