From 68dd1bb27f192d16c2367cfcb5287a65438a414d Mon Sep 17 00:00:00 2001 From: Richard Henry Date: Wed, 7 Feb 2024 12:56:47 -0800 Subject: [PATCH 1/3] Add optional file protection + backup exclusion to FileLoggerable --- Sources/Puppy/FileLogger.swift | 13 ++++++++- Sources/Puppy/FileLoggerable.swift | 14 +++++++++ Sources/Puppy/FileRotationLogger.swift | 14 ++++++++- Tests/PuppyTests/FileLoggerTests.swift | 40 ++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/Sources/Puppy/FileLogger.swift b/Sources/Puppy/FileLogger.swift index 5cc02bd..8274c9a 100644 --- a/Sources/Puppy/FileLogger.swift +++ b/Sources/Puppy/FileLogger.swift @@ -9,11 +9,20 @@ public struct FileLogger: FileLoggerable { public let fileURL: URL public let filePermission: String + public let fileProtectionType: FileProtectionType? + public let isExcludedFromBackup: Bool public let flushMode: FlushMode public let writeMode: FileWritingErrorHandlingMode - public init(_ label: String, logLevel: LogLevel = .trace, logFormat: LogFormattable? = nil, fileURL: URL, filePermission: String = "640", flushMode: FlushMode = .always, writeMode: FileWritingErrorHandlingMode = .force) throws { + public init(_ label: String, + logLevel: LogLevel = .trace, + logFormat: LogFormattable? = nil, + fileURL: URL, filePermission: String = "640", + fileProtectionType: FileProtectionType? = nil, + isExcludedFromBackup: Bool = false, + flushMode: FlushMode = .always, + writeMode: FileWritingErrorHandlingMode = .force) throws { self.label = label self.queue = DispatchQueue(label: label) self.logLevel = logLevel @@ -22,6 +31,8 @@ public struct FileLogger: FileLoggerable { self.fileURL = fileURL puppyDebug("initialized, fileURL: \(fileURL)") self.filePermission = filePermission + self.fileProtectionType = fileProtectionType + self.isExcludedFromBackup = isExcludedFromBackup self.flushMode = flushMode self.writeMode = writeMode diff --git a/Sources/Puppy/FileLoggerable.swift b/Sources/Puppy/FileLoggerable.swift index 2089c2e..1a41fd5 100644 --- a/Sources/Puppy/FileLoggerable.swift +++ b/Sources/Puppy/FileLoggerable.swift @@ -19,6 +19,8 @@ public enum FileWritingErrorHandlingMode: Sendable { public protocol FileLoggerable: Loggerable, Sendable { var fileURL: URL { get } var filePermission: String { get } + var fileProtectionType: FileProtectionType? { get } + var isExcludedFromBackup: Bool { get } } extension FileLoggerable { @@ -107,6 +109,18 @@ extension FileLoggerable { if !FileManager.default.fileExists(atPath: fileURL.path) { let successful = FileManager.default.createFile(atPath: fileURL.path, contents: nil, attributes: [FileAttributeKey.posixPermissions: uintPermission]) + + if let fileProtectionType = fileProtectionType { + try FileManager.default.setAttributes([.protectionKey: fileProtectionType], ofItemAtPath: fileURL.path) + } + + if isExcludedFromBackup { + var resourceValues = URLResourceValues() + resourceValues.isExcludedFromBackup = true + var fileURL = fileURL + try fileURL.setResourceValues(resourceValues) + } + if successful { puppyDebug("succeeded in creating filePath") } else { diff --git a/Sources/Puppy/FileRotationLogger.swift b/Sources/Puppy/FileRotationLogger.swift index 78fe376..9b12485 100644 --- a/Sources/Puppy/FileRotationLogger.swift +++ b/Sources/Puppy/FileRotationLogger.swift @@ -9,13 +9,23 @@ public struct FileRotationLogger: FileLoggerable { public let fileURL: URL public let filePermission: String + public var fileProtectionType: FileProtectionType? + public var isExcludedFromBackup: Bool let rotationConfig: RotationConfig private weak var delegate: FileRotationLoggerDelegate? private var dateFormat: DateFormatter - public init(_ label: String, logLevel: LogLevel = .trace, logFormat: LogFormattable? = nil, fileURL: URL, filePermission: String = "640", rotationConfig: RotationConfig, delegate: FileRotationLoggerDelegate? = nil) throws { + public init(_ label: String, + logLevel: LogLevel = .trace, + logFormat: LogFormattable? = nil, + fileURL: URL, + filePermission: String = "640", + fileProtectionType: FileProtectionType? = nil, + isExcludedFromBackup: Bool = false, + rotationConfig: RotationConfig, + delegate: FileRotationLoggerDelegate? = nil) throws { self.label = label self.queue = DispatchQueue(label: label) self.logLevel = logLevel @@ -29,6 +39,8 @@ public struct FileRotationLogger: FileLoggerable { self.fileURL = fileURL puppyDebug("initialized, fileURL: \(fileURL)") self.filePermission = filePermission + self.fileProtectionType = fileProtectionType + self.isExcludedFromBackup = isExcludedFromBackup self.rotationConfig = rotationConfig self.delegate = delegate diff --git a/Tests/PuppyTests/FileLoggerTests.swift b/Tests/PuppyTests/FileLoggerTests.swift index 0f5defa..2191424 100644 --- a/Tests/PuppyTests/FileLoggerTests.swift +++ b/Tests/PuppyTests/FileLoggerTests.swift @@ -88,6 +88,46 @@ final class FileLoggerTests: XCTestCase { log.remove(fileLogger) } + func testFileProtection() throws { + #if os(iOS) || os(macOS) + let fileURL = URL(fileURLWithPath: "./protected.log").absoluteURL + let fileLogger: FileLogger = try .init("com.example.yourapp.filelogger.protected", fileURL: fileURL, fileProtectionType: .completeUntilFirstUserAuthentication) + var log = Puppy() + log.add(fileLogger) + log.trace("protection, TRACE message using FileLogger") + log.verbose("protection, VERBOSE message using FileLogger") + fileLogger.flush(fileURL) + + let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path) + // swiftlint:disable force_cast + let protection = attributes[FileAttributeKey.protectionKey] as! FileProtectionType + // swiftlint:enable force_cast + + XCTAssertEqual(protection, FileProtectionType.completeUntilFirstUserAuthentication) + + _ = fileLogger.delete(fileURL) + log.remove(fileLogger) + #endif + } + + func testExcludeFromBackup() throws { + #if os(iOS) || os(macOS) + let fileURL = URL(fileURLWithPath: "./exclude-from-backup.log").absoluteURL + let fileLogger: FileLogger = try .init("com.example.yourapp.filelogger.exclude-from-backup", fileURL: fileURL, isExcludedFromBackup: true) + var log = Puppy() + log.add(fileLogger) + log.trace("exclude from backup, TRACE message using FileLogger") + log.verbose("exclude from backup, VERBOSE message using FileLogger") + fileLogger.flush(fileURL) + + let resourceValues = try fileURL.resourceValues(forKeys: [.isExcludedFromBackupKey]) + XCTAssertTrue(resourceValues.isExcludedFromBackup == true) + + _ = fileLogger.delete(fileURL) + log.remove(fileLogger) + #endif + } + func testFilePermissionError() throws { let permission800FileURL = URL(fileURLWithPath: "./permission800.log").absoluteURL let filePermission800 = "800" From f71641d64ee415a61ab885a36fe6b460410e032d Mon Sep 17 00:00:00 2001 From: Richard Henry Date: Wed, 7 Feb 2024 15:30:56 -0800 Subject: [PATCH 2/3] Add flushMode and writeMode to FileRotationLogger --- Sources/Puppy/FileRotationLogger.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/Puppy/FileRotationLogger.swift b/Sources/Puppy/FileRotationLogger.swift index 9b12485..918cbf3 100644 --- a/Sources/Puppy/FileRotationLogger.swift +++ b/Sources/Puppy/FileRotationLogger.swift @@ -12,6 +12,9 @@ public struct FileRotationLogger: FileLoggerable { public var fileProtectionType: FileProtectionType? public var isExcludedFromBackup: Bool + public let flushMode: FlushMode + public let writeMode: FileWritingErrorHandlingMode + let rotationConfig: RotationConfig private weak var delegate: FileRotationLoggerDelegate? @@ -25,6 +28,8 @@ public struct FileRotationLogger: FileLoggerable { fileProtectionType: FileProtectionType? = nil, isExcludedFromBackup: Bool = false, rotationConfig: RotationConfig, + flushMode: FlushMode = .always, + writeMode: FileWritingErrorHandlingMode = .force, delegate: FileRotationLoggerDelegate? = nil) throws { self.label = label self.queue = DispatchQueue(label: label) @@ -42,6 +47,9 @@ public struct FileRotationLogger: FileLoggerable { self.fileProtectionType = fileProtectionType self.isExcludedFromBackup = isExcludedFromBackup + self.flushMode = flushMode + self.writeMode = writeMode + self.rotationConfig = rotationConfig self.delegate = delegate @@ -52,7 +60,7 @@ public struct FileRotationLogger: FileLoggerable { public func log(_ level: LogLevel, string: String) { rotateFiles() - append(level, string: string) + append(level, string: string, flushMode: flushMode, writeMode: writeMode) rotateFiles() } From c3bda2533d090405cceb46e668e589aa87fe8664 Mon Sep 17 00:00:00 2001 From: Richard Henry Date: Fri, 19 Apr 2024 21:44:57 -0700 Subject: [PATCH 3/3] Fix build on Linux and Windows --- Sources/Puppy/FileLogger.swift | 33 ++++++++++++++-- Sources/Puppy/FileRotationLogger.swift | 52 ++++++++++++++++++++------ 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/Sources/Puppy/FileLogger.swift b/Sources/Puppy/FileLogger.swift index 8274c9a..d9c39ba 100644 --- a/Sources/Puppy/FileLogger.swift +++ b/Sources/Puppy/FileLogger.swift @@ -9,16 +9,21 @@ public struct FileLogger: FileLoggerable { public let fileURL: URL public let filePermission: String + + #if os(iOS) || os(macOS) public let fileProtectionType: FileProtectionType? public let isExcludedFromBackup: Bool + #endif public let flushMode: FlushMode public let writeMode: FileWritingErrorHandlingMode + #if os(iOS) || os(macOS) public init(_ label: String, logLevel: LogLevel = .trace, logFormat: LogFormattable? = nil, - fileURL: URL, filePermission: String = "640", + fileURL: URL, + filePermission: String = "640", fileProtectionType: FileProtectionType? = nil, isExcludedFromBackup: Bool = false, flushMode: FlushMode = .always, @@ -27,16 +32,36 @@ public struct FileLogger: FileLoggerable { self.queue = DispatchQueue(label: label) self.logLevel = logLevel self.logFormat = logFormat - self.fileURL = fileURL - puppyDebug("initialized, fileURL: \(fileURL)") self.filePermission = filePermission self.fileProtectionType = fileProtectionType self.isExcludedFromBackup = isExcludedFromBackup - self.flushMode = flushMode self.writeMode = writeMode + try commonInit() + } + #else + public init(_ label: String, + logLevel: LogLevel = .trace, + logFormat: LogFormattable? = nil, + fileURL: URL, + filePermission: String = "640", + flushMode: FlushMode = .always, + writeMode: FileWritingErrorHandlingMode = .force) throws { + self.label = label + self.queue = DispatchQueue(label: label) + self.logLevel = logLevel + self.logFormat = logFormat + self.fileURL = fileURL + self.filePermission = filePermission + self.flushMode = flushMode + self.writeMode = writeMode + try commonInit() + } + #endif + private func commonInit() throws { + puppyDebug("initialized, fileURL: \(fileURL)") try validateFileURL(fileURL) try validateFilePermission(fileURL, filePermission: filePermission) try openFile() diff --git a/Sources/Puppy/FileRotationLogger.swift b/Sources/Puppy/FileRotationLogger.swift index 918cbf3..0b6c421 100644 --- a/Sources/Puppy/FileRotationLogger.swift +++ b/Sources/Puppy/FileRotationLogger.swift @@ -9,8 +9,11 @@ public struct FileRotationLogger: FileLoggerable { public let fileURL: URL public let filePermission: String - public var fileProtectionType: FileProtectionType? - public var isExcludedFromBackup: Bool + + #if os(iOS) || os(macOS) + public let fileProtectionType: FileProtectionType? + public let isExcludedFromBackup: Bool + #endif public let flushMode: FlushMode public let writeMode: FileWritingErrorHandlingMode @@ -18,8 +21,15 @@ public struct FileRotationLogger: FileLoggerable { let rotationConfig: RotationConfig private weak var delegate: FileRotationLoggerDelegate? - private var dateFormat: DateFormatter + private let dateFormat: DateFormatter = { + let dateFormat = DateFormatter() + dateFormat.dateFormat = "yyyyMMdd'T'HHmmssZZZZZ" + dateFormat.timeZone = TimeZone(identifier: "UTC") + dateFormat.locale = Locale(identifier: "en_US_POSIX") + return dateFormat + }() + #if os(iOS) || os(macOS) public init(_ label: String, logLevel: LogLevel = .trace, logFormat: LogFormattable? = nil, @@ -35,24 +45,42 @@ public struct FileRotationLogger: FileLoggerable { self.queue = DispatchQueue(label: label) self.logLevel = logLevel self.logFormat = logFormat - - self.dateFormat = DateFormatter() - self.dateFormat.dateFormat = "yyyyMMdd'T'HHmmssZZZZZ" - self.dateFormat.timeZone = TimeZone(identifier: "UTC") - self.dateFormat.locale = Locale(identifier: "en_US_POSIX") - self.fileURL = fileURL - puppyDebug("initialized, fileURL: \(fileURL)") self.filePermission = filePermission self.fileProtectionType = fileProtectionType self.isExcludedFromBackup = isExcludedFromBackup - self.flushMode = flushMode self.writeMode = writeMode - self.rotationConfig = rotationConfig self.delegate = delegate + try commonInit() + } + #else + public init(_ label: String, + logLevel: LogLevel = .trace, + logFormat: LogFormattable? = nil, + fileURL: URL, + filePermission: String = "640", + rotationConfig: RotationConfig, + flushMode: FlushMode = .always, + writeMode: FileWritingErrorHandlingMode = .force, + delegate: FileRotationLoggerDelegate? = nil) throws { + self.label = label + self.queue = DispatchQueue(label: label) + self.logLevel = logLevel + self.logFormat = logFormat + self.fileURL = fileURL + self.filePermission = filePermission + self.flushMode = flushMode + self.writeMode = writeMode + self.rotationConfig = rotationConfig + self.delegate = delegate + try commonInit() + } + #endif + private func commonInit() throws { + puppyDebug("initialized, fileURL: \(fileURL)") try validateFileURL(fileURL) try validateFilePermission(fileURL, filePermission: filePermission) try openFile()