diff --git a/Sources/Logger/Loggers/FileLogger/FileLogger.swift b/Sources/Logger/Loggers/FileLogger/FileLogger.swift index f116ed9..24a5c8a 100644 --- a/Sources/Logger/Loggers/FileLogger/FileLogger.swift +++ b/Sources/Logger/Loggers/FileLogger/FileLogger.swift @@ -68,10 +68,10 @@ public class FileLogger: Logging { } } - private var currentWritableFileHandle: FileHandle? { + var currentWritableFileHandle: FileHandle? { willSet { if currentWritableFileHandle != newValue { - currentWritableFileHandle?.closeFile() + try? currentWritableFileHandle?.close() } } } @@ -264,8 +264,8 @@ public class FileLogger: Logging { throw FileLoggerError.stringToDataConversionFailure } - fileHandle.seekToEndOfFile() - fileHandle.write(data) + try fileHandle.seekToEnd() + try fileHandle.write(contentsOf: data) } catch let error { self.externalLogger("Failed to write to a log file with error: \(error)!") } diff --git a/Tests/LoggerTests/Loggers/FileLoggerTests.swift b/Tests/LoggerTests/Loggers/FileLoggerTests.swift index b5b63f8..7664db2 100644 --- a/Tests/LoggerTests/Loggers/FileLoggerTests.swift +++ b/Tests/LoggerTests/Loggers/FileLoggerTests.swift @@ -196,6 +196,41 @@ class FileLoggerTests: XCTestCase { } } + func test_failed_write_is_reported_instead_of_crashing() throws { + var internalErrors: [String] = [] + + let fileLogger = try FileLogger( + appName: nil, + fileManager: fileManager, + userDefaults: userDefaults, + logDirURL: logDirURL, + namespace: nil, + numberOfLogFiles: 3, + dateFormatter: DateFormatter.dateFormatter, + fileHeaderContent: "", + lineSeparator: "\n", + logEntryEncoder: LogEntryEncoder(), + logEntryDecoder: LogEntryDecoder(), + externalLogger: { internalErrors.append($0) }, + fileAccessQueue: .syncMock + ) + + fileLogger.log(.mock("First message")) + + // Simulate an I/O failure (e.g. "No space left on device") with a handle that cannot be written to. + // The legacy `seekToEndOfFile()` / `write(_:)` API raised an uncatchable NSException here and crashed the app. + fileLogger.currentWritableFileHandle = try FileHandle(forReadingFrom: fileLogger.currentLogFileUrl) + + fileLogger.log(.mock("Second message")) + + XCTAssertEqual(internalErrors.count, 1) + XCTAssertTrue(internalErrors.first?.hasPrefix("Failed to write to a log file") == true, internalErrors.description) + + let content = try String(contentsOf: fileLogger.currentLogFileUrl, encoding: .utf8) + XCTAssertTrue(content.contains("First message"), content) + XCTAssertFalse(content.contains("Second message"), content) + } + func test_file_rotation() throws { let fileLogger = try FileLogger( appName: nil,