diff --git a/DiagnosticsTests/Reporters/LogsReporterTests.swift b/DiagnosticsTests/Reporters/LogsReporterTests.swift index 1fe7d74..37c8d01 100644 --- a/DiagnosticsTests/Reporters/LogsReporterTests.swift +++ b/DiagnosticsTests/Reporters/LogsReporterTests.swift @@ -62,5 +62,4 @@ final class LogsReporterTests: XCTestCase { let secondIndex = try XCTUnwrap(diagnostics.range(of: "second")?.lowerBound) XCTAssertTrue(firstIndex > secondIndex) } - } diff --git a/DiagnosticsTests/Reporters/LogsTrimmerTests.swift b/DiagnosticsTests/Reporters/LogsTrimmerTests.swift new file mode 100644 index 0000000..d875091 --- /dev/null +++ b/DiagnosticsTests/Reporters/LogsTrimmerTests.swift @@ -0,0 +1,60 @@ +// +// LogsTrimmerTests.swift +// +// +// Created by Antoine van der Lee on 01/03/2024. +// +// swiftlint:disable line_length + +@testable import Diagnostics +import XCTest + +final class LogsTrimmerTests: XCTestCase { + + /// It should trim the oldest line and skip session headers. + func testTrimmingSessionsSingleLine() { + let expectedOutput = """ +

Date: 2024-02-20 10:33:47

System: iOS 16.3

Locale: en-GB

Version: 6.2.8 (17000)

+

2024-02-20 10:33:47 | SYSTEM: 2024-02-20 10:33:47.086 Collect[32949:1669571] Reachability Flag Status: -R t------ reachabilityStatusForFlags

+ """ + + var input = expectedOutput + input += """ +

2024-02-20 10:33:47 | SYSTEM: 2024-02-20 10:33:47.101 Collect[32949:1669571] [Firebase/Crashlytics] Version 8.15.0

+ """ + + var inputData = Data(input.utf8) + let trimmer = LogsTrimmer( + numberOfLinesToTrim: 1 + ) + + trimmer.trim(data: &inputData) + + let outputString = String(data: inputData, encoding: .utf8) + XCTAssertEqual(outputString, expectedOutput) + } + + /// It should trim the oldest lines and skip session headers. + func testTrimmingSessionsMultipleLines() { + let expectedOutput = """ +

Date: 2024-02-20 10:33:47

System: iOS 16.3

Locale: en-GB

Version: 6.2.8 (17000)

+ """ + + var input = expectedOutput + input += """ +

2024-02-20 10:33:47 | SYSTEM: 2024-02-20 10:33:47.086 Collect[32949:1669571] Reachability Flag Status: -R t------ reachabilityStatusForFlags

+

2024-02-20 10:33:47 | SYSTEM: 2024-02-20 10:33:47.101 Collect[32949:1669571] [Firebase/Crashlytics] Version 8.15.0

+ """ + + var inputData = Data(input.utf8) + let trimmer = LogsTrimmer( + numberOfLinesToTrim: 10 + ) + + trimmer.trim(data: &inputData) + + let outputString = String(data: inputData, encoding: .utf8) + XCTAssertEqual(outputString, expectedOutput) + } +} +// swiftlint:enable line_length diff --git a/Sources/Logging/DiagnosticsLogger.swift b/Sources/Logging/DiagnosticsLogger.swift index a20e61e..59a1b63 100644 --- a/Sources/Logging/DiagnosticsLogger.swift +++ b/Sources/Logging/DiagnosticsLogger.swift @@ -223,19 +223,14 @@ extension DiagnosticsLogger { guard var data = try? Data(contentsOf: self.logFileLocation, options: .mappedIfSafe), - !data.isEmpty, - let newline = "\n".data(using: .utf8) else { - return assertionFailure("Trimming the current log file failed") + !data.isEmpty else { + return assertionFailure("Trimming the current log file failed") } - var position: Int = 0 - while (logSize - Int64(position)) > (maximumSize - trimSize) { - guard let range = data.firstRange(of: newline, in: position ..< data.count) else { break } - position = range.startIndex.advanced(by: 1) - } + let trimmer = LogsTrimmer(numberOfLinesToTrim: 10) + trimmer.trim(data: &data) - logSize -= Int64(position) - data.removeSubrange(0 ..< position) + logSize = Int64(data.count) guard (try? data.write(to: logFileLocation, options: .atomic)) != nil else { return assertionFailure("Could not write trimmed log to target file location: \(logFileLocation)") diff --git a/Sources/Logging/LogsTrimmer.swift b/Sources/Logging/LogsTrimmer.swift new file mode 100644 index 0000000..c28afee --- /dev/null +++ b/Sources/Logging/LogsTrimmer.swift @@ -0,0 +1,49 @@ +// +// LogsTrimmer.swift +// +// +// Created by Antoine van der Lee on 01/03/2024. +// + +import Foundation + +struct LogsTrimmer { + let numberOfLinesToTrim: Int + + func trim(data: inout Data) { + guard let logs = String(data: data, encoding: .utf8) else { + return + } + + // Define the regular expression pattern + let pattern = "

(.*?)

" + + // Create a regular expression object + guard let regex = try? NSRegularExpression(pattern: pattern) else { + return + } + + // Find all matches in the input string + let matches = regex + .matches( + in: logs, + range: NSRange(location: 0, length: logs.utf16.count) + ) + .suffix(numberOfLinesToTrim) + + guard let firstMatch = matches.first, let lastMatch = matches.last else { + return + } + + let range = NSRange( + location: firstMatch.range.location, + length: lastMatch.range.upperBound - firstMatch.range.location + ) + guard let range = Range(range, in: logs) else { + return + } + + let trimmedLogs = logs.replacingCharacters(in: range, with: "") + data = Data(trimmedLogs.utf8) + } +}