Skip to content

Commit e82d9a4

Browse files
authored
Merge pull request #389 from loopandlearn/additional-logging
Log app version and build details
2 parents c70256e + 7ea2851 commit e82d9a4

File tree

3 files changed

+77
-16
lines changed

3 files changed

+77
-16
lines changed

LoopFollow/Helpers/BuildDetails.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,25 @@ class BuildDetails {
5151
return "sandboxReceipt".caseInsensitiveCompare(receiptName) == .orderedSame
5252
#endif
5353
}
54-
54+
55+
// Determine if the build is for Simulator
56+
func isSimulatorBuild() -> Bool {
57+
#if targetEnvironment(simulator)
58+
return true
59+
#else
60+
return false
61+
#endif
62+
}
63+
64+
// Determine if the build is for Mac
65+
func isMacApp() -> Bool {
66+
#if targetEnvironment(macCatalyst)
67+
return true
68+
#else
69+
return false
70+
#endif
71+
}
72+
5573
// Parse the build date string into a Date object
5674
func buildDate() -> Date? {
5775
guard let dateString = dict["com-LoopFollow-build-date"] as? String else {

LoopFollow/Log/LogManager.swift

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

LoopFollow/ViewControllers/SettingsViewController.swift

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
5353
let expirationHeaderString = buildDetails.expirationHeaderString
5454
let versionManager = AppVersionManager()
5555
let version = versionManager.version()
56+
let isMacApp = buildDetails.isMacApp()
57+
let isSimulatorBuild = buildDetails.isSimulatorBuild()
5658

5759
form
5860
+++ Section(header: "Data Settings", footer: "")
@@ -210,7 +212,7 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
210212
<<< LabelRow() {
211213
$0.title = expirationHeaderString
212214
$0.value = expiration
213-
$0.hidden = Condition(booleanLiteral: isMacApp())
215+
$0.hidden = Condition(booleanLiteral: isMacApp || isSimulatorBuild)
214216
}
215217
<<< LabelRow() {
216218
$0.title = "Built"
@@ -259,14 +261,6 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
259261
}
260262
}
261263

262-
func isMacApp() -> Bool {
263-
#if targetEnvironment(macCatalyst)
264-
return true
265-
#else
266-
return false
267-
#endif
268-
}
269-
270264
func presentInfoDisplaySettings() {
271265
let viewModel = InfoDisplaySettingsViewModel()
272266
let settingsView = InfoDisplaySettingsView(viewModel: viewModel)

0 commit comments

Comments
 (0)