Skip to content

Commit

Permalink
fixes crash due to non-removal of files exceeding max, zeros out file… (
Browse files Browse the repository at this point in the history
#1)

* fixes crash due to non-removal of files exceeding max, zeros out file instead of deleting to allow tail-f to keep working

* added log file rotation if file size exceeds 'fileSizeLimitBytes'.  Fixed bug : no new file created in setNewFile(), Fixed bug : file size not reported correctly due to pending async writes.

* removed unused function

* added wait() call to deinit
  • Loading branch information
zazula authored Nov 25, 2020
1 parent 290f285 commit d82d0fc
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 13 deletions.
5 changes: 5 additions & 0 deletions Sources/ExtendedLogging/FileLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,9 @@ public struct FileLogger: LogHandler {
self.metadata[metadataKey] = newValue
}
}

// for testing
public func wait() -> Void {
fileWriter.wait()
}
}
62 changes: 49 additions & 13 deletions Sources/ExtendedLogging/FileWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal class FileWriter {
private var nextFilePathGenerationDate: Date? = nil
private var fileHandle: FileHandle? = nil
private var fileSize: Int = 0
private var sequenceNumber: Int = 0

init(path: String, rollingInterval: RollingInteval = .day, fileSizeLimitBytes: Int = 10485760) {
self.path = path
Expand All @@ -22,36 +23,41 @@ internal class FileWriter {
try? file.close()
self.fileHandle = nil
}
wait()
}

func write(message: Data?) {
guard let data = message else {
print("[FileWriter] Logged message cannot be nil.")
return
}

if self.shouldCreateNewFile() {
self.setNewFile()
}


self.saveToFile(data: data)
}

private func saveToFile(data: Data) {
fileQueue.async {
do {
if let file = self.fileHandle {
file.write(data)
self.fileSize += data.count
} else if let file = try? FileHandle(forWritingTo: self.filePath) {
file.seekToEndOfFile()
file.write(data)
// do the size / date check in the async queue
if self.shouldCreateNewFile() {
self.setNewFile()
}

self.fileHandle = file
self.fileSize = self.getFileSize()
if self.fileHandle == nil {
do {
self.fileHandle = try FileHandle(forWritingTo: self.filePath)
self.fileHandle?.seekToEndOfFile()
}
}

if let handle = self.fileHandle {
handle.write(data)
handle.synchronizeFile()
} else {
try data.write(to: self.filePath, options: .atomic)
}

self.fileSize = self.getFileSize()
} catch(let error) {
print("[FileWriter] Could not write to file: \(self.filePath), error: \(error).")
}
Expand All @@ -74,9 +80,11 @@ internal class FileWriter {
self.nextFilePathGenerationDate = self.getNextFilePathGenerationDate()
self.fileSize = 0
self.createDirectories()
FileManager.default.createFile(atPath: self.filePath.path, contents:nil)
}

private func getFilePathWithDateStamp() -> URL {
let fileManager = FileManager.default
var filePath = self.getFilePath()

let pathExtension = filePath.pathExtension
Expand All @@ -85,6 +93,26 @@ internal class FileWriter {
filePath.deletePathExtension()
filePath.appendPathExtension(timeStamp + "." + pathExtension)

// if log file exists at this path, rename it with the next sequence number
if fileManager.fileExists(atPath: filePath.path) {
var newPath = URL(fileURLWithPath:filePath.path)
while fileManager.fileExists(atPath: newPath.path) {
newPath = self.getFilePath()
newPath.deletePathExtension()
newPath.appendPathExtension(timeStamp + String(format: ".%03d.", sequenceNumber) + pathExtension)
sequenceNumber += 1
}

// move old.log -> old.NNN.log
do {
try fileManager.moveItem(at: filePath, to: newPath)
} catch {
print("[FileWriter] Could not rotate log file: \(self.filePath.path), to: \(newPath.path).")
}
} else {
sequenceNumber = 0
}

return filePath
}

Expand Down Expand Up @@ -139,6 +167,7 @@ internal class FileWriter {

private func getFileSize() -> Int {
do {
self.filePath.removeAllCachedResourceValues()
let values = try self.filePath.resourceValues(forKeys: [URLResourceKey.fileSizeKey])
if let fileSize = values.fileSize {
return fileSize
Expand All @@ -159,4 +188,11 @@ internal class FileWriter {
print("[FileWriter] Could not create directory for file: \(self.filePath), error: \(error).")
}
}

// for testing
func wait() -> Void {
fileQueue.sync {
return
}
}
}
38 changes: 38 additions & 0 deletions Tests/ExtendedLoggingTests/ExtendedLoggingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,44 @@ final class ExtendedLoggingTests: XCTestCase {
XCTAssertTrue(content!.contains("[INFO] (mikroservices.mczachurski.dev): Hello World!"))
}

func testFileLoggerRotateLogFiles() {
// Arrange.
let fileManager = FileManager.default
let filePath = getFilePathWithDateStamp(fileName: URL(string: "rotate.log")!)
var filePath2 = URL(fileURLWithPath:filePath.path)

// clean up...
let pathExtension = filePath.pathExtension
filePath2.deletePathExtension()
filePath2.appendPathExtension("000." + pathExtension)

try? fileManager.removeItem(at: filePath)
try? fileManager.removeItem(at: filePath2)

// Act.
let logger = FileLogger(label: "mikroservices.mczachurski.dev",
path: "rotate.log",
level: .debug,
rollingInterval: .day,
fileSizeLimitBytes: 10000)


for _ in 1...200 {
logger.log(level: .info, message: "Hello World!", metadata:nil, file: #file, function: "testFileLoggerRotateLogFiles", line: #line)
}

// wait for async writes to complete
logger.wait()

// Assert.
let fileHandle = try? FileHandle(forReadingFrom: filePath)
XCTAssertNotNil(fileHandle, "Log file missing at (file: \(filePath.path))")


let rotatedFileHandle = try? FileHandle(forReadingFrom: filePath2)
XCTAssertNotNil(rotatedFileHandle, "Log file missing at (file: \(filePath2.path))")
}

private func getFilePathWithDateStamp(fileName: URL) -> URL {
var filePath = fileName
let pathExtension = filePath.pathExtension
Expand Down

0 comments on commit d82d0fc

Please sign in to comment.