From d6ca170ed9a8207df61c74ea9f9b2131bdbf071e Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Thu, 24 Jul 2025 09:49:29 +0100 Subject: [PATCH] Update the default logs directory and allow collection from elsewhere. (#4352) * Replace deprecated URL methods. * Start putting log files in Library/Logs (moving existing files with a migration.) * Make Tracing.deleteLogFiles aware of the legacy logs location. * Allow the logs to be collected from a different directory. --- ElementX.xcodeproj/project.pbxproj | 4 +- .../Sources/Application/AppCoordinator.swift | 7 ++- ElementX/Sources/Other/Extensions/URL.swift | 25 +++++---- ElementX/Sources/Other/Logging/Tracing.swift | 54 ++++++++++++++++--- Enterprise | 2 +- UnitTests/Sources/LoggingTests.swift | 2 +- project.yml | 2 +- 7 files changed, 73 insertions(+), 23 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 79686461c..4b4c70dea 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -8623,7 +8623,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 17.6; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 14.6; - MARKETING_VERSION = 25.07.3; + MARKETING_VERSION = 25.07.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCTION_APP_NAME = Element; @@ -8699,7 +8699,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 17.6; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 14.6; - MARKETING_VERSION = 25.07.3; + MARKETING_VERSION = 25.07.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 546ec23af..cefb8fe09 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -401,9 +401,14 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg } if oldVersion < Version(1, 6, 7) { - Tracing.deleteLogFiles() + Tracing.deleteLogFiles(in: Tracing.legacyLogsDirectory) MXLog.info("Migrating to v1.6.7, log files have been wiped") } + + if oldVersion < Version(25, 7, 4) { + Tracing.migrateLogFiles() + MXLog.info("Migrating to version 25.07.4, log files have been moved.") + } } // This could be removed once the adoption of 25.06.x is widespread. diff --git a/ElementX/Sources/Other/Extensions/URL.swift b/ElementX/Sources/Other/Extensions/URL.swift index cff546c3a..e3efe4476 100644 --- a/ElementX/Sources/Other/Extensions/URL.swift +++ b/ElementX/Sources/Other/Extensions/URL.swift @@ -23,10 +23,17 @@ extension URL { return url } + + static var appGroupLogsDirectory: URL { + appGroupContainerDirectory + .appending(component: "Library", directoryHint: .isDirectory) + .appending(component: "Logs", directoryHint: .isDirectory) + .appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory) + } /// The base directory where all session data is stored. static var sessionsBaseDirectory: URL { - let applicationSupportSessionsURL = applicationSupportBaseDirectory.appendingPathComponent("Sessions", isDirectory: true) + let applicationSupportSessionsURL = applicationSupportBaseDirectory.appending(component: "Sessions", directoryHint: .isDirectory) try? FileManager.default.createDirectoryIfNeeded(at: applicationSupportSessionsURL) @@ -36,9 +43,9 @@ extension URL { /// The base directory where all application support data is stored. static var applicationSupportBaseDirectory: URL { var url = appGroupContainerDirectory - .appendingPathComponent("Library", isDirectory: true) - .appendingPathComponent("Application Support", isDirectory: true) - .appendingPathComponent(InfoPlistReader.main.baseBundleIdentifier, isDirectory: true) + .appending(component: "Library", directoryHint: .isDirectory) + .appending(component: "Application Support", directoryHint: .isDirectory) + .appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory) try? FileManager.default.createDirectoryIfNeeded(at: url) @@ -56,10 +63,10 @@ extension URL { /// The base directory where all application support data is stored. static var sessionCachesBaseDirectory: URL { let url = appGroupContainerDirectory - .appendingPathComponent("Library", isDirectory: true) - .appendingPathComponent("Caches", isDirectory: true) - .appendingPathComponent(InfoPlistReader.main.baseBundleIdentifier, isDirectory: true) - .appendingPathComponent("Sessions", isDirectory: true) + .appending(component: "Library", directoryHint: .isDirectory) + .appending(component: "Caches", directoryHint: .isDirectory) + .appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory) + .appending(component: "Sessions", directoryHint: .isDirectory) try? FileManager.default.createDirectoryIfNeeded(at: url) @@ -75,7 +82,7 @@ extension URL { /// Make sure to manually tidy up any files you place in here once you've transferred them from one bundle to another. static var appGroupTemporaryDirectory: URL { let url = appGroupContainerDirectory - .appendingPathComponent("tmp", isDirectory: true) + .appending(component: "tmp", directoryHint: .isDirectory) try? FileManager.default.createDirectoryIfNeeded(at: url) diff --git a/ElementX/Sources/Other/Logging/Tracing.swift b/ElementX/Sources/Other/Logging/Tracing.swift index 63a41d15b..bcd964ec0 100644 --- a/ElementX/Sources/Other/Logging/Tracing.swift +++ b/ElementX/Sources/Other/Logging/Tracing.swift @@ -17,10 +17,16 @@ enum Tracing { if ProcessInfo.isRunningIntegrationTests { "/Users/Shared" } else { - .appGroupContainerDirectory + logsDirectoryOverride ?? .appGroupLogsDirectory } } + /// Set this to temporarily override the directory from which logs will be collected. + /// This basically only affects ``logFiles``, and doesn't inform the SDK to write + /// the logs to a different directory, which should be done before setting this. + static var logsDirectoryOverride: URL? + static var legacyLogsDirectory: URL { .appGroupContainerDirectory } + static let fileExtension = "log" static func buildConfiguration(logLevel: LogLevel, traceLogPacks: Set, @@ -52,13 +58,17 @@ enum Tracing { sentryDsn: sentryURL?.absoluteString) } - /// A list of all log file URLs, sorted chronologically. This is only public for testing purposes, within - /// the app please use ``copyLogs(to:)`` so that the files are name appropriates for QuickLook. - static var logFiles: [URL] { + /// A list of all log file URLs, sorted chronologically. + static var logFiles: [URL] { logFiles(in: logsDirectory) } + + /// Collect all of the logs in the given directory, sorting them chronologically. + private static func logFiles(in directory: URL) -> [URL] { var logFiles = [(url: URL, modificationDate: Date)]() let fileManager = FileManager.default - let enumerator = fileManager.enumerator(at: logsDirectory, includingPropertiesForKeys: [.contentModificationDateKey]) + let enumerator = fileManager.enumerator(at: directory, + includingPropertiesForKeys: [.contentModificationDateKey], + options: .skipsSubdirectoryDescendants) // Find all *.log files and their modification dates. while let logURL = enumerator?.nextObject() as? URL { @@ -78,10 +88,38 @@ enum Tracing { return sortedFiles } - /// Delete all log files. - static func deleteLogFiles() { + static func migrateLogFiles() { + MXLog.info("Moving log files to \(logsDirectory)") let fileManager = FileManager.default - for logFileURL in logFiles { + let oldLogFiles = logFiles(in: legacyLogsDirectory) + + for oldFileURL in oldLogFiles { + do { + let newFileURL = logsDirectory.appending(component: oldFileURL.lastPathComponent) + try fileManager.moveItem(at: oldFileURL, to: newFileURL) + MXLog.info("Moved \(newFileURL.lastPathComponent)") + } catch { + MXLog.error("Failed to move \(oldFileURL.lastPathComponent): \(error.localizedDescription)") + + let nsError = error as NSError + if nsError.domain == NSCocoaErrorDomain, nsError.code == NSFileWriteFileExistsError { + // By now there will already be some logs in the new directory, so there is likely to be + // one log file that cannot be removed. As this is a one-off operation lets just delete it. + MXLog.error("Attempting to delete log file \(oldFileURL.lastPathComponent)") + try? fileManager.removeItem(at: oldFileURL) + } + } + } + } + + /// Delete all log files. + static func deleteLogFiles(in directory: URL) { + let fileManager = FileManager.default + + // We don't simply delete logsDirectory as once upon a time the logs + // we written to the very top-level of the app group container and + // there's a migration in place for old users of the app. + for logFileURL in logFiles(in: directory) { try? fileManager.removeItem(at: logFileURL) } } diff --git a/Enterprise b/Enterprise index 6db7fe89b..7761fe61c 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit 6db7fe89bdf0a4fe8be9278a5290002bb05f93d3 +Subproject commit 7761fe61cac3a2d27de3b62f67b11bfe38ee53ed diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 1b5ea5a4e..eb9b67ce9 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -17,7 +17,7 @@ class LoggingTests: XCTestCase { } override func setUpWithError() throws { - Tracing.deleteLogFiles() + Tracing.deleteLogFiles(in: Tracing.logsDirectory) } func testLogging() async throws { diff --git a/project.yml b/project.yml index c206bc630..90197cfca 100644 --- a/project.yml +++ b/project.yml @@ -48,7 +48,7 @@ settings: ENABLE_BITCODE: false APP_NAME: ElementX KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)" - MARKETING_VERSION: 25.07.3 + MARKETING_VERSION: 25.07.4 CURRENT_PROJECT_VERSION: 1 SUPPORTS_MACCATALYST: false