summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2024-05-06 11:33:37 +0200
committerBug Magnet <marco.nikic@mullvad.net>2024-05-06 11:33:37 +0200
commit82b1aebf3f305111f9a283d84ca488c1897b952c (patch)
tree98b38e47234d3e1ef5cd69d0b220d091fd37c1c2
parent957d0bd06131f8776a25809ba944e1143ceedcfe (diff)
parent9e909cffb9ae7028373d66e505842fe56461fbe0 (diff)
downloadmullvadvpn-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.swift10
-rw-r--r--ios/MullvadLogging/Date+LogFormat.swift10
-rw-r--r--ios/MullvadLogging/LogFileOutputStream.swift74
-rw-r--r--ios/MullvadLogging/LogRotation.swift21
-rw-r--r--ios/MullvadLogging/Logging.swift2
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/AddressCacheTracker/AddressCacheTracker.swift4
-rw-r--r--ios/MullvadVPN/AppDelegate.swift6
-rw-r--r--ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift5
-rw-r--r--ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift2
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift8
-rw-r--r--ios/MullvadVPNTests/MullvadLogging/LogRotationTests.swift45
-rw-r--r--ios/MullvadVPNTests/MullvadLogging/LoggingTests.swift8
-rw-r--r--ios/MullvadVPNUITests/RelayTests.swift4
-rw-r--r--ios/Shared/ApplicationConfiguration.swift32
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/")!