From 75edda5d92445daec0d312191710951afac59e24 Mon Sep 17 00:00:00 2001 From: Justin Magnini Date: Thu, 27 May 2021 16:45:43 -0500 Subject: [PATCH] Add LocalLogHandler --- Example/Pods/Pods.xcodeproj/project.pbxproj | 20 ++-- Example/Tests/LogTests.swift | 14 ++- LocalLogHandler.swift | 105 ++++++++++++++++++++ Pod/Classes/Logging/Log.swift | 10 +- 4 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 LocalLogHandler.swift diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index 17ba111..77f1bbf 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 */; }; + 5206744926602E8400EAA222 /* LocalLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5206744826602E8400EAA222 /* 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 = ""; }; + 5206744826602E8400EAA222 /* LocalLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = 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 */, + 5206744826602E8400EAA222 /* LocalLogHandler.swift */, ); name = Logging; sourceTree = ""; @@ -802,6 +803,7 @@ F3B44AF972F75F3EE1041E7134B1FAEE /* FloatingPoint+Scale.swift in Sources */, A75DE9DCB6EF0DC9CDB4C65E49357887 /* FormattedTextField.swift in Sources */, 176B24FD0C3AB94CFFAECCD428B7878B /* GradientView.swift in Sources */, + 5206744926602E8400EAA222 /* LocalLogHandler.swift in Sources */, 0487515C15D580FFB1D6D8DFF94134C6 /* HairlineView.swift in Sources */, FEAFB5C63CFC4AD763F04A0983133AC8 /* IndexPath+Extensions.swift in Sources */, 85EFC41D452763AF84CB440990493133 /* Keyboard.swift in Sources */, diff --git a/Example/Tests/LogTests.swift b/Example/Tests/LogTests.swift index 7ad5b46..c3b937c 100644 --- a/Example/Tests/LogTests.swift +++ b/Example/Tests/LogTests.swift @@ -26,8 +26,11 @@ class LogTests: XCTestCase { func testLogHandler() { + let handler = LocalLogHandler() + var loggedStrings: [(level: Log.Level, string: String)] = [] Log.globalHandler = { (log, level, string) in + handler.addEntry(log: log, level: level, entry: string) loggedStrings.append((level: level, string: string)) } @@ -49,21 +52,26 @@ class LogTests: XCTestCase { NetworkLog.verbose("network verbose") NetworkLog.debug("network debug") NetworkLog.info("network info") - NetworkLog.warn("network warning") - NetworkLog.error("network error") + 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, 8) + 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/LocalLogHandler.swift b/LocalLogHandler.swift new file mode 100644 index 0000000..640f9d2 --- /dev/null +++ b/LocalLogHandler.swift @@ -0,0 +1,105 @@ +// +// LocalLogHandler.swift +// Swiftilities +// +// Created by Justin Magnini on 5/27/21. +// + +import Foundation + +/// A handler to collect Log messages locally, and allow for searching and filtering. +open class LocalLogHandler { + + /// Represents a single entry collected from a Log instance + public struct 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. + 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, entry: String) { + entries.append(.init(category: log.name, level: level, message: entry, timestamp: Date())) + 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 + } + } +} diff --git a/Pod/Classes/Logging/Log.swift b/Pod/Classes/Logging/Log.swift index c25f2e2..92ac2af 100644 --- a/Pod/Classes/Logging/Log.swift +++ b/Pod/Classes/Logging/Log.swift @@ -150,15 +150,15 @@ open class Log { let logger = Logger(subsystem: subsystem, category: name) switch level { case .trace, .verbose: - logger.trace("\(logMessage):\n\(objectString, privacy: .private)") + logger.trace("\(logMessage):\n\(objectString)") case .debug: - logger.debug("\(logMessage):\n\(objectString, privacy: .private)") + logger.debug("\(logMessage):\n\(objectString)") case .info: - logger.info("\(logMessage):\n\(objectString, privacy: .private)") + logger.info("\(logMessage):\n\(objectString)") case .warn: - logger.warning("\(logMessage):\n\(objectString, privacy: .private)") + logger.warning("\(logMessage):\n\(objectString)") case .error: - logger.error("\(logMessage):\n\(objectString, privacy: .private)") + logger.error("\(logMessage):\n\(objectString)") case .off: break }