Sora/Sora/Utlis & Misc/Logger/Logger.swift
2025-06-25 10:33:42 +02:00

154 lines
5.1 KiB
Swift

//
// Logging.swift
// Sora
//
// Created by seiike on 16/01/2025.
//
import Foundation
class Logger {
static let shared = Logger()
struct LogEntry {
let message: String
let type: String
let timestamp: Date
}
private let queue = DispatchQueue(label: "me.cranci.sora.logger", attributes: .concurrent)
private var logs: [LogEntry] = []
private let logFileURL: URL
private let logFilterViewModel = LogFilterViewModel.shared
private let maxFileSize = 1024 * 512
private let maxLogEntries = 1000
private init() {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
logFileURL = documentDirectory.appendingPathComponent("logs.txt")
}
func log(_ message: String, type: String = "General") {
guard logFilterViewModel.isFilterEnabled(for: type) else { return }
let entry = LogEntry(message: message, type: type, timestamp: Date())
queue.async(flags: .barrier) {
self.logs.append(entry)
if self.logs.count > self.maxLogEntries {
self.logs.removeFirst(self.logs.count - self.maxLogEntries)
}
self.saveLogToFile(entry)
self.debugLog(entry)
}
}
func getLogs() -> String {
var result = ""
queue.sync {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM HH:mm:ss"
result = logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.type)] \($0.message)" }
.joined(separator: "\n----\n")
}
return result
}
func getLogsAsync() async -> String {
return await withCheckedContinuation { continuation in
queue.async {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM HH:mm:ss"
let result = self.logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.type)] \($0.message)" }
.joined(separator: "\n----\n")
continuation.resume(returning: result)
}
}
}
func clearLogs() {
queue.async(flags: .barrier) {
self.logs.removeAll()
try? FileManager.default.removeItem(at: self.logFileURL)
}
}
func clearLogsAsync() async {
await withCheckedContinuation { continuation in
queue.async(flags: .barrier) {
self.logs.removeAll()
try? FileManager.default.removeItem(at: self.logFileURL)
continuation.resume()
}
}
}
private func saveLogToFile(_ log: LogEntry) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM HH:mm:ss"
let logString = "[\(dateFormatter.string(from: log.timestamp))] [\(log.type)] \(log.message)\n---\n"
guard let data = logString.data(using: .utf8) else {
print("Failed to encode log string to UTF-8")
return
}
do {
if FileManager.default.fileExists(atPath: logFileURL.path) {
let attributes = try FileManager.default.attributesOfItem(atPath: logFileURL.path)
let fileSize = attributes[.size] as? UInt64 ?? 0
if fileSize + UInt64(data.count) > maxFileSize {
self.truncateLogFile()
}
if let handle = try? FileHandle(forWritingTo: logFileURL) {
handle.seekToEndOfFile()
handle.write(data)
handle.closeFile()
}
} else {
try data.write(to: logFileURL)
}
} catch {
print("Error managing log file: \(error)")
try? data.write(to: logFileURL)
}
}
private func truncateLogFile() {
do {
guard let content = try? String(contentsOf: logFileURL, encoding: .utf8),
!content.isEmpty else {
return
}
let entries = content.components(separatedBy: "\n---\n")
guard entries.count > 10 else { return }
let keepCount = entries.count / 2
let truncatedEntries = Array(entries.suffix(keepCount))
let truncatedContent = truncatedEntries.joined(separator: "\n---\n")
if let truncatedData = truncatedContent.data(using: .utf8) {
try truncatedData.write(to: logFileURL)
}
} catch {
print("Error truncating log file: \(error)")
try? FileManager.default.removeItem(at: logFileURL)
}
}
private func debugLog(_ entry: LogEntry) {
#if DEBUG
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM HH:mm:ss"
let formattedMessage = "[\(dateFormatter.string(from: entry.timestamp))] [\(entry.type)] \(entry.message)"
print(formattedMessage)
#endif
}
}