@@ -15,10 +15,12 @@ class LogManager {
1515 private let logDirectory : URL
1616 private let dateFormatter : DateFormatter
1717 private let consoleQueue = DispatchQueue ( label: " com.loopfollow.log.console " , qos: . background)
18-
18+
1919 private let rateLimitQueue = DispatchQueue ( label: " com.loopfollow.log.ratelimit " )
2020 private var lastLoggedTimestamps : [ String : Date ] = [ : ]
2121
22+ private var shouldLogVersionHeader : Bool = true
23+
2224 enum Category : String , CaseIterable {
2325 case bluetooth = " Bluetooth "
2426 case nightscout = " Nightscout "
@@ -41,7 +43,12 @@ class LogManager {
4143 dateFormatter = DateFormatter ( )
4244 dateFormatter. dateFormat = " yyyy-MM-dd "
4345 }
44-
46+
47+ private func formattedLogMessage( for category: Category , message: String ) -> String {
48+ let timestamp = DateFormatter . localizedString ( from: Date ( ) , dateStyle: . none, timeStyle: . medium)
49+ return " [ \( timestamp) ] [ \( category. rawValue) ] \( message) "
50+ }
51+
4552 /// Logs a message with an optional rate limit.
4653 ///
4754 /// - Parameters:
@@ -51,13 +58,12 @@ class LogManager {
5158 /// - limitIdentifier: Optional key to rate-limit similar log messages.
5259 /// - limitInterval: Time interval (in seconds) to wait before logging the same type again.
5360 func log( category: Category , message: String , isDebug: Bool = false , limitIdentifier: String ? = nil , limitInterval: TimeInterval = 300 ) {
54- let timestamp = DateFormatter . localizedString ( from: Date ( ) , dateStyle: . none, timeStyle: . medium)
55- let logMessage = " [ \( timestamp) ] [ \( category. rawValue) ] \( message) "
61+ let logMessage = formattedLogMessage ( for: category, message: message)
5662
5763 consoleQueue. async {
5864 print ( logMessage)
5965 }
60-
66+
6167 if let key = limitIdentifier, !Storage. shared. debugLogLevel. value {
6268 let shouldLog : Bool = rateLimitQueue. sync {
6369 if let lastLogged = lastLoggedTimestamps [ key] {
@@ -76,10 +82,53 @@ class LogManager {
7682
7783 if !isDebug || Storage . shared. debugLogLevel. value {
7884 let logFileURL = self . currentLogFileURL
85+ self . writeVersionHeaderIfNeeded ( for: logFileURL)
7986 self . append ( logMessage + " \n " , to: logFileURL)
8087 }
8188 }
8289
90+ /// Helper method: checks if the log file is empty.
91+ private func isLogFileEmpty( at fileURL: URL ) -> Bool {
92+ if !fileManager. fileExists ( atPath: fileURL. path) { return true }
93+ if let attributes = try ? fileManager. attributesOfItem ( atPath: fileURL. path) ,
94+ let fileSize = attributes [ . size] as? UInt64 {
95+ return fileSize == 0
96+ }
97+ return false
98+ }
99+
100+ /// Helper method: writes the version header if needed.
101+ private func writeVersionHeaderIfNeeded( for fileURL: URL ) {
102+ if shouldLogVersionHeader || isLogFileEmpty ( at: fileURL) {
103+ let versionManager = AppVersionManager ( )
104+ let version = versionManager. version ( )
105+
106+ // Retrieve build details
107+ let buildDetails = BuildDetails . default
108+ let formattedBuildDate = dateTimeUtils. formattedDate ( from: buildDetails. buildDate ( ) )
109+ let branchAndSha = buildDetails. branchAndSha
110+ let expiration = dateTimeUtils. formattedDate ( from: buildDetails. calculateExpirationDate ( ) )
111+ let expirationHeaderString = buildDetails. expirationHeaderString
112+ let isMacApp = buildDetails. isMacApp ( )
113+ let isSimulatorBuild = buildDetails. isSimulatorBuild ( )
114+
115+ // Assemble header information
116+ var headerLines = [ String] ( )
117+ headerLines. append ( " LoopFollow Version: \( version) " )
118+ if !isMacApp && !isSimulatorBuild {
119+ headerLines. append ( " \( expirationHeaderString) : \( expiration) " )
120+ }
121+ headerLines. append ( " Built: \( formattedBuildDate) " )
122+ headerLines. append ( " Branch: \( branchAndSha) " )
123+
124+ let headerMessage = headerLines. joined ( separator: " , " ) + " \n "
125+ let logMessage = formattedLogMessage ( for: . general, message: headerMessage)
126+
127+ self . append ( logMessage, to: fileURL)
128+ shouldLogVersionHeader = false
129+ }
130+ }
131+
83132 func cleanupOldLogs( ) {
84133 let today = dateFormatter. string ( from: Date ( ) )
85134 let yesterday = dateFormatter. string ( from: Calendar . current. date ( byAdding: . day, value: - 1 , to: Date ( ) ) !)
@@ -110,7 +159,7 @@ class LogManager {
110159 var currentLogFileURL : URL {
111160 return logFileURL ( for: Date ( ) )
112161 }
113-
162+
114163 private func append( _ message: String , to fileURL: URL ) {
115164 if !fileManager. fileExists ( atPath: fileURL. path) {
116165 fileManager. createFile ( atPath: fileURL. path, contents: nil , attributes: nil )
0 commit comments