prole/prole-tools-app/Sources/Logger.swift

78 lines
2.8 KiB
Swift

import Foundation
final class Logger {
static let shared = Logger()
private let queue = DispatchQueue(label: "org.prole.tools.logger", qos: .utility)
private var currentDateStr: String = Logger.dateString(Date())
private init() {
// Ensure directories exist
ProleEnv.bootstrap()
// Ensure current symlink points to today's file (best effort)
queue.async { [weak self] in self?.ensureSymlink() }
}
private static func dateString(_ date: Date) -> String {
let fmt = DateFormatter()
fmt.locale = Locale(identifier: "en_US_POSIX")
fmt.dateFormat = "yyyy-MM-dd"
return fmt.string(from: date)
}
private func logDir() -> URL { ProleEnv.logsDir() }
private func datedLogURL(for dateStr: String) -> URL {
return logDir().appendingPathComponent("prole-tools-app-\(dateStr).log")
}
private func currentLogURL() -> URL {
return logDir().appendingPathComponent("prole-tools-app.log")
}
private func ensureSymlink() {
let fm = FileManager.default
let target = datedLogURL(for: currentDateStr)
let link = currentLogURL()
// If link exists and points to target, nothing to do
if let attrs = try? fm.attributesOfItem(atPath: link.path), attrs[.type] as? FileAttributeType == .typeSymbolicLink {
if let dest = try? fm.destinationOfSymbolicLink(atPath: link.path), dest.hasSuffix(target.lastPathComponent) {
return
}
}
// Recreate symlink to today's file
_ = try? fm.removeItem(at: link)
try? fm.createSymbolicLink(at: link, withDestinationURL: target)
}
private func rotateIfNeeded(now: Date = Date()) {
let today = Logger.dateString(now)
if today != currentDateStr {
currentDateStr = today
ensureSymlink()
}
}
func info(_ message: String) { write(level: "INFO", message) }
func error(_ message: String) { write(level: "ERROR", message) }
func write(level: String, _ message: String) {
queue.async {
self.rotateIfNeeded()
let ts = ISO8601DateFormatter().string(from: Date())
let line = "[\(level)] \(ts) \(message)\n"
let fileURL = self.datedLogURL(for: self.currentDateStr)
if let data = line.data(using: .utf8) {
if FileManager.default.fileExists(atPath: fileURL.path) {
if let h = try? FileHandle(forWritingTo: fileURL) {
defer { try? h.close() }
try? h.seekToEnd()
try? h.write(contentsOf: data)
}
} else {
try? data.write(to: fileURL, options: .atomic)
}
}
}
}
}