LogManager.swift 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. //
  2. // LogManager.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2025-01-10.
  6. // Copyright © 2025 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. class LogManager {
  10. static let shared = LogManager()
  11. private let fileManager = FileManager.default
  12. private let logDirectory: URL
  13. private let dateFormatter: DateFormatter
  14. private let consoleQueue = DispatchQueue(label: "com.loopfollow.log.console", qos: .background)
  15. enum Category: String, CaseIterable {
  16. case bluetooth = "Bluetooth"
  17. case nightscout = "Nightscout"
  18. case apns = "APNS"
  19. case general = "General"
  20. case contact = "Contact"
  21. case taskScheduler = "Task Scheduler"
  22. case dexcom = "Dexcom"
  23. case alarm = "Alarm"
  24. case calendar = "Calendar"
  25. }
  26. init() {
  27. // Create log directory in the app's Documents folder
  28. let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
  29. logDirectory = documentsDirectory.appendingPathComponent("Logs")
  30. // Ensure the directory exists
  31. if !fileManager.fileExists(atPath: logDirectory.path) {
  32. try? fileManager.createDirectory(at: logDirectory, withIntermediateDirectories: true, attributes: nil)
  33. }
  34. dateFormatter = DateFormatter()
  35. dateFormatter.dateFormat = "yyyy-MM-dd"
  36. }
  37. func log(category: Category, message: String, isDebug: Bool = false) {
  38. let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
  39. let logMessage = "[\(timestamp)] [\(category.rawValue)] \(message)"
  40. consoleQueue.async {
  41. print(logMessage)
  42. }
  43. if !isDebug || Storage.shared.debugLogLevel.value {
  44. let logFileURL = self.currentLogFileURL
  45. self.append(logMessage + "\n", to: logFileURL)
  46. }
  47. }
  48. func cleanupOldLogs() {
  49. let today = dateFormatter.string(from: Date())
  50. let yesterday = dateFormatter.string(from: Calendar.current.date(byAdding: .day, value: -1, to: Date())!)
  51. do {
  52. let logFiles = try fileManager.contentsOfDirectory(at: logDirectory, includingPropertiesForKeys: nil)
  53. for logFile in logFiles {
  54. let filename = logFile.lastPathComponent
  55. if !filename.contains(today) && !filename.contains(yesterday) {
  56. try fileManager.removeItem(at: logFile)
  57. }
  58. }
  59. } catch {
  60. print("Failed to clean up old logs: \(error)")
  61. }
  62. }
  63. var currentLogFileURL: URL {
  64. let today = dateFormatter.string(from: Date())
  65. return logDirectory.appendingPathComponent("LoopFollow \(today).log")
  66. }
  67. private func append(_ message: String, to fileURL: URL) {
  68. if !fileManager.fileExists(atPath: fileURL.path) {
  69. fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil)
  70. }
  71. if let fileHandle = try? FileHandle(forWritingTo: fileURL) {
  72. defer { fileHandle.closeFile() }
  73. fileHandle.seekToEndOfFile()
  74. if let data = message.data(using: .utf8) {
  75. fileHandle.write(data)
  76. }
  77. } else {
  78. print("Failed to open log file at \(fileURL.path)")
  79. }
  80. }
  81. }