Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional file protection + backup exclusion to FileLoggerable #93

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions Sources/Puppy/FileLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,59 @@ 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

public init(_ label: String, logLevel: LogLevel = .trace, logFormat: LogFormattable? = nil, fileURL: URL, filePermission: String = "640", flushMode: FlushMode = .always, writeMode: FileWritingErrorHandlingMode = .force) throws {
#if os(iOS) || os(macOS)
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
self.logFormat = logFormat
self.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
puppyDebug("initialized, 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()
Expand Down
14 changes: 14 additions & 0 deletions Sources/Puppy/FileLoggerable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
72 changes: 60 additions & 12 deletions Sources/Puppy/FileRotationLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,86 @@ public struct FileRotationLogger: 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

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 {
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,
fileURL: URL,
filePermission: String = "640",
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)
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()
}

public func log(_ level: LogLevel, string: String) {
rotateFiles()
append(level, string: string)
append(level, string: string, flushMode: flushMode, writeMode: writeMode)
rotateFiles()
}

Expand Down
40 changes: 40 additions & 0 deletions Tests/PuppyTests/FileLoggerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading