Logging overhaul

This commit is contained in:
2025-11-18 12:58:45 -07:00
parent c2f95f2793
commit 6d67ae6d81
25 changed files with 1428 additions and 1205 deletions

View File

@@ -12,10 +12,27 @@ class SyncService: ObservableObject {
@Published var isOfflineMode = false
private let userDefaults = UserDefaults.standard
private let logTag = "SyncService"
private var syncTask: Task<Void, Never>?
private var pendingChanges = false
private let syncDebounceDelay: TimeInterval = 2.0
private func logDebug(_ message: @autoclosure () -> String) {
AppLogger.debug(message(), tag: logTag)
}
private func logInfo(_ message: @autoclosure () -> String) {
AppLogger.info(message(), tag: logTag)
}
private func logWarning(_ message: @autoclosure () -> String) {
AppLogger.warning(message(), tag: logTag)
}
private func logError(_ message: @autoclosure () -> String) {
AppLogger.error(message(), tag: logTag)
}
private enum Keys {
static let serverURL = "sync_server_url"
static let authToken = "sync_auth_token"
@@ -201,7 +218,7 @@ class SyncService: ObservableObject {
return false
}
print(
logInfo(
"iOS DELTA SYNC: Sending gyms=\(modifiedGyms.count), problems=\(modifiedProblems.count), sessions=\(modifiedSessions.count), attempts=\(modifiedAttempts.count), deletions=\(modifiedDeletions.count)"
)
@@ -244,7 +261,7 @@ class SyncService: ObservableObject {
let decoder = JSONDecoder()
let deltaResponse = try decoder.decode(DeltaSyncResponse.self, from: data)
print(
logInfo(
"iOS DELTA SYNC: Received gyms=\(deltaResponse.gyms.count), problems=\(deltaResponse.problems.count), sessions=\(deltaResponse.sessions.count), attempts=\(deltaResponse.attempts.count), deletions=\(deltaResponse.deletedItems.count)"
)
@@ -270,7 +287,7 @@ class SyncService: ObservableObject {
let allDeletions = dataManager.getDeletedItems() + response.deletedItems
let uniqueDeletions = Array(Set(allDeletions))
print(
logInfo(
"iOS DELTA SYNC: Applying \(uniqueDeletions.count) deletion records before merging data"
)
applyDeletionsToDataManager(deletions: uniqueDeletions, dataManager: dataManager)
@@ -298,10 +315,10 @@ class SyncService: ObservableObject {
_ = try imageManager.saveImportedImage(imageData, filename: consistentFilename)
imagePathMapping[serverFilename] = consistentFilename
} catch SyncError.imageNotFound {
print("Image not found on server: \(serverFilename)")
logInfo("Image not found on server: \(serverFilename)")
continue
} catch {
print("Failed to download image \(serverFilename): \(error)")
logInfo("Failed to download image \(serverFilename): \(error)")
continue
}
}
@@ -436,7 +453,7 @@ class SyncService: ObservableObject {
) async throws {
guard !modifiedProblems.isEmpty else { return }
print("iOS DELTA SYNC: Syncing images for \(modifiedProblems.count) modified problems")
logInfo("iOS DELTA SYNC: Syncing images for \(modifiedProblems.count) modified problems")
for backupProblem in modifiedProblems {
guard
@@ -465,9 +482,9 @@ class SyncService: ObservableObject {
}
try await uploadImage(filename: consistentFilename, imageData: imageData)
print("Uploaded modified problem image: \(consistentFilename)")
logInfo("Uploaded modified problem image: \(consistentFilename)")
} catch {
print("Failed to upload image \(consistentFilename): \(error)")
logInfo("Failed to upload image \(consistentFilename): \(error)")
}
}
}
@@ -549,7 +566,7 @@ class SyncService: ObservableObject {
func syncWithServer(dataManager: ClimbingDataManager) async throws {
if isOfflineMode {
print("Sync skipped: Offline mode is enabled.")
logInfo("Sync skipped: Offline mode is enabled.")
return
}
@@ -586,7 +603,7 @@ class SyncService: ObservableObject {
// If both client and server have been synced before, use delta sync
if hasLocalData && hasServerData && lastSyncTime != nil {
print("iOS SYNC: Using delta sync for incremental updates")
logInfo("iOS SYNC: Using delta sync for incremental updates")
try await performDeltaSync(dataManager: dataManager)
// Update last sync time
@@ -597,32 +614,32 @@ class SyncService: ObservableObject {
if !hasLocalData && hasServerData {
// Case 1: No local data - do full restore from server
print("iOS SYNC: Case 1 - No local data, performing full restore from server")
print("Syncing images from server first...")
logInfo("iOS SYNC: Case 1 - No local data, performing full restore from server")
logInfo("Syncing images from server first...")
let imagePathMapping = try await syncImagesFromServer(
backup: serverBackup, dataManager: dataManager)
print("Importing data after images...")
logInfo("Importing data after images...")
try importBackupToDataManager(
serverBackup, dataManager: dataManager, imagePathMapping: imagePathMapping)
print("Full restore completed")
logInfo("Full restore completed")
} else if hasLocalData && !hasServerData {
// Case 2: No server data - upload local data to server
print("iOS SYNC: Case 2 - No server data, uploading local data to server")
logInfo("iOS SYNC: Case 2 - No server data, uploading local data to server")
let currentBackup = createBackupFromDataManager(dataManager)
_ = try await uploadData(currentBackup)
print("Uploading local images to server...")
logInfo("Uploading local images to server...")
try await syncImagesToServer(dataManager: dataManager)
print("Initial upload completed")
logInfo("Initial upload completed")
} else if hasLocalData && hasServerData {
// Case 3: Both have data - use safe merge strategy
print("iOS SYNC: Case 3 - Merging local and server data safely")
logInfo("iOS SYNC: Case 3 - Merging local and server data safely")
try await mergeDataSafely(
localBackup: localBackup,
serverBackup: serverBackup,
dataManager: dataManager)
print("Safe merge completed")
logInfo("Safe merge completed")
} else {
print("No data to sync")
logInfo("No data to sync")
}
// Update last sync time
@@ -640,7 +657,7 @@ class SyncService: ObservableObject {
if let date = formatter.date(from: timestamp) {
return Int64(date.timeIntervalSince1970 * 1000)
}
print("Failed to parse timestamp: \(timestamp), using 0")
logInfo("Failed to parse timestamp: \(timestamp), using 0")
return 0
}
@@ -666,12 +683,12 @@ class SyncService: ObservableObject {
imageData, filename: consistentFilename)
imagePathMapping[serverFilename] = consistentFilename
print("Downloaded and mapped image: \(serverFilename) -> \(consistentFilename)")
logInfo("Downloaded and mapped image: \(serverFilename) -> \(consistentFilename)")
} catch SyncError.imageNotFound {
print("Image not found on server: \(serverFilename)")
logInfo("Image not found on server: \(serverFilename)")
continue
} catch {
print("Failed to download image \(serverFilename): \(error)")
logInfo("Failed to download image \(serverFilename): \(error)")
continue
}
}
@@ -704,18 +721,18 @@ class SyncService: ObservableObject {
).path
do {
try FileManager.default.moveItem(atPath: fullPath, toPath: newPath)
print("Renamed local image: \(filename) -> \(consistentFilename)")
logInfo("Renamed local image: \(filename) -> \(consistentFilename)")
// Update problem's image path in memory for consistency
} catch {
print("Failed to rename local image, using original: \(error)")
logInfo("Failed to rename local image, using original: \(error)")
}
}
try await uploadImage(filename: consistentFilename, imageData: imageData)
print("Successfully uploaded image: \(consistentFilename)")
logInfo("Successfully uploaded image: \(consistentFilename)")
} catch {
print("Failed to upload image \(consistentFilename): \(error)")
logInfo("Failed to upload image \(consistentFilename): \(error)")
// Continue with other images even if one fails
}
}
@@ -733,7 +750,7 @@ class SyncService: ObservableObject {
!activeSessionIds.contains($0.sessionId)
}
print(
logInfo(
"iOS SYNC: Excluding \(dataManager.sessions.count - completedSessions.count) active sessions and \(dataManager.attempts.count - completedAttempts.count) active session attempts from sync"
)
@@ -808,26 +825,26 @@ class SyncService: ObservableObject {
let allDeletions = localDeletions + serverBackup.deletedItems
let uniqueDeletions = Array(Set(allDeletions))
print("Merging gyms...")
logInfo("Merging gyms...")
let mergedGyms = mergeGyms(
local: dataManager.gyms,
server: serverBackup.gyms,
deletedItems: uniqueDeletions)
print("Merging problems...")
logInfo("Merging problems...")
let mergedProblems = try mergeProblems(
local: dataManager.problems,
server: serverBackup.problems,
imagePathMapping: imagePathMapping,
deletedItems: uniqueDeletions)
print("Merging sessions...")
logInfo("Merging sessions...")
let mergedSessions = try mergeSessions(
local: dataManager.sessions,
server: serverBackup.sessions,
deletedItems: uniqueDeletions)
print("Merging attempts...")
logInfo("Merging attempts...")
let mergedAttempts = try mergeAttempts(
local: dataManager.attempts,
server: serverBackup.attempts,
@@ -887,7 +904,7 @@ class SyncService: ObservableObject {
&& !allDeletedAttemptIds.contains($0.id.uuidString)
}
print(
logInfo(
"iOS IMPORT: Preserving \(activeSessions.count) active sessions and \(activeAttempts.count) active attempts during import"
)
@@ -977,7 +994,7 @@ class SyncService: ObservableObject {
// Restore active sessions and their attempts after import
for session in activeSessions {
print("iOS IMPORT: Restoring active session: \(session.id)")
logInfo("iOS IMPORT: Restoring active session: \(session.id)")
dataManager.sessions.append(session)
if session.id == dataManager.activeSession?.id {
dataManager.activeSession = session
@@ -997,12 +1014,12 @@ class SyncService: ObservableObject {
dataManager.clearDeletedItems()
if let data = try? JSONEncoder().encode(backup.deletedItems) {
UserDefaults.standard.set(data, forKey: "ascently_deleted_items")
print("iOS IMPORT: Imported \(backup.deletedItems.count) deletion records")
logInfo("iOS IMPORT: Imported \(backup.deletedItems.count) deletion records")
}
// Update local data state to match imported data timestamp
DataStateManager.shared.setLastModified(backup.exportedAt)
print("Data state synchronized to imported timestamp: \(backup.exportedAt)")
logInfo("Data state synchronized to imported timestamp: \(backup.exportedAt)")
} catch {
throw SyncError.importFailed(error)