86 lines
3.1 KiB
Swift
86 lines
3.1 KiB
Swift
import Foundation
|
|
import os.log
|
|
|
|
/// SPEC §13: os_log subsystem `com.iccery.TargetPrint`, optional rotating file under
|
|
/// `~/Library/Logs/TargetPrint/` when launched with `--verbose`.
|
|
enum TPLog {
|
|
static let subsystem = "com.iccery.TargetPrint"
|
|
static let os = OSLog(subsystem: subsystem, category: "engine")
|
|
|
|
private static var verbose = false
|
|
private static var fileHandle: FileHandle?
|
|
private static let queue = DispatchQueue(label: "com.iccery.TargetPrint.log")
|
|
|
|
static func setVerbose(_ on: Bool) {
|
|
verbose = on
|
|
if on { openFileIfNeeded() }
|
|
}
|
|
|
|
static var isVerbose: Bool { verbose }
|
|
|
|
static func debug(_ message: String) {
|
|
os_log("%{public}@", log: os, type: .debug, message)
|
|
writeFile("DEBUG", message)
|
|
}
|
|
|
|
static func info(_ message: String) {
|
|
os_log("%{public}@", log: os, type: .info, message)
|
|
writeFile("INFO", message)
|
|
}
|
|
|
|
static func error(_ message: String) {
|
|
os_log("%{public}@", log: os, type: .error, message)
|
|
writeFile("ERROR", message)
|
|
}
|
|
|
|
static func fault(_ message: String) {
|
|
os_log("%{public}@", log: os, type: .fault, message)
|
|
writeFile("FAULT", message)
|
|
}
|
|
|
|
private static func openFileIfNeeded() {
|
|
queue.sync {
|
|
guard fileHandle == nil else { return }
|
|
let dir = FileManager.default.homeDirectoryForCurrentUser
|
|
.appendingPathComponent("Library/Logs/TargetPrint", isDirectory: true)
|
|
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
let url = dir.appendingPathComponent("TargetPrint.log")
|
|
if !FileManager.default.fileExists(atPath: url.path) {
|
|
FileManager.default.createFile(atPath: url.path, contents: nil)
|
|
}
|
|
rotateIfNeeded(url)
|
|
fileHandle = try? FileHandle(forWritingTo: url)
|
|
fileHandle?.seekToEndOfFile()
|
|
}
|
|
}
|
|
|
|
private static func rotateIfNeeded(_ url: URL) {
|
|
let maxBytes: UInt64 = 2 * 1024 * 1024
|
|
guard let attrs = try? FileManager.default.attributesOfItem(atPath: url.path),
|
|
let size = attrs[.size] as? UInt64,
|
|
size > maxBytes else { return }
|
|
let bak = url.deletingPathExtension().appendingPathExtension("log.1")
|
|
try? FileManager.default.removeItem(at: bak)
|
|
try? FileManager.default.moveItem(at: url, to: bak)
|
|
FileManager.default.createFile(atPath: url.path, contents: nil)
|
|
}
|
|
|
|
private static func writeFile(_ level: String, _ message: String) {
|
|
guard verbose else { return }
|
|
queue.async {
|
|
let line = "\(isoNow()) [\(level)] \(message)\n"
|
|
if let data = line.data(using: .utf8) {
|
|
fileHandle?.write(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func isoNow() -> String {
|
|
let f = DateFormatter()
|
|
f.locale = Locale(identifier: "en_US_POSIX")
|
|
f.timeZone = TimeZone(secondsFromGMT: 0)
|
|
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
|
|
return f.string(from: Date())
|
|
}
|
|
}
|