diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2024-05-06 11:33:37 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2024-05-06 11:33:37 +0200 |
| commit | 82b1aebf3f305111f9a283d84ca488c1897b952c (patch) | |
| tree | 98b38e47234d3e1ef5cd69d0b220d091fd37c1c2 | |
| parent | 957d0bd06131f8776a25809ba944e1143ceedcfe (diff) | |
| parent | 9e909cffb9ae7028373d66e505842fe56461fbe0 (diff) | |
| download | mullvadvpn-82b1aebf3f305111f9a283d84ca488c1897b952c.tar.xz mullvadvpn-82b1aebf3f305111f9a283d84ca488c1897b952c.zip | |
Merge branch 'ensure-log-file-sizes-stay-below-a-certain-threshold-ios-635'
| -rw-r--r-- | ios/MullvadLogging/CustomFormatLogHandler.swift | 10 | ||||
| -rw-r--r-- | ios/MullvadLogging/Date+LogFormat.swift | 10 | ||||
| -rw-r--r-- | ios/MullvadLogging/LogFileOutputStream.swift | 74 | ||||
| -rw-r--r-- | ios/MullvadLogging/LogRotation.swift | 21 | ||||
| -rw-r--r-- | ios/MullvadLogging/Logging.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/AppDelegate.swift | 6 | ||||
| -rw-r--r-- | ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift | 5 | ||||
| -rw-r--r-- | ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/TunnelManager/TunnelManager.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift | 8 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/MullvadLogging/LogRotationTests.swift | 45 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/MullvadLogging/LoggingTests.swift | 8 | ||||
| -rw-r--r-- | ios/MullvadVPNUITests/RelayTests.swift | 4 | ||||
| -rw-r--r-- | ios/Shared/ApplicationConfiguration.swift | 32 |
16 files changed, 171 insertions, 66 deletions
diff --git a/ios/MullvadLogging/CustomFormatLogHandler.swift b/ios/MullvadLogging/CustomFormatLogHandler.swift index 92dcb26b72..6a209e1acf 100644 --- a/ios/MullvadLogging/CustomFormatLogHandler.swift +++ b/ios/MullvadLogging/CustomFormatLogHandler.swift @@ -16,14 +16,6 @@ public struct CustomFormatLogHandler: LogHandler { private let label: String private let streams: [TextOutputStream] - private let dateFormatter = Self.makeDateFormatter() - - public static func makeDateFormatter() -> DateFormatter { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss.SSS" - return dateFormatter - } - public init(label: String, streams: [TextOutputStream]) { self.label = label self.streams = streams @@ -54,7 +46,7 @@ public struct CustomFormatLogHandler: LogHandler { } let prettyMetadata = Self.formatMetadata(mergedMetadata) let metadataOutput = prettyMetadata.isEmpty ? "" : " \(prettyMetadata)" - let timestamp = dateFormatter.string(from: Date()) + let timestamp = Date().logFormatted let formattedMessage = "[\(timestamp)][\(label)][\(level)]\(metadataOutput) \(message)\n" for var stream in streams { diff --git a/ios/MullvadLogging/Date+LogFormat.swift b/ios/MullvadLogging/Date+LogFormat.swift index 463d340b59..0cd51c08b1 100644 --- a/ios/MullvadLogging/Date+LogFormat.swift +++ b/ios/MullvadLogging/Date+LogFormat.swift @@ -9,16 +9,22 @@ import Foundation extension Date { - public func logFormatDate() -> String { + public var logFormatted: String { let formatter = DateFormatter() + formatter.dateFormat = "dd/MM/yyyy @ HH:mm:ss" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(abbreviation: "UTC") return formatter.string(from: self) } - public func logFormatFilename() -> String { + public var logFileFormatted: String { let formatter = DateFormatter() + formatter.dateFormat = "dd-MM-yyyy'T'HH:mm:ss" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(abbreviation: "UTC") return formatter.string(from: self) } diff --git a/ios/MullvadLogging/LogFileOutputStream.swift b/ios/MullvadLogging/LogFileOutputStream.swift index 26b059638e..17d335849e 100644 --- a/ios/MullvadLogging/LogFileOutputStream.swift +++ b/ios/MullvadLogging/LogFileOutputStream.swift @@ -16,10 +16,14 @@ private let reopenFileLogInterval: Duration = .seconds(5) class LogFileOutputStream: TextOutputStream { private let queue = DispatchQueue(label: "LogFileOutputStreamQueue", qos: .utility) - private let fileURL: URL + private let baseFileURL: URL + private var fileURL: URL private let encoding: String.Encoding - private let maxBufferCapacity: Int + private let maximumBufferCapacity: Int private let fileHeader: String + private let fileSizeLimit: UInt64 + private var partialFileSizeCounter: UInt64 = 0 + private var partialFileNameCounter = 1 private var state: State = .closed { didSet { @@ -45,11 +49,20 @@ class LogFileOutputStream: TextOutputStream { case waitingToReopen } - init(fileURL: URL, header: String, encoding: String.Encoding = .utf8, maxBufferCapacity: Int = 16 * 1024) { + init( + fileURL: URL, + header: String, + fileSizeLimit: UInt64 = ApplicationConfiguration.logMaximumFileSize, + encoding: String.Encoding = .utf8, + maxBufferCapacity: Int = 16 * 1024 + ) { self.fileURL = fileURL - self.encoding = encoding - self.maxBufferCapacity = maxBufferCapacity self.fileHeader = header + self.fileSizeLimit = fileSizeLimit + self.encoding = encoding + self.maximumBufferCapacity = maxBufferCapacity + + baseFileURL = fileURL.deletingPathExtension() } deinit { @@ -58,11 +71,11 @@ class LogFileOutputStream: TextOutputStream { func write(_ string: String) { queue.async { - self.writeNoQueue(string) + self.writeOnQueue(string) } } - private func writeNoQueue(_ string: String) { + private func writeOnQueue(_ string: String) { guard let data = string.data(using: encoding) else { return } switch state { @@ -89,7 +102,26 @@ class LogFileOutputStream: TextOutputStream { } } - @discardableResult private func write(fileHandle: FileHandle, data: Data) throws -> Int { + private func write(fileHandle: FileHandle, data: Data) throws { + dispatchPrecondition(condition: .onQueue(queue)) + + let incomingDataSize = UInt64(data.count) + + // Make sure incoming data chunks are not larger than the file size limit. + // Failure to handle this leads to data neither being written nor buffered/trimmed. + guard incomingDataSize <= fileSizeLimit else { + throw POSIXError(.EDQUOT) + } + + let predictedFileSize = partialFileSizeCounter + incomingDataSize + + // Rotate file if threshold has been met, then rerun the write operation. + guard predictedFileSize <= fileSizeLimit else { + try rotateFile(handle: fileHandle) + write(String(data: data, encoding: encoding) ?? "") + return + } + let bytesWritten = data.withUnsafeBytes { buffer -> Int in guard let ptr = buffer.baseAddress else { return 0 } @@ -99,9 +131,17 @@ class LogFileOutputStream: TextOutputStream { if bytesWritten == -1 { let code = POSIXErrorCode(rawValue: errno)! throw POSIXError(code) - } else { - return bytesWritten } + + partialFileSizeCounter += UInt64(bytesWritten) + } + + private func rotateFile(handle: FileHandle) throws { + try handle.close() + + state = .closed + partialFileSizeCounter = 0 + fileURL = try incrementFileName() } private func openFile() throws -> FileHandle { @@ -171,8 +211,18 @@ class LogFileOutputStream: TextOutputStream { private func bufferData(_ data: Data) { buffer.append(data) - if buffer.count > maxBufferCapacity { - buffer.removeFirst(buffer.count - maxBufferCapacity) + if buffer.count > maximumBufferCapacity { + buffer.removeFirst(buffer.count - maximumBufferCapacity) + } + } + + private func incrementFileName() throws -> URL { + partialFileNameCounter += 1 + + if let url = URL(string: baseFileURL.relativePath + "_\(partialFileNameCounter).log") { + return url + } else { + throw POSIXError(.ENOENT) } } } diff --git a/ios/MullvadLogging/LogRotation.swift b/ios/MullvadLogging/LogRotation.swift index 23d1e5f178..6b19da497e 100644 --- a/ios/MullvadLogging/LogRotation.swift +++ b/ios/MullvadLogging/LogRotation.swift @@ -36,7 +36,7 @@ public enum LogRotation { public var errorDescription: String? { switch self { case .rotateLogFiles: - return "Failure to rotate the source log file to backup." + return "Failure to rotate logs" } } @@ -74,30 +74,23 @@ public enum LogRotation { log1.creationDate > log2.creationDate } - try deleteLogsOlderThan(options.oldestAllowedDate, in: logs) - try deleteLogsWithCombinedSizeLargerThan(options.storageSizeLimit, in: logs) + try deleteLogs(dateThreshold: options.oldestAllowedDate, sizeThreshold: options.storageSizeLimit, in: logs) } catch { throw Error.rotateLogFiles(error) } } - private static func deleteLogsOlderThan(_ dateThreshold: Date, in logs: [LogData]) throws { + private static func deleteLogs(dateThreshold: Date, sizeThreshold: Int, in logs: [LogData]) throws { let fileManager = FileManager.default - for log in logs where log.creationDate < dateThreshold { - try fileManager.removeItem(at: log.path) - } - } - - private static func deleteLogsWithCombinedSizeLargerThan(_ sizeThreshold: Int, in logs: [LogData]) throws { - let fileManager = FileManager.default - - // Delete all logs outside maximum capacity (ordered newest to oldest). var fileSizes = UInt64.zero for log in logs { fileSizes += log.size - if fileSizes > sizeThreshold { + let logIsTooOld = log.creationDate < dateThreshold + let logCapacityIsExceeded = fileSizes > sizeThreshold + + if logIsTooOld || logCapacityIsExceeded { try fileManager.removeItem(at: log.path) } } diff --git a/ios/MullvadLogging/Logging.swift b/ios/MullvadLogging/Logging.swift index 60ad30de6f..4b5b7a3965 100644 --- a/ios/MullvadLogging/Logging.swift +++ b/ios/MullvadLogging/Logging.swift @@ -78,7 +78,7 @@ public struct LoggerBuilder { let rotationLogger = Logger(label: "LogRotation") for error in logRotationErrors { - rotationLogger.error(error: error, message: "Failed to rotate log") + rotationLogger.error(error: error, message: error.localizedDescription) } } } diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index e2d33e5c27..2a5f7feac0 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -603,6 +603,8 @@ 7ADCB2D82B6A6EB300C88F89 /* AnyRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCB2D72B6A6EB300C88F89 /* AnyRelay.swift */; }; 7ADCB2DA2B6A730400C88F89 /* IPOverrideRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCB2D92B6A730400C88F89 /* IPOverrideRepositoryStub.swift */; }; 7AE044BB2A935726003915D8 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A88DCD02A8FABBE00D2FF0E /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7AED35CC2BD13F60002A67D1 /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; }; + 7AED35CD2BD13FC4002A67D1 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; }; 7AEF7F1A2AD00F52006FE45D /* AppMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */; }; 7AF10EB22ADE859200C090B9 /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF10EB12ADE859200C090B9 /* AlertViewController.swift */; }; 7AF10EB42ADE85BC00C090B9 /* RelayFilterCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF10EB32ADE85BC00C090B9 /* RelayFilterCoordinator.swift */; }; @@ -5732,6 +5734,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7AED35CC2BD13F60002A67D1 /* ApplicationConfiguration.swift in Sources */, + 7AED35CD2BD13FC4002A67D1 /* ApplicationTarget.swift in Sources */, 58D22402294C90050029F5F8 /* Logger+Errors.swift in Sources */, 58D22404294C90050029F5F8 /* Date+LogFormat.swift in Sources */, 58D22403294C90050029F5F8 /* Logging.swift in Sources */, diff --git a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift index 2734729c46..f36d9b6df6 100644 --- a/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift +++ b/ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift @@ -65,7 +65,7 @@ final class AddressCacheTracker { let scheduleDate = _nextScheduleDate() - logger.debug("Schedule address cache update at \(scheduleDate.logFormatDate()).") + logger.debug("Schedule address cache update at \(scheduleDate.logFormatted).") scheduleEndpointsUpdate(startTime: .now() + scheduleDate.timeIntervalSinceNow) } @@ -165,7 +165,7 @@ final class AddressCacheTracker { let scheduleDate = self._nextScheduleDate() self.logger - .debug("Schedule next address cache update at \(scheduleDate.logFormatDate()).") + .debug("Schedule next address cache update at \(scheduleDate.logFormatted).") self.scheduleEndpointsUpdate(startTime: .now() + scheduleDate.timeIntervalSinceNow) } diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index a1087d70a9..156b477142 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -307,7 +307,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let request = BGAppRefreshTaskRequest(identifier: BackgroundTask.appRefresh.identifier) request.earliestBeginDate = date - logger.debug("Schedule app refresh task at \(date.logFormatDate()).") + logger.debug("Schedule app refresh task at \(date.logFormatted).") try BGTaskScheduler.shared.submit(request) } catch { @@ -325,7 +325,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD request.requiresNetworkConnectivity = true request.earliestBeginDate = date - logger.debug("Schedule key rotation task at \(date.logFormatDate()).") + logger.debug("Schedule key rotation task at \(date.logFormatted).") try BGTaskScheduler.shared.submit(request) } catch { @@ -341,7 +341,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD request.requiresNetworkConnectivity = true request.earliestBeginDate = date - logger.debug("Schedule address cache update task at \(date.logFormatDate()).") + logger.debug("Schedule address cache update task at \(date.logFormatted).") try BGTaskScheduler.shared.submit(request) } catch { diff --git a/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift b/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift index 0876e86dea..48dbf6a723 100644 --- a/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift +++ b/ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift @@ -8,7 +8,6 @@ import Foundation -private let kLogMaxReadBytes: UInt64 = 128 * 1024 private let kLogDelimiter = "====================" private let kRedactedPlaceholder = "[REDACTED]" private let kRedactedAccountPlaceholder = "[REDACTED ACCOUNT NUMBER]" @@ -93,7 +92,7 @@ class ConsolidatedApplicationLog: TextOutputStreamable { let path = fileURL.path let redactedPath = redact(string: path) - if let lossyString = Self.readFileLossy(path: path, maxBytes: kLogMaxReadBytes) { + if let lossyString = Self.readFileLossy(path: path, maxBytes: ApplicationConfiguration.logMaximumFileSize) { let redactedString = redact(string: lossyString) logs.append(LogAttachment(label: redactedPath, content: redactedString)) @@ -126,7 +125,7 @@ class ConsolidatedApplicationLog: TextOutputStreamable { fileHandle.seek(toFileOffset: 0) } - let data = fileHandle.readData(ofLength: Int(kLogMaxReadBytes)) + let data = fileHandle.readData(ofLength: Int(ApplicationConfiguration.logMaximumFileSize)) let replacementCharacter = Character(UTF8.decode(UTF8.encodedReplacementCharacter)) let lossyString = String( String(decoding: data, as: UTF8.self) diff --git a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift index 8efbc96095..acb32083cd 100644 --- a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift +++ b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift @@ -140,7 +140,7 @@ class SendStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentResponse """ AppStore receipt was processed. \ Time added: \(response.timeAdded), \ - New expiry: \(response.newExpiry.logFormatDate()) + New expiry: \(response.newExpiry.logFormatted) """ ) self.finish(result: .success(response)) diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index fe989a790c..8f3b69112c 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -172,7 +172,7 @@ final class TunnelManager: StorePaymentObserver { privateKeyRotationTimer = timer - logger.debug("Schedule next private key rotation at \(scheduleDate.logFormatDate()).") + logger.debug("Schedule next private key rotation at \(scheduleDate.logFormatted).") } // MARK: - Public methods diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift index 09fa2dfd0a..f522662942 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift @@ -30,6 +30,10 @@ final class ProblemReportInteractor { return report }() + var reportString: String { + consolidatedLog.string + } + init(apiProxy: APIQuerying, tunnelManager: TunnelManager) { self.apiProxy = apiProxy self.tunnelManager = tunnelManager @@ -55,8 +59,4 @@ final class ProblemReportInteractor { completionHandler: completion ) } - - var reportString: String { - consolidatedLog.string - } } diff --git a/ios/MullvadVPNTests/MullvadLogging/LogRotationTests.swift b/ios/MullvadVPNTests/MullvadLogging/LogRotationTests.swift index 4d7baf6212..f4f70d0124 100644 --- a/ios/MullvadVPNTests/MullvadLogging/LogRotationTests.swift +++ b/ios/MullvadVPNTests/MullvadLogging/LogRotationTests.swift @@ -6,7 +6,7 @@ // Copyright © 2024 Mullvad VPN AB. All rights reserved. // -import MullvadLogging +@testable import MullvadLogging import XCTest final class LogRotationTests: XCTestCase { @@ -24,6 +24,45 @@ final class LogRotationTests: XCTestCase { try fileManager.removeItem(atPath: directoryPath.relativePath) } + func testRotatingActiveLogWhenSizeLimitIsExceeded() throws { + let logName = "test.log" + let logPath = directoryPath.appendingPathComponent(logName) + + let totalLogSizeLimit = 200 + let totalLogTestSize = 645 + let logChunkSize = 20 + + let expectedLogCount = Int(ceil(Double(totalLogTestSize) / Double(totalLogSizeLimit))) + let writeOperationCount = Int(ceil(Double(totalLogTestSize) / Double(logChunkSize))) + + let stream = LogFileOutputStream(fileURL: logPath, header: "", fileSizeLimit: UInt64(totalLogSizeLimit)) + for _ in 0 ..< writeOperationCount { + stream.write(stringOfSize(logChunkSize)) + + // Without sync between every write the test fails on Github. + sync() + } + + let actualLogCount = try fileManager.contentsOfDirectory(atPath: directoryPath.relativePath).count + XCTAssertEqual(expectedLogCount, actualLogCount) + + for index in 0 ..< actualLogCount { + var expectedFileName = logName + + if index != 0 { + // Rotated log filenames start at "_2". + expectedFileName = expectedFileName.replacingOccurrences(of: ".log", with: "_\(index + 1).log") + } + + let logExists = fileManager.fileExists( + atPath: directoryPath + .appendingPathComponent(expectedFileName) + .relativePath + ) + XCTAssertTrue(logExists) + } + } + func testRotateLogsByStorageSizeLimit() throws { let logPaths = [ directoryPath.appendingPathComponent("test1.log"), @@ -97,6 +136,10 @@ final class LogRotationTests: XCTestCase { } extension LogRotationTests { + private func stringOfSize(_ size: Int) -> String { + (0 ..< size).map { "\($0 % 10)" }.joined(separator: "") + } + private func writeDataToDisk(path: URL, fileSize: Int) throws { let data = Data((0 ..< fileSize).map { UInt8($0 & 0xff) }) try data.write(to: path) diff --git a/ios/MullvadVPNTests/MullvadLogging/LoggingTests.swift b/ios/MullvadVPNTests/MullvadLogging/LoggingTests.swift index bdc08e959c..63817d6aa2 100644 --- a/ios/MullvadVPNTests/MullvadLogging/LoggingTests.swift +++ b/ios/MullvadVPNTests/MullvadLogging/LoggingTests.swift @@ -26,7 +26,7 @@ class MullvadLoggingTests: XCTestCase { return fileURL } - func testLogFileOutputStreamWritesHeader() { + func testLogFileOutputStreamWritesHeader() throws { let headerText = "This is a header" let logMessage = "And this is a log message\n" let fileURL = temporaryFileURL() @@ -34,11 +34,11 @@ class MullvadLoggingTests: XCTestCase { stream.write(logMessage) sync() - let contents = String(decoding: try! Data(contentsOf: fileURL), as: UTF8.self) + let contents = try XCTUnwrap(String(contentsOf: fileURL)) XCTAssertEqual(contents, "\(headerText)\n\(logMessage)") } - func testLogHeader() { + func testLogHeader() throws { let expectedHeader = "Header of a log file" var builder = LoggerBuilder(header: expectedHeader) @@ -51,7 +51,7 @@ class MullvadLoggingTests: XCTestCase { sync() - let contents = String(decoding: try! Data(contentsOf: fileURL), as: UTF8.self) + let contents = try XCTUnwrap(String(contentsOf: fileURL)) XCTAssert(contents.hasPrefix(expectedHeader)) } diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift index b3ebedda09..c8848e2a46 100644 --- a/ios/MullvadVPNUITests/RelayTests.swift +++ b/ios/MullvadVPNUITests/RelayTests.swift @@ -196,7 +196,9 @@ class RelayTests: LoggedInWithTimeUITestCase { .enterText("4001") .dismissKeyboard() .swipeDownToDismissModal() - .swipeDownToDismissModal() // After editing text field the table is first responder for the first swipe so we need to swipe twice to swipe the modal + + // After editing text field the table is first responder for the first swipe so we need to swipe twice to swipe the modal + .swipeDownToDismissModal() TunnelControlPage(app) .tapSecureConnectionButton() diff --git a/ios/Shared/ApplicationConfiguration.swift b/ios/Shared/ApplicationConfiguration.swift index 9acea1b971..4e2b28c62f 100644 --- a/ios/Shared/ApplicationConfiguration.swift +++ b/ios/Shared/ApplicationConfiguration.swift @@ -24,24 +24,40 @@ enum ApplicationConfiguration { /// Returns URL for new log file associated with application target and located within shared container. static func newLogFileURL(for target: ApplicationTarget) -> URL { containerURL.appendingPathComponent( - "\(target.bundleIdentifier)_\(Date().logFormatFilename()).log", + "\(target.bundleIdentifier)_\(Date().logFileFormatted).log", isDirectory: false ) } /// Returns URLs for log files associated with application target and located within shared container. static func logFileURLs(for target: ApplicationTarget) -> [URL] { + let fileManager = FileManager.default let containerUrl = containerURL + let filePathsInDirectory = try? fileManager.contentsOfDirectory(atPath: containerURL.relativePath) - return (try? FileManager.default.contentsOfDirectory(atPath: containerURL.relativePath))?.compactMap { file in - if file.split(separator: ".").last == "log" { - containerUrl.appendingPathComponent(file) - } else { - nil - } - }.sorted { $0.relativePath > $1.relativePath } ?? [] + let filteredFilePaths: [URL] = filePathsInDirectory?.compactMap { path in + let pathIsLog = path.split(separator: ".").last == "log" + let pathBelongsToTarget = path.contains(target.bundleIdentifier) + + return pathIsLog && pathBelongsToTarget ? containerUrl.appendingPathComponent(path) : nil + } ?? [] + + let sortedFilePaths = try? filteredFilePaths.sorted { path1, path2 in + let path1Attributes = try fileManager.attributesOfItem(atPath: path1.relativePath) + let date1 = (path1Attributes[.creationDate] as? Date) ?? Date.distantPast + + let path2Attributes = try fileManager.attributesOfItem(atPath: path2.relativePath) + let date2 = (path2Attributes[.creationDate] as? Date) ?? Date.distantPast + + return date1 > date2 + } + + return sortedFilePaths ?? [] } + // Maximum file size for writing and reading logs. + static let logMaximumFileSize: UInt64 = 131_072 // 128 kB. + /// Privacy policy URL. static let privacyPolicyURL = URL(string: "https://mullvad.net/help/privacy-policy/")! |
