From 0a74291b48fe76f53bd2151544856c49a63f476a Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Tue, 24 May 2022 20:20:22 +0200 Subject: [PATCH 1/9] Add pod spec file --- CleanroomLogger.podspec | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CleanroomLogger.podspec diff --git a/CleanroomLogger.podspec b/CleanroomLogger.podspec new file mode 100644 index 00000000..d21c69aa --- /dev/null +++ b/CleanroomLogger.podspec @@ -0,0 +1,16 @@ +Pod::Spec.new do |s| + s.name = 'CleanroomLogger' + s.version = '7.0.1' + s.summary = 'Extensible Swift-based logging API that is simple, lightweight and performant' + s.homepage = 'https://github.com/emaloney/CleanroomLogger' + s.author = 'emaloney' + s.source = { :git => 'https://github.com/emaloney/CleanroomLogger.git', :tag => s.version } + s.ios.deployment_target = "9.0" + s.watchos.deployment_target = "4.0" + s.tvos.deployment_target = "12.0" + s.osx.deployment_target = "10.10" + s.source_files = 'Sources/*.swift' + s.license = 'MIT' + + s.swift_version = '5.0' +end \ No newline at end of file From 027ecfbdfb42b04646c7041c65e63c38e5e1e3b9 Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Thu, 26 May 2022 16:00:42 +0200 Subject: [PATCH 2/9] Remove DevelopmentTeam from signing/capabilities --- CleanroomLogger.xcodeproj/project.pbxproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CleanroomLogger.xcodeproj/project.pbxproj b/CleanroomLogger.xcodeproj/project.pbxproj index fc765979..6047dcac 100644 --- a/CleanroomLogger.xcodeproj/project.pbxproj +++ b/CleanroomLogger.xcodeproj/project.pbxproj @@ -311,7 +311,6 @@ TargetAttributes = { 3B9059021DAECB5200B4EEC0 = { CreatedOnToolsVersion = 8.0; - DevelopmentTeam = MTP6A36P8K; LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; @@ -328,6 +327,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 3B9058F91DAECB5200B4EEC0; @@ -548,6 +548,7 @@ baseConfigurationReference = 3B9059251DAECB9E00B4EEC0 /* Debug.xcconfig */; buildSettings = { DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 35; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -562,6 +563,7 @@ baseConfigurationReference = 3B9059281DAECB9E00B4EEC0 /* Release.xcconfig */; buildSettings = { DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 35; DYLIB_INSTALL_NAME_BASE = "@rpath"; From fb6f1e7d98f2fba3a058d563e4eda0141cad033b Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Thu, 26 May 2022 16:01:17 +0200 Subject: [PATCH 3/9] Add parameters for the max size of a log file --- Sources/RotatingLogFileConfiguration.swift | 7 +++++-- Sources/RotatingLogFileRecorder.swift | 11 ++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Sources/RotatingLogFileConfiguration.swift b/Sources/RotatingLogFileConfiguration.swift index bc3b2262..6816abb3 100644 --- a/Sources/RotatingLogFileConfiguration.swift +++ b/Sources/RotatingLogFileConfiguration.swift @@ -56,10 +56,13 @@ open class RotatingLogFileConfiguration: BasicLogConfiguration sequence, and the formatted string returned by the first formatter to yield a non-`nil` value will be recorded. If every formatter returns `nil`, the log entry is silently ignored and not recorded. + + - parameter maximumFileSize: The approximate maximum size (in bytes) to allow log files to grow. + If a log file is larger than this value after a log statement is appended, then the log file is rolled. */ - public init(minimumSeverity: LogSeverity, daysToKeep: Int, directoryPath: String, synchronousMode: Bool = false, filters: [LogFilter] = [], formatters: [LogFormatter] = [ReadableLogFormatter()]) + public init(minimumSeverity: LogSeverity, daysToKeep: Int, directoryPath: String, synchronousMode: Bool = false, filters: [LogFilter] = [], formatters: [LogFormatter] = [ReadableLogFormatter()], maximumFileSize: Int64? = nil) { - logFileRecorder = RotatingLogFileRecorder(daysToKeep: daysToKeep, directoryPath: directoryPath, formatters: formatters) + logFileRecorder = RotatingLogFileRecorder(daysToKeep: daysToKeep, directoryPath: directoryPath, formatters: formatters, maximumFileSize: maximumFileSize) super.init(minimumSeverity: minimumSeverity, filters: filters, recorders: [logFileRecorder], synchronousMode: synchronousMode) } diff --git a/Sources/RotatingLogFileRecorder.swift b/Sources/RotatingLogFileRecorder.swift index d5c534de..480fdcf8 100644 --- a/Sources/RotatingLogFileRecorder.swift +++ b/Sources/RotatingLogFileRecorder.swift @@ -25,6 +25,11 @@ open class RotatingLogFileRecorder: LogRecorderBase /** The filesystem path to a directory where the log files will be stored. */ public let directoryPath: String + + /** The approximate maximum size (in bytes) to allow log files to grow. + If a log file is larger than this value after a log statement is appended, + then the log file is rolled. */ + public let maximumFileSize: Int64? private static let filenameFormatter: DateFormatter = { let fmt = DateFormatter() @@ -56,11 +61,15 @@ open class RotatingLogFileRecorder: LogRecorderBase sequence, and the formatted string returned by the first formatter to yield a non-`nil` value will be recorded. If every formatter returns `nil`, the log entry is silently ignored and not recorded. + + - parameter maximumFileSize: The approximate maximum size (in bytes) to allow log files to grow. + If a log file is larger than this value after a log statement is appended, then the log file is rolled. */ - public init(daysToKeep: Int, directoryPath: String, formatters: [LogFormatter] = [ReadableLogFormatter()]) + public init(daysToKeep: Int, directoryPath: String, formatters: [LogFormatter] = [ReadableLogFormatter()], maximumFileSize: Int64? = nil) { self.daysToKeep = daysToKeep self.directoryPath = directoryPath + self.maximumFileSize = maximumFileSize super.init(formatters: formatters) } From 123cddbed62c8ee9040b104f73ece20996a67bdd Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Thu, 26 May 2022 16:05:15 +0200 Subject: [PATCH 4/9] Add support for rolling log files once they are too large - Once a log file meets the maximum size, create a new log file to continue logging - Update pruning the files to handle rolled files and not delete them - Update logFilename generation to incorporate the rolled log file number --- Sources/RotatingLogFileRecorder.swift | 121 +++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 13 deletions(-) diff --git a/Sources/RotatingLogFileRecorder.swift b/Sources/RotatingLogFileRecorder.swift index 480fdcf8..0b58babc 100644 --- a/Sources/RotatingLogFileRecorder.swift +++ b/Sources/RotatingLogFileRecorder.swift @@ -27,24 +27,25 @@ open class RotatingLogFileRecorder: LogRecorderBase public let directoryPath: String /** The approximate maximum size (in bytes) to allow log files to grow. - If a log file is larger than this value after a log statement is appended, - then the log file is rolled. */ + If a log file is larger than this value after a log statement is appended, + then the log file is rolled. */ public let maximumFileSize: Int64? private static let filenameFormatter: DateFormatter = { let fmt = DateFormatter() - fmt.dateFormat = "yyyy-MM-dd'.log'" + fmt.dateFormat = "yyyy-MM-dd" return fmt }() private var mostRecentLogTime: Date? private var currentFileRecorder: FileLogRecorder? + private static var currentNumberOfRolledFiles: Int? /** Initializes a new `RotatingLogFileRecorder` instance. - warning: The `RotatingLogFileRecorder` expects to have full control over - the contents of its `directoryPath`. Any file not recognized as an active + the contents of its `directoryPath`. Any file not recognized as an active log file will be deleted during the automatic pruning process, which may occur at any time. Therefore, be __extremely careful__ when constructing the value passed in as the `directoryPath`. @@ -82,24 +83,101 @@ open class RotatingLogFileRecorder: LogRecorderBase - returns: The filename. */ - open class func logFilename(forDate date: Date) + open class func logFilename(forDate date: Date, rolledLogFileNumber: Int? = nil, withExtension: Bool = true) -> String { - return filenameFormatter.string(from: date) + guard let rolledLogFileNumber = rolledLogFileNumber, + rolledLogFileNumber > 0 else + { + return "\(filenameFormatter.string(from: date))\(withExtension ? ".log" : "")" + } + return "\(filenameFormatter.string(from: date))(\(rolledLogFileNumber))\(withExtension ? ".log" : "")" + } + + private class func hasExceeded(fileSize: Int64, at path: String) -> Bool + { + guard let fileAttributes = try? FileManager.default.attributesOfItem(atPath: path), + let bytes = fileAttributes[.size] as? Int64, + bytes >= fileSize else + { + return false + } + + return true } - private class func fileLogRecorder(_ date: Date, directoryPath: String, formatters: [LogFormatter]) + private class func fileLogRecorder(_ date: Date, directoryPath: String, formatters: [LogFormatter], maximumFileSize: Int64? = nil) -> FileLogRecorder? { - let fileName = logFilename(forDate: date) - let filePath = (directoryPath as NSString).appendingPathComponent(fileName) + let fileNameWithoutExtension = logFilename(forDate: date, withExtension: false) + var fileName = logFilename(forDate: date) + var filePath = (directoryPath as NSString).appendingPathComponent(fileName) + + guard let maximumFileSize = maximumFileSize else + { + return FileLogRecorder(filePath: filePath, formatters: formatters) + } + + if FileManager.default.fileExists(atPath: filePath) + { + // Check if the first file is larger than the maxLogSize + guard hasExceeded(fileSize: maximumFileSize, at: filePath) else + { + return FileLogRecorder(filePath: filePath, formatters: formatters) + } + + if let currentNumberOfRolledFiles = self.currentNumberOfRolledFiles + { + let nextRolledNumber = currentNumberOfRolledFiles + 1 + + fileName = logFilename(forDate: date, rolledLogFileNumber: nextRolledNumber) + filePath = (directoryPath as NSString).appendingPathComponent(fileName) + self.currentNumberOfRolledFiles = nextRolledNumber + return FileLogRecorder(filePath: filePath, formatters: formatters) + } + else + { + // Identify the current number of rolled log files for this date + let directoryContents = try? FileManager.default.contentsOfDirectory(atPath: directoryPath) + .filter { return $0 != fileName } + .filter { return $0.contains(fileNameWithoutExtension) } + .sorted() + + guard directoryContents?.isEmpty == false else + { + // If no rolled files, start with 1 + fileName = logFilename(forDate: date, rolledLogFileNumber: 1) + filePath = (directoryPath as NSString).appendingPathComponent(fileName) + self.currentNumberOfRolledFiles = 1 + return FileLogRecorder(filePath: filePath, formatters: formatters) + } + + // Check if the newest rolled file exceeds the limit or not + let newestFilePath = (directoryPath as NSString).appendingPathComponent(directoryContents!.last!) + guard hasExceeded(fileSize: maximumFileSize, at: newestFilePath) else + { + self.currentNumberOfRolledFiles = directoryContents!.count + fileName = logFilename(forDate: date, rolledLogFileNumber: directoryContents!.count) + filePath = (directoryPath as NSString).appendingPathComponent(fileName) + return FileLogRecorder(filePath: filePath, formatters: formatters) + } + + // If it does, create a new file + // +1 because the initial (non-rolled) file has been filtered from the directoryContents + fileName = logFilename(forDate: date, rolledLogFileNumber: directoryContents!.count + 1) + filePath = (directoryPath as NSString).appendingPathComponent(fileName) + self.currentNumberOfRolledFiles = directoryContents!.count + 1 + + } + } + return FileLogRecorder(filePath: filePath, formatters: formatters) } private func fileLogRecorder(_ date: Date) -> FileLogRecorder? { - return type(of: self).fileLogRecorder(date, directoryPath: directoryPath, formatters: formatters) + return type(of: self).fileLogRecorder(date, directoryPath: directoryPath, formatters: formatters, maximumFileSize: self.maximumFileSize) } private func isDate(_ firstDate: Date, onSameDayAs secondDate: Date) @@ -109,6 +187,16 @@ open class RotatingLogFileRecorder: LogRecorderBase let secondDateStr = type(of: self).logFilename(forDate: secondDate) return firstDateStr == secondDateStr } + + /// Checks if the current log file exceeds the maximum file size allowed for a log file + private func validateMaxLogSize(_ entry: LogEntry) + { + guard let maximumFileSize = self.maximumFileSize, + let filePath = currentFileRecorder?.filePath, + RotatingLogFileRecorder.hasExceeded(fileSize: maximumFileSize, at: filePath) else { return } + + currentFileRecorder = fileLogRecorder(entry.timestamp) + } /** Attempts to create—if it does not already exist—the directory indicated @@ -149,6 +237,8 @@ open class RotatingLogFileRecorder: LogRecorderBase mostRecentLogTime = entry.timestamp as Date currentFileRecorder?.record(message: message, for: entry, currentQueue: queue, synchronousMode: synchronousMode) + + validateMaxLogSize(entry) } /** @@ -165,7 +255,7 @@ open class RotatingLogFileRecorder: LogRecorderBase var date = Date() var filesToKeep = Set() for _ in 0.. Bool { + strings.contains { contains($0) } + } +} From 279528b45151979f111d50dfb5a18f8995d2ad78 Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Thu, 26 May 2022 18:10:11 +0200 Subject: [PATCH 5/9] Update naming and documentation for functions --- Sources/RotatingLogFileRecorder.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/RotatingLogFileRecorder.swift b/Sources/RotatingLogFileRecorder.swift index 0b58babc..8940172a 100644 --- a/Sources/RotatingLogFileRecorder.swift +++ b/Sources/RotatingLogFileRecorder.swift @@ -188,8 +188,12 @@ open class RotatingLogFileRecorder: LogRecorderBase return firstDateStr == secondDateStr } - /// Checks if the current log file exceeds the maximum file size allowed for a log file - private func validateMaxLogSize(_ entry: LogEntry) + /** + Checks if the current log file exceeds the maximum file size allowed, if it does, the log files are rolled. + + - parameter entry: the log entry to base the new log file off if required + */ + private func rollLogFileIfNeeded(_ entry: LogEntry) { guard let maximumFileSize = self.maximumFileSize, let filePath = currentFileRecorder?.filePath, @@ -238,7 +242,7 @@ open class RotatingLogFileRecorder: LogRecorderBase currentFileRecorder?.record(message: message, for: entry, currentQueue: queue, synchronousMode: synchronousMode) - validateMaxLogSize(entry) + rollLogFileIfNeeded(entry) } /** From 50b47ea9731ef509891d9c817ec56c91f8588085 Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Thu, 26 May 2022 18:50:58 +0200 Subject: [PATCH 6/9] Add documentation to function --- Sources/RotatingLogFileRecorder.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/RotatingLogFileRecorder.swift b/Sources/RotatingLogFileRecorder.swift index 8940172a..571d2c1b 100644 --- a/Sources/RotatingLogFileRecorder.swift +++ b/Sources/RotatingLogFileRecorder.swift @@ -94,6 +94,14 @@ open class RotatingLogFileRecorder: LogRecorderBase return "\(filenameFormatter.string(from: date))(\(rolledLogFileNumber))\(withExtension ? ".log" : "")" } + /** + Returns a bool defining whether the size of the file at the provided path is greater than the provided file size + + - parameter fileSize: The file size `(in bytes)` that is used as the max size for file + - parameter path: The path to the file to be checked + + - returns: A bool indicating if the file exceeds the given file size. + */ private class func hasExceeded(fileSize: Int64, at path: String) -> Bool { guard let fileAttributes = try? FileManager.default.attributesOfItem(atPath: path), From 7597bf2be8097652a9eb27fcbd8c579a99e71a5d Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Tue, 29 Nov 2022 17:55:09 +0100 Subject: [PATCH 7/9] Add disable function --- Sources/Log.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/Log.swift b/Sources/Log.swift index 5986196e..dd476919 100644 --- a/Sources/Log.swift +++ b/Sources/Log.swift @@ -233,6 +233,19 @@ public struct Log } logLock.unlock() } + + /// Experimental - Try to use in case you need to disable logger to change the configuration and restart. + public static func disable() + { + logLock.lock() + + if didEnable + { + didEnable = false + } + + logLock.unlock() + } private static let logLock = NSLock() private static var didEnable = false From 7b33c1a93912bbdd0c58558ef670edd9289235aa Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Tue, 29 Nov 2022 18:13:53 +0100 Subject: [PATCH 8/9] Update channels to be set to nil on disable --- Sources/Log.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/Log.swift b/Sources/Log.swift index dd476919..b4ada9ad 100644 --- a/Sources/Log.swift +++ b/Sources/Log.swift @@ -241,6 +241,11 @@ public struct Log if didEnable { + self.error = nil + self.warning = nil + self.info = nil + self.debug = nil + self.verbose = nil didEnable = false } From e8d4dbaa93deee42113d4d443316a734edd0ca7a Mon Sep 17 00:00:00 2001 From: Alex Marchant Date: Wed, 30 Nov 2022 14:15:46 +0100 Subject: [PATCH 9/9] Update disable function description --- Sources/Log.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/Log.swift b/Sources/Log.swift index b4ada9ad..ad213d9f 100644 --- a/Sources/Log.swift +++ b/Sources/Log.swift @@ -234,7 +234,11 @@ public struct Log logLock.unlock() } - /// Experimental - Try to use in case you need to disable logger to change the configuration and restart. + /** + Disables the logger, setting all existing channels to nil. + + Disabling the logger allows you to restart/reconfigure the logger in real-time without the need to restart the whole app. + */ public static func disable() { logLock.lock()