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.
This commit is contained in:
Doug
2025-07-24 09:49:29 +01:00
committed by GitHub
parent 76e83462a8
commit d6f7607538
7 changed files with 73 additions and 23 deletions
+2 -2
View File
@@ -8623,7 +8623,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 14.6; MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 25.07.3; MARKETING_VERSION = 25.07.4;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCTION_APP_NAME = Element; PRODUCTION_APP_NAME = Element;
@@ -8699,7 +8699,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)";
MACOSX_DEPLOYMENT_TARGET = 14.6; MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 25.07.3; MARKETING_VERSION = 25.07.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
@@ -401,9 +401,14 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
} }
if oldVersion < Version(1, 6, 7) { 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") 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. // This could be removed once the adoption of 25.06.x is widespread.
+16 -9
View File
@@ -24,9 +24,16 @@ extension URL {
return 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. /// The base directory where all session data is stored.
static var sessionsBaseDirectory: URL { 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) try? FileManager.default.createDirectoryIfNeeded(at: applicationSupportSessionsURL)
@@ -36,9 +43,9 @@ extension URL {
/// The base directory where all application support data is stored. /// The base directory where all application support data is stored.
static var applicationSupportBaseDirectory: URL { static var applicationSupportBaseDirectory: URL {
var url = appGroupContainerDirectory var url = appGroupContainerDirectory
.appendingPathComponent("Library", isDirectory: true) .appending(component: "Library", directoryHint: .isDirectory)
.appendingPathComponent("Application Support", isDirectory: true) .appending(component: "Application Support", directoryHint: .isDirectory)
.appendingPathComponent(InfoPlistReader.main.baseBundleIdentifier, isDirectory: true) .appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory)
try? FileManager.default.createDirectoryIfNeeded(at: url) try? FileManager.default.createDirectoryIfNeeded(at: url)
@@ -56,10 +63,10 @@ extension URL {
/// The base directory where all application support data is stored. /// The base directory where all application support data is stored.
static var sessionCachesBaseDirectory: URL { static var sessionCachesBaseDirectory: URL {
let url = appGroupContainerDirectory let url = appGroupContainerDirectory
.appendingPathComponent("Library", isDirectory: true) .appending(component: "Library", directoryHint: .isDirectory)
.appendingPathComponent("Caches", isDirectory: true) .appending(component: "Caches", directoryHint: .isDirectory)
.appendingPathComponent(InfoPlistReader.main.baseBundleIdentifier, isDirectory: true) .appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory)
.appendingPathComponent("Sessions", isDirectory: true) .appending(component: "Sessions", directoryHint: .isDirectory)
try? FileManager.default.createDirectoryIfNeeded(at: url) 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. /// 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 { static var appGroupTemporaryDirectory: URL {
let url = appGroupContainerDirectory let url = appGroupContainerDirectory
.appendingPathComponent("tmp", isDirectory: true) .appending(component: "tmp", directoryHint: .isDirectory)
try? FileManager.default.createDirectoryIfNeeded(at: url) try? FileManager.default.createDirectoryIfNeeded(at: url)
+46 -8
View File
@@ -17,10 +17,16 @@ enum Tracing {
if ProcessInfo.isRunningIntegrationTests { if ProcessInfo.isRunningIntegrationTests {
"/Users/Shared" "/Users/Shared"
} else { } 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 let fileExtension = "log"
static func buildConfiguration(logLevel: LogLevel, traceLogPacks: Set<TraceLogPack>, static func buildConfiguration(logLevel: LogLevel, traceLogPacks: Set<TraceLogPack>,
@@ -52,13 +58,17 @@ enum Tracing {
sentryDsn: sentryURL?.absoluteString) sentryDsn: sentryURL?.absoluteString)
} }
/// A list of all log file URLs, sorted chronologically. This is only public for testing purposes, within /// A list of all log file URLs, sorted chronologically.
/// the app please use ``copyLogs(to:)`` so that the files are name appropriates for QuickLook. static var logFiles: [URL] { logFiles(in: logsDirectory) }
static var logFiles: [URL] {
/// 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)]() var logFiles = [(url: URL, modificationDate: Date)]()
let fileManager = FileManager.default 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. // Find all *.log files and their modification dates.
while let logURL = enumerator?.nextObject() as? URL { while let logURL = enumerator?.nextObject() as? URL {
@@ -78,10 +88,38 @@ enum Tracing {
return sortedFiles return sortedFiles
} }
/// Delete all log files. static func migrateLogFiles() {
static func deleteLogFiles() { MXLog.info("Moving log files to \(logsDirectory)")
let fileManager = FileManager.default 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) try? fileManager.removeItem(at: logFileURL)
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@ class LoggingTests: XCTestCase {
} }
override func setUpWithError() throws { override func setUpWithError() throws {
Tracing.deleteLogFiles() Tracing.deleteLogFiles(in: Tracing.logsDirectory)
} }
func testLogging() async throws { func testLogging() async throws {
+1 -1
View File
@@ -48,7 +48,7 @@ settings:
ENABLE_BITCODE: false ENABLE_BITCODE: false
APP_NAME: ElementX APP_NAME: ElementX
KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)" KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"
MARKETING_VERSION: 25.07.3 MARKETING_VERSION: 25.07.4
CURRENT_PROJECT_VERSION: 1 CURRENT_PROJECT_VERSION: 1
SUPPORTS_MACCATALYST: false SUPPORTS_MACCATALYST: false