فهرست منبع

Log app version and build details

Jonas Björkert 1 سال پیش
والد
کامیت
7ea2851510

+ 19 - 1
LoopFollow/Helpers/BuildDetails.swift

@@ -51,7 +51,25 @@ class BuildDetails {
         return "sandboxReceipt".caseInsensitiveCompare(receiptName) == .orderedSame
 #endif
     }
-        
+
+    // Determine if the build is for Simulator
+    func isSimulatorBuild() -> Bool {
+#if targetEnvironment(simulator)
+        return true
+#else
+        return false
+#endif
+    }
+
+    // Determine if the build is for Mac
+    func isMacApp() -> Bool {
+#if targetEnvironment(macCatalyst)
+        return true
+#else
+        return false
+#endif
+    }
+
     // Parse the build date string into a Date object
     func buildDate() -> Date? {
         guard let dateString = dict["com-LoopFollow-build-date"] as? String else {

+ 55 - 6
LoopFollow/Log/LogManager.swift

@@ -15,10 +15,12 @@ class LogManager {
     private let logDirectory: URL
     private let dateFormatter: DateFormatter
     private let consoleQueue = DispatchQueue(label: "com.loopfollow.log.console", qos: .background)
-    
+
     private let rateLimitQueue = DispatchQueue(label: "com.loopfollow.log.ratelimit")
     private var lastLoggedTimestamps: [String: Date] = [:]
 
+    private var shouldLogVersionHeader: Bool = true
+
     enum Category: String, CaseIterable {
         case bluetooth = "Bluetooth"
         case nightscout = "Nightscout"
@@ -41,7 +43,12 @@ class LogManager {
         dateFormatter = DateFormatter()
         dateFormatter.dateFormat = "yyyy-MM-dd"
     }
-    
+
+    private func formattedLogMessage(for category: Category, message: String) -> String {
+        let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
+        return "[\(timestamp)] [\(category.rawValue)] \(message)"
+    }
+
     /// Logs a message with an optional rate limit.
     ///
     /// - Parameters:
@@ -51,13 +58,12 @@ class LogManager {
     ///   - limitIdentifier: Optional key to rate-limit similar log messages.
     ///   - limitInterval: Time interval (in seconds) to wait before logging the same type again.
     func log(category: Category, message: String, isDebug: Bool = false, limitIdentifier: String? = nil, limitInterval: TimeInterval = 300) {
-        let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
-        let logMessage = "[\(timestamp)] [\(category.rawValue)] \(message)"
+        let logMessage = formattedLogMessage(for: category, message: message)
 
         consoleQueue.async {
             print(logMessage)
         }
-        
+
         if let key = limitIdentifier, !Storage.shared.debugLogLevel.value {
             let shouldLog: Bool = rateLimitQueue.sync {
                 if let lastLogged = lastLoggedTimestamps[key] {
@@ -76,10 +82,53 @@ class LogManager {
 
         if !isDebug || Storage.shared.debugLogLevel.value {
             let logFileURL = self.currentLogFileURL
+            self.writeVersionHeaderIfNeeded(for: logFileURL)
             self.append(logMessage + "\n", to: logFileURL)
         }
     }
 
+    /// Helper method: checks if the log file is empty.
+    private func isLogFileEmpty(at fileURL: URL) -> Bool {
+        if !fileManager.fileExists(atPath: fileURL.path) { return true }
+        if let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path),
+           let fileSize = attributes[.size] as? UInt64 {
+            return fileSize == 0
+        }
+        return false
+    }
+
+    /// Helper method: writes the version header if needed.
+    private func writeVersionHeaderIfNeeded(for fileURL: URL) {
+        if shouldLogVersionHeader || isLogFileEmpty(at: fileURL) {
+            let versionManager = AppVersionManager()
+            let version = versionManager.version()
+
+            // Retrieve build details
+            let buildDetails = BuildDetails.default
+            let formattedBuildDate = dateTimeUtils.formattedDate(from: buildDetails.buildDate())
+            let branchAndSha = buildDetails.branchAndSha
+            let expiration = dateTimeUtils.formattedDate(from: buildDetails.calculateExpirationDate())
+            let expirationHeaderString = buildDetails.expirationHeaderString
+            let isMacApp = buildDetails.isMacApp()
+            let isSimulatorBuild = buildDetails.isSimulatorBuild()
+
+            // Assemble header information
+            var headerLines = [String]()
+            headerLines.append("LoopFollow Version: \(version)")
+            if !isMacApp && !isSimulatorBuild {
+                headerLines.append("\(expirationHeaderString): \(expiration)")
+            }
+            headerLines.append("Built: \(formattedBuildDate)")
+            headerLines.append("Branch: \(branchAndSha)")
+
+            let headerMessage = headerLines.joined(separator: ", ") + "\n"
+            let logMessage = formattedLogMessage(for: .general, message: headerMessage)
+
+            self.append(logMessage, to: fileURL)
+            shouldLogVersionHeader = false
+        }
+    }
+
     func cleanupOldLogs() {
         let today = dateFormatter.string(from: Date())
         let yesterday = dateFormatter.string(from: Calendar.current.date(byAdding: .day, value: -1, to: Date())!)
@@ -110,7 +159,7 @@ class LogManager {
     var currentLogFileURL: URL {
         return logFileURL(for: Date())
     }
-    
+
     private func append(_ message: String, to fileURL: URL) {
         if !fileManager.fileExists(atPath: fileURL.path) {
             fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil)

+ 3 - 9
LoopFollow/ViewControllers/SettingsViewController.swift

@@ -53,6 +53,8 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
         let expirationHeaderString = buildDetails.expirationHeaderString
         let versionManager = AppVersionManager()
         let version = versionManager.version()
+        let isMacApp = buildDetails.isMacApp()
+        let isSimulatorBuild = buildDetails.isSimulatorBuild()
 
         form
         +++ Section(header: "Data Settings", footer: "")
@@ -210,7 +212,7 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
         <<< LabelRow() {
             $0.title = expirationHeaderString
             $0.value = expiration
-            $0.hidden = Condition(booleanLiteral: isMacApp())
+            $0.hidden = Condition(booleanLiteral: isMacApp || isSimulatorBuild)
         }
         <<< LabelRow() {
             $0.title = "Built"
@@ -259,14 +261,6 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
         }
     }
 
-    func isMacApp() -> Bool {
-#if targetEnvironment(macCatalyst)
-        return true
-#else
-        return false
-#endif
-    }
-
     func presentInfoDisplaySettings() {
         let viewModel = InfoDisplaySettingsViewModel()
         let settingsView = InfoDisplaySettingsView(viewModel: viewModel)