Handover log file management to Rust. (#2495)

* Handover log file management to Rust.

Fix all the tests, disabling the logging ones for now.

* Merge logging tests into a single case 😬

Not ideal but at least they're now running.

* Move TracingConfiguration into its own file.

* Split up the tests into smaller functions instead of writing code like a caveman.
This commit is contained in:
Doug
2024-02-23 18:29:13 +00:00
committed by GitHub
parent c573b7caa1
commit ff35688088
19 changed files with 379 additions and 772 deletions
+7 -7
View File
@@ -150,6 +150,7 @@
24A75F72EEB7561B82D726FD /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; }; 24A75F72EEB7561B82D726FD /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; };
24B7CD41342C143117ADA768 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B1CC9AA154F4D5435BF60A /* Comparable.swift */; }; 24B7CD41342C143117ADA768 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B1CC9AA154F4D5435BF60A /* Comparable.swift */; };
24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; }; 24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; };
24DF253C18D3E2C56DD0E597 /* TracingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */; };
25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */; }; 25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */; };
256D76972BA3254F7CB7F88B /* LocationAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD8234D0E9C9B12BF9F240B /* LocationAnnotation.swift */; }; 256D76972BA3254F7CB7F88B /* LocationAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD8234D0E9C9B12BF9F240B /* LocationAnnotation.swift */; };
25C4C1100B6EA79F5CC7CBB5 /* AppLockSetupPINScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 989D7380D9C86B3A10D30B13 /* AppLockSetupPINScreenViewModelTests.swift */; }; 25C4C1100B6EA79F5CC7CBB5 /* AppLockSetupPINScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 989D7380D9C86B3A10D30B13 /* AppLockSetupPINScreenViewModelTests.swift */; };
@@ -556,7 +557,6 @@
8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */; }; 8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */; };
8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; }; 8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; };
8C42B5B1642D189C362A5EDF /* SecureBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.swift */; }; 8C42B5B1642D189C362A5EDF /* SecureBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.swift */; };
8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; }; 8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; };
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; }; 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
@@ -728,7 +728,6 @@
B6048166B4AA4CEFEA9B77A6 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; B6048166B4AA4CEFEA9B77A6 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; };
B6064D82FCDCB829601C1F59 /* SecureBackupLogoutConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEE10AB666891E6A675E5E /* SecureBackupLogoutConfirmationScreen.swift */; }; B6064D82FCDCB829601C1F59 /* SecureBackupLogoutConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEE10AB666891E6A675E5E /* SecureBackupLogoutConfirmationScreen.swift */; };
B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD6E621CC5E6D4830D96D2D /* MockMediaProvider.swift */; }; B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD6E621CC5E6D4830D96D2D /* MockMediaProvider.swift */; };
B66757D0254843162595B25D /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; }; B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; };
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; }; B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; };
B6EC2148FA5443C9289BEEBA /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; }; B6EC2148FA5443C9289BEEBA /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
@@ -895,6 +894,7 @@
E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0287793F11C480E242B03DF5 /* UserDiscoveryServiceTest.swift */; }; E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0287793F11C480E242B03DF5 /* UserDiscoveryServiceTest.swift */; };
E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */; }; E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */; };
E32A18802EB37EEE3EF7B965 /* GlobalSearchScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B788615712FED326F73D3F83 /* GlobalSearchScreenViewModelProtocol.swift */; }; E32A18802EB37EEE3EF7B965 /* GlobalSearchScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B788615712FED326F73D3F83 /* GlobalSearchScreenViewModelProtocol.swift */; };
E37044401D9951D6C02C0855 /* TracingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */; };
E3AC72E3E58F364EF15C1CC7 /* NotificationSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */; }; E3AC72E3E58F364EF15C1CC7 /* NotificationSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */; };
E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */; }; E3CA565A4B9704F191B191F0 /* JoinedRoomSize+MemberCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */; };
E3E1E255DC8CB34BD8573E0D /* UserIndicatorControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */; }; E3E1E255DC8CB34BD8573E0D /* UserIndicatorControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */; };
@@ -1659,7 +1659,6 @@
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; }; A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; };
A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPicker.swift; sourceTree = "<group>"; }; A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPicker.swift; sourceTree = "<group>"; };
A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineView.swift; sourceTree = "<group>"; }; A2AC3C656E960E15B5905E05 /* UnsupportedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineView.swift; sourceTree = "<group>"; };
A34A814CBD56230BC74FFCF4 /* MXLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLogger.swift; sourceTree = "<group>"; };
A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenContent.swift; sourceTree = "<group>"; }; A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenContent.swift; sourceTree = "<group>"; };
A3FBD9C2B9A5479526920399 /* BugReportScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenCoordinator.swift; sourceTree = "<group>"; }; A3FBD9C2B9A5479526920399 /* BugReportScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenCoordinator.swift; sourceTree = "<group>"; };
A40C19719687984FD9478FBE /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; }; A40C19719687984FD9478FBE /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
@@ -1953,6 +1952,7 @@
ECB08484CD5D77C9BF97AA78 /* WaitlistScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenUITests.swift; sourceTree = "<group>"; }; ECB08484CD5D77C9BF97AA78 /* WaitlistScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenUITests.swift; sourceTree = "<group>"; };
ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; }; ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; }; ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingConfiguration.swift; sourceTree = "<group>"; };
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; }; ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
@@ -2175,8 +2175,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
111B698739E3410E2CDB7144 /* MXLog.swift */, 111B698739E3410E2CDB7144 /* MXLog.swift */,
A34A814CBD56230BC74FFCF4 /* MXLogger.swift */,
542D4F49FABA056DEEEB3400 /* RustTracing.swift */, 542D4F49FABA056DEEEB3400 /* RustTracing.swift */,
ED003DF1B7CF40E7073A2280 /* TracingConfiguration.swift */,
); );
path = Logging; path = Logging;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -5281,7 +5281,6 @@
A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */, A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */,
FB0A9D06FC9122E37992D962 /* LayoutDirection.swift in Sources */, FB0A9D06FC9122E37992D962 /* LayoutDirection.swift in Sources */,
AD2A81B65A9F6163012086F1 /* MXLog.swift in Sources */, AD2A81B65A9F6163012086F1 /* MXLog.swift in Sources */,
8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */,
52473A4D7B1FBD4CD1E770C8 /* MatrixEntityRegex.swift in Sources */, 52473A4D7B1FBD4CD1E770C8 /* MatrixEntityRegex.swift in Sources */,
8B76191B9DDD1AC90A6E3A35 /* MediaFileHandleProxy.swift in Sources */, 8B76191B9DDD1AC90A6E3A35 /* MediaFileHandleProxy.swift in Sources */,
68184EF36396424FE19A727D /* MediaLoader.swift in Sources */, 68184EF36396424FE19A727D /* MediaLoader.swift in Sources */,
@@ -5312,6 +5311,7 @@
6EC7A40A537CFB3D526A111C /* Strings.swift in Sources */, 6EC7A40A537CFB3D526A111C /* Strings.swift in Sources */,
719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */, 719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */,
E0FB26262689F04D66A949D7 /* TestablePreview.swift in Sources */, E0FB26262689F04D66A949D7 /* TestablePreview.swift in Sources */,
24DF253C18D3E2C56DD0E597 /* TracingConfiguration.swift in Sources */,
DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */, DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */,
281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */, 281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */,
518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */, 518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */,
@@ -5716,7 +5716,6 @@
A5B9EF45C7B8ACEB4954AE36 /* LoginScreenViewModelProtocol.swift in Sources */, A5B9EF45C7B8ACEB4954AE36 /* LoginScreenViewModelProtocol.swift in Sources */,
4362C770C7E05ADC750E5070 /* LongPressWithFeedback.swift in Sources */, 4362C770C7E05ADC750E5070 /* LongPressWithFeedback.swift in Sources */,
B94368839BDB69172E28E245 /* MXLog.swift in Sources */, B94368839BDB69172E28E245 /* MXLog.swift in Sources */,
B66757D0254843162595B25D /* MXLogger.swift in Sources */,
C1D0AB8222D7BAFC9AF9C8C0 /* MapLibreMapView.swift in Sources */, C1D0AB8222D7BAFC9AF9C8C0 /* MapLibreMapView.swift in Sources */,
C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */, C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */,
E2DDA49BD62F03F180A42E30 /* MapLibreStaticMapView.swift in Sources */, E2DDA49BD62F03F180A42E30 /* MapLibreStaticMapView.swift in Sources */,
@@ -6059,6 +6058,7 @@
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */, 69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */,
FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */, FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */,
2B1E080B32167AE9EFC763A2 /* TimelineTableViewController.swift in Sources */, 2B1E080B32167AE9EFC763A2 /* TimelineTableViewController.swift in Sources */,
E37044401D9951D6C02C0855 /* TracingConfiguration.swift in Sources */,
6D6E651ACACE27E9C5690818 /* TypingIndicatorView.swift in Sources */, 6D6E651ACACE27E9C5690818 /* TypingIndicatorView.swift in Sources */,
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */, 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */,
A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */, A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */,
@@ -6891,7 +6891,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 1.1.42; version = 1.1.43;
}; };
}; };
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {
@@ -130,8 +130,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "62a45668e8e8a70079b8fae0b8031852533900e9", "revision" : "87f6686023df7a7053bfb18162a17ee803ab1bc7",
"version" : "1.1.42" "version" : "1.1.43"
} }
}, },
{ {
@@ -98,7 +98,7 @@ class BugReportFlowCoordinator: FlowCoordinatorProtocol {
} }
private func presentLogViewerScreen() { private func presentLogViewerScreen() {
let coordinator = LogViewerScreenCoordinator(parameters: .init()) let coordinator = LogViewerScreenCoordinator()
coordinator.actions.sink { [weak self] action in coordinator.actions.sink { [weak self] action in
guard let self else { return } guard let self else { return }
+3 -31
View File
@@ -24,15 +24,6 @@ import MatrixRustSDK
enum MXLog { enum MXLog {
private enum Constants { private enum Constants {
static let target = "elementx" static let target = "elementx"
// Avoid redirecting NSLogs to files if we are attached to a debugger.
static let redirectToFiles = isatty(STDERR_FILENO) == 0
/// the maximum number of log files to use before rolling. `10` by default.
static let maxLogFileCount: UInt = 10
/// the maximum total space to use for log files in bytes. `100MB` by default.
static let logFilesSizeLimit: UInt = 100 * 1024 * 1024 // 100MB
} }
// Rust side crashes if invoking setupTracing multiple times // Rust side crashes if invoking setupTracing multiple times
@@ -43,39 +34,20 @@ enum MXLog {
static func configure(target: String? = nil, static func configure(target: String? = nil,
logLevel: TracingConfiguration.LogLevel, logLevel: TracingConfiguration.LogLevel,
otlpConfiguration: OTLPConfiguration? = nil, otlpConfiguration: OTLPConfiguration? = nil) {
redirectToFiles: Bool = Constants.redirectToFiles, guard !didConfigureOnce else { return }
maxLogFileCount: UInt = Constants.maxLogFileCount,
logFileSizeLimit: UInt = Constants.logFilesSizeLimit) {
guard didConfigureOnce == false else {
if let target {
MXLogger.setSubLogName(target)
}
// SubLogName needs to be set before calling configure in order to be applied
MXLogger.configure(redirectToFiles: redirectToFiles,
maxLogFileCount: maxLogFileCount,
logFileSizeLimit: logFileSizeLimit)
return
}
RustTracing.setup(configuration: .init(logLevel: logLevel), otlpConfiguration: otlpConfiguration) RustTracing.setup(configuration: .init(logLevel: logLevel, target: target), otlpConfiguration: otlpConfiguration)
if let target { if let target {
self.target = target self.target = target
MXLogger.setSubLogName(target)
} else { } else {
self.target = Constants.target self.target = Constants.target
} }
rootSpan = Span(file: #file, line: #line, level: .info, target: self.target, name: "root") rootSpan = Span(file: #file, line: #line, level: .info, target: self.target, name: "root")
rootSpan.enter() rootSpan.enter()
MXLogger.configure(redirectToFiles: redirectToFiles,
maxLogFileCount: maxLogFileCount,
logFileSizeLimit: logFileSizeLimit)
didConfigureOnce = true didConfigureOnce = true
} }
@@ -1,335 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
/// The `MXLogger` tool redirects NSLog output into a fixed pool of files.
/// Another log file is used every time `MXLogger redirectNSLog(toFiles: true)`
/// is called. The pool contains 3 files.
///
/// `MXLogger` can track and log uncaught exceptions or crashes.
enum MXLogger {
/// stderr so it can be restored.
static var stderrSave: Int32 = 0
private enum Constants {
/// The filename used for the crash log.
static let crashLogFileName = "crash.log"
}
/// Redirect NSLog output to MXLogger files.
///
/// It is advised to condition this redirection in `#if (!isatty(STDERR_FILENO))` block to enable
/// it only when the device is not attached to the debugger.
///
/// - Parameters:
/// - redirectToFiles: `true` to enable the redirection.
/// - maxLogFileCount: number of files to keep (default is 10).
/// - logFileSizeLimit: size limit of log files in bytes. 0 means no limitation, the default value for other methods
static func configure(redirectToFiles: Bool,
maxLogFileCount: UInt,
logFileSizeLimit: UInt) {
if redirectToFiles {
var tempLog = ""
// Do a circular buffer based on X files
for index in (0...(maxLogFileCount - 2)).reversed() {
rotateLog(at: index, tempLog: &tempLog)
}
// Save stderr so it can be restored.
stderrSave = dup(STDERR_FILENO)
let nsLogURL = logURL(for: "console\(subLogName).log")
freopen((nsLogURL as NSURL).fileSystemRepresentation, "w+", stderr)
MXLog.info("redirectNSLogToFiles: true")
if !tempLog.isEmpty {
// We can now log into files
MXLog.info(tempLog)
}
removeExtraFiles(from: maxLogFileCount)
if logFileSizeLimit > 0 {
removeFiles(after: logFileSizeLimit)
}
} else if stderrSave > 0 {
// Flush before restoring stderr
fflush(stderr)
// Now restore stderr, so new output goes to console.
dup2(stderrSave, STDERR_FILENO)
close(stderrSave)
}
}
private static func rotateLog(at index: UInt, tempLog: inout String) {
let fileManager = FileManager.default
let currentURL: URL
let newURL: URL
if index == 0 {
currentURL = logURL(for: String("console\(subLogName).log"))
newURL = logURL(for: String("console\(subLogName).1.log"))
} else {
currentURL = logURL(for: String("console\(subLogName).\(index).log"))
newURL = logURL(for: String("console\(subLogName).\(index + 1).log"))
}
guard fileManager.fileExists(atPath: currentURL.path()) else { return }
if fileManager.fileExists(atPath: newURL.path()) {
// Temp log
tempLog.append("removeItemAt: \(newURL)\n")
do {
try fileManager.removeItem(at: newURL)
} catch {
tempLog.append("removeItemAt: \(newURL). Error: \(error)\n")
}
}
// Temp log
tempLog.append("moveItemAt: \(currentURL) to: \(newURL)\n")
do {
try fileManager.moveItem(at: currentURL, to: newURL)
} catch {
tempLog.append("moveItemAt: \(currentURL) to: \(newURL). Error: \(error)\n")
}
}
private static func logURL(for fileName: String) -> URL {
MXLogger.logsFolderURL.appending(path: fileName)
}
/// Delete all log files.
static func deleteLogFiles() {
let fileManager = FileManager.default
for logFileURL in logFiles {
try? fileManager.removeItem(at: logFileURL)
}
}
/// The list of all log file URLs, sorted chronologically.
static var logFiles: [URL] {
var logFiles = [(url: URL, modificationDate: Date)]()
let fileManager = FileManager.default
let enumerator = fileManager.enumerator(at: logsFolderURL, includingPropertiesForKeys: [.contentModificationDateKey])
// Find all *.log files and their modification dates.
while let logURL = enumerator?.nextObject() as? URL {
guard let resourceValues = try? logURL.resourceValues(forKeys: [.contentModificationDateKey]),
let modificationDate = resourceValues.contentModificationDate
else { continue }
if logURL.pathExtension == "log" {
logFiles.append((logURL, modificationDate))
}
}
let sortedFiles = logFiles.sorted { $0.modificationDate > $1.modificationDate }.map(\.url)
MXLog.info("logFiles: \(sortedFiles.map(\.lastPathComponent))")
return sortedFiles
}
// MARK: - Exceptions and crashes
/// Exceptions uncaught by try catch block are handled here
static func handleUncaughtException(_ exception: NSException) {
MXLogger.logCrashes(false)
// Extract running app information
let app = InfoPlistReader.main.bundleExecutable
let appId = InfoPlistReader.main.bundleIdentifier
let appVersion = "\(InfoPlistReader.main.bundleShortVersionString) (r\(InfoPlistReader.main.bundleVersion))"
// Build the crash log
let model = UIDevice.current.model
let version = UIDevice.current.systemVersion
let backtrace = exception.callStackSymbols
let description = String(format: "%.0f - %@\n%@\nApplication: %@ (%@)\nApplication version: %@\nBuild: %@\n%@ %@\n\nMain thread: %@\n%@\n",
Date.now.timeIntervalSince1970,
NSDate(),
exception.description,
app, appId,
appVersion,
buildVersion ?? "Unknown",
model, version,
Thread.isMainThread ? "true" : "false",
backtrace)
// Write to the crash log file
MXLogger.deleteCrashLog()
let crashLog = crashLogURL
try? description.write(to: crashLog, atomically: false, encoding: .utf8)
MXLog.error("handleUncaughtException", context: ["description": description])
}
// Signals emitted by the app are handled here
private static func handleSignal(_ signalValue: Int32) {
// Throw a custom Objective-C exception
// The Objective-C runtime will then be able to build a readable call stack in handleUncaughtException
withVaList([signalValue]) { NSException.raise(.init("Signal detected"), format: "Signal detected: %d", arguments: $0) }
}
/// Make `MXLogger` catch and log unmanaged exceptions or application crashes.
///
/// When such error happens, `MXLogger` stores the application stack trace into a file
/// just before the application leaves. The path of this file is provided by `MXLogger.crashLog`.
///
/// - Parameter enabled: `true` to enable the catch.
static func logCrashes(_ enabled: Bool) {
if enabled {
// Handle not managed exceptions by ourselves
NSSetUncaughtExceptionHandler { exception in
MXLogger.handleUncaughtException(exception)
}
// Register signal event (seg fault & cie)
signal(SIGABRT) { MXLogger.handleSignal($0) }
signal(SIGILL) { MXLogger.handleSignal($0) }
signal(SIGSEGV) { MXLogger.handleSignal($0) }
signal(SIGFPE) { MXLogger.handleSignal($0) }
signal(SIGBUS) { MXLogger.handleSignal($0) }
signal(SIGABRT) { MXLogger.handleSignal($0) }
} else {
// Disable crash handling
NSSetUncaughtExceptionHandler(nil)
signal(SIGABRT, SIG_DFL)
signal(SIGILL, SIG_DFL)
signal(SIGSEGV, SIG_DFL)
signal(SIGFPE, SIG_DFL)
signal(SIGBUS, SIG_DFL)
}
}
/// Set the app build version.
/// It will be reported in crash report.
static var buildVersion: String?
/// Set a sub name for namespacing log files.
///
/// A sub name must be set when running from an app extension because extensions can
/// run in parallel to the app.
/// It must be called before `redirectNSLog(toFiles)`.
///
/// - Parameter name: the subname for log files. Files will be named as `console-[subLogName].log`
/// Default is nil.
static func setSubLogName(_ name: String) {
if name.isEmpty {
subLogName = ""
} else {
subLogName = "-\(name)"
}
}
private static var subLogName = ""
/// The URL used for a crash log file.
static var crashLogURL: URL {
MXLogger.logsFolderURL.appending(path: Constants.crashLogFileName)
}
/// The URL of the file containing the last application crash if one exists or `nil` if there is none.
///
/// Only one crash log is stored at a time. The best moment for the app to handle it is the
/// at its next startup.
static var crashLog: URL? {
let crashLogURL = MXLogger.crashLogURL
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: crashLogURL.path()) else { return nil }
return crashLogURL
}
/// Delete the crash log file.
static func deleteCrashLog() {
let crashLog = MXLogger.crashLogURL
let fileManager = FileManager.default
if fileManager.fileExists(atPath: crashLog.path()) {
try? fileManager.removeItem(at: crashLog)
}
}
// MARK: - Private
/// The folder where logs are stored
private static var logsFolderURL: URL {
.appGroupContainerDirectory
}
/// If `self.redirectNSLog(toFiles:numberOfFiles:)` is called with a lower numberOfFiles we need to do some cleanup.
private static func removeExtraFiles(from count: UInt) {
let fileManager = FileManager.default
for index in count... {
let fileName = "console\(subLogName).\(index).log"
let logFile = logURL(for: fileName)
if fileManager.fileExists(atPath: logFile.path()) {
try? fileManager.removeItem(at: logFile)
MXLog.info("removeExtraFilesFromCount: \(count). removeItemAt: \(logFile)\n")
} else {
break
}
}
}
/// If `redirectNSLog(toFiles:sizeLimit:)` is called with a size limit, we may need to do some cleanup.
private static func removeFiles(after sizeLimit: UInt) {
var logSize: UInt = 0
var indexExceedingSizeLimit: Int?
let fileManager = FileManager.default
// Start from console.1.log. Do not consider console.log. It should be almost empty
for index in 1... {
let fileName = "console\(subLogName).\(index).log"
let logFile = logURL(for: fileName)
if fileManager.fileExists(atPath: logFile.path()) {
if let attributes = try? fileManager.attributesOfItem(atPath: logFile.path()), let fileSize = attributes[.size] as? UInt {
logSize += fileSize
}
if logSize >= sizeLimit {
indexExceedingSizeLimit = index
break
}
} else {
break
}
}
let logSizeString = logSize.formatted(.byteCount(style: .binary))
let sizeLimitString = sizeLimit.formatted(.byteCount(style: .binary))
if let indexExceedingSizeLimit {
MXLog.info("removeFilesAfterSizeLimit: Remove files from index \(indexExceedingSizeLimit) because logs are too large (\(logSizeString) for a limit of \(sizeLimitString)\n")
removeExtraFiles(from: UInt(indexExceedingSizeLimit))
} else {
MXLog.info("removeFilesAfterSizeLimit: No need: \(logSizeString) for a limit of \(sizeLimitString)\n")
}
}
}
+53 -126
View File
@@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
// //
import Collections import Foundation
import MatrixRustSDK import MatrixRustSDK
struct OTLPConfiguration { struct OTLPConfiguration {
@@ -23,134 +23,21 @@ struct OTLPConfiguration {
let password: String let password: String
} }
// This exposes the full Rust side tracing subscriber filter for more flexibility.
// We can filter by level, crate and even file. See more details here:
// https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples
struct TracingConfiguration {
enum LogLevel: Codable, Hashable {
case error, warn, info, debug, trace
case custom(String)
var title: String {
switch self {
case .error:
return "Error"
case .warn:
return "Warning"
case .info:
return "Info"
case .debug:
return "Debug"
case .trace:
return "Trace"
case .custom:
return "Custom"
}
}
fileprivate var rawValue: String {
switch self {
case .error:
return "error"
case .warn:
return "warn"
case .info:
return "info"
case .debug:
return "debug"
case .trace:
return "trace"
case .custom(let filter):
return filter
}
}
}
enum Target: String {
case common = ""
case elementx
case hyper, matrix_sdk_ffi, matrix_sdk_crypto
case matrix_sdk_client = "matrix_sdk::client"
case matrix_sdk_crypto_account = "matrix_sdk_crypto::olm::account"
case matrix_sdk_oidc = "matrix_sdk::oidc"
case matrix_sdk_http_client = "matrix_sdk::http_client"
case matrix_sdk_sliding_sync = "matrix_sdk::sliding_sync"
case matrix_sdk_base_sliding_sync = "matrix_sdk_base::sliding_sync"
case matrix_sdk_ui_timeline = "matrix_sdk_ui::timeline"
case matrix_sdk_base_read_receipts = "matrix_sdk_base::read_receipts"
}
static let targets: OrderedDictionary<Target, LogLevel> = [
.common: .info,
.elementx: .info,
.hyper: .warn,
.matrix_sdk_ffi: .info,
.matrix_sdk_client: .trace,
.matrix_sdk_crypto: .debug,
.matrix_sdk_crypto_account: .trace,
.matrix_sdk_oidc: .trace,
.matrix_sdk_http_client: .debug,
.matrix_sdk_sliding_sync: .info,
.matrix_sdk_base_sliding_sync: .info,
.matrix_sdk_ui_timeline: .info,
.matrix_sdk_base_read_receipts: .trace
]
let filter: String
/// Sets the same log level for all Targets
/// - Parameter logLevel: the desired log level
/// - Returns: a custom tracing configuration
init(logLevel: LogLevel) {
if case let .custom(filter) = logLevel {
self.filter = filter
return
}
let overrides = Self.targets.keys.reduce(into: [Target: LogLevel]()) { partialResult, target in
// Keep the defaults here
let ignoredTargets: [Target] = [.common,
.hyper,
.matrix_sdk_ffi,
.matrix_sdk_oidc,
.matrix_sdk_client,
.matrix_sdk_crypto,
.matrix_sdk_crypto_account,
.matrix_sdk_http_client,
.matrix_sdk_base_read_receipts]
if ignoredTargets.contains(target) {
return
}
partialResult[target] = logLevel
}
var newTargets = Self.targets
for (target, logLevel) in overrides {
newTargets.updateValue(logLevel, forKey: target)
}
let components = newTargets.map { (target: Target, logLevel: LogLevel) in
guard !target.rawValue.isEmpty else {
return logLevel.rawValue
}
return "\(target.rawValue)=\(logLevel.rawValue)"
}
filter = components.joined(separator: ",")
}
}
enum RustTracing { enum RustTracing {
private(set) static var currentTracingConfiguration: TracingConfiguration? /// The base filename used for log files. This may be suffixed by the target
/// name and other log management metadata during rotation.
static let filePrefix = "console"
/// The directory that stores all of the log files.
static var logsDirectory: URL { .appGroupContainerDirectory }
private(set) static var currentTracingConfiguration: TracingConfiguration?
static func setup(configuration: TracingConfiguration, otlpConfiguration: OTLPConfiguration?) { static func setup(configuration: TracingConfiguration, otlpConfiguration: OTLPConfiguration?) {
currentTracingConfiguration = configuration currentTracingConfiguration = configuration
// Keep a minimum of 1 week of log files. In reality it will be longer
// as the app is unlikely to be running continuously.
let maxFiles: UInt64 = 24 * 7
if let otlpConfiguration { if let otlpConfiguration {
setupOtlpTracing(config: .init(clientName: "ElementX-iOS", setupOtlpTracing(config: .init(clientName: "ElementX-iOS",
user: otlpConfiguration.username, user: otlpConfiguration.username,
@@ -158,11 +45,51 @@ enum RustTracing {
otlpEndpoint: otlpConfiguration.url, otlpEndpoint: otlpConfiguration.url,
filter: configuration.filter, filter: configuration.filter,
writeToStdoutOrSystem: true, writeToStdoutOrSystem: true,
writeToFiles: nil)) writeToFiles: .init(path: logsDirectory.path(),
filePrefix: configuration.fileName,
fileSuffix: configuration.fileExtension,
maxFiles: maxFiles)))
} else { } else {
setupTracing(config: .init(filter: configuration.filter, setupTracing(config: .init(filter: configuration.filter,
writeToStdoutOrSystem: true, writeToStdoutOrSystem: true,
writeToFiles: nil)) writeToFiles: .init(path: logsDirectory.path(),
filePrefix: configuration.fileName,
fileSuffix: configuration.fileExtension,
maxFiles: maxFiles)))
}
}
/// 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] {
var logFiles = [(url: URL, modificationDate: Date)]()
let fileManager = FileManager.default
let enumerator = fileManager.enumerator(at: logsDirectory, includingPropertiesForKeys: [.contentModificationDateKey])
// Find all *.log files and their modification dates.
while let logURL = enumerator?.nextObject() as? URL {
guard let resourceValues = try? logURL.resourceValues(forKeys: [.contentModificationDateKey]),
let modificationDate = resourceValues.contentModificationDate
else { continue }
if logURL.lastPathComponent.hasPrefix(filePrefix) {
logFiles.append((logURL, modificationDate))
}
}
let sortedFiles = logFiles.sorted { $0.modificationDate > $1.modificationDate }.map(\.url)
MXLog.info("logFiles: \(sortedFiles.map(\.lastPathComponent))")
return sortedFiles
}
/// Delete all log files.
static func deleteLogFiles() {
let fileManager = FileManager.default
for logFileURL in logFiles {
try? fileManager.removeItem(at: logFileURL)
} }
} }
} }
@@ -0,0 +1,151 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Collections
// This exposes the full Rust side tracing subscriber filter for more flexibility.
// We can filter by level, crate and even file. See more details here:
// https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples
struct TracingConfiguration {
enum LogLevel: Codable, Hashable {
case error, warn, info, debug, trace
case custom(String)
var title: String {
switch self {
case .error:
return "Error"
case .warn:
return "Warning"
case .info:
return "Info"
case .debug:
return "Debug"
case .trace:
return "Trace"
case .custom:
return "Custom"
}
}
fileprivate var rawValue: String {
switch self {
case .error:
return "error"
case .warn:
return "warn"
case .info:
return "info"
case .debug:
return "debug"
case .trace:
return "trace"
case .custom(let filter):
return filter
}
}
}
enum Target: String {
case common = ""
case elementx
case hyper, matrix_sdk_ffi, matrix_sdk_crypto
case matrix_sdk_client = "matrix_sdk::client"
case matrix_sdk_crypto_account = "matrix_sdk_crypto::olm::account"
case matrix_sdk_oidc = "matrix_sdk::oidc"
case matrix_sdk_http_client = "matrix_sdk::http_client"
case matrix_sdk_sliding_sync = "matrix_sdk::sliding_sync"
case matrix_sdk_base_sliding_sync = "matrix_sdk_base::sliding_sync"
case matrix_sdk_ui_timeline = "matrix_sdk_ui::timeline"
case matrix_sdk_base_read_receipts = "matrix_sdk_base::read_receipts"
}
static let targets: OrderedDictionary<Target, LogLevel> = [
.common: .info,
.elementx: .info,
.hyper: .warn,
.matrix_sdk_ffi: .info,
.matrix_sdk_client: .trace,
.matrix_sdk_crypto: .debug,
.matrix_sdk_crypto_account: .trace,
.matrix_sdk_oidc: .trace,
.matrix_sdk_http_client: .debug,
.matrix_sdk_sliding_sync: .info,
.matrix_sdk_base_sliding_sync: .info,
.matrix_sdk_ui_timeline: .info,
.matrix_sdk_base_read_receipts: .trace
]
let filter: String
/// The filename that logs should be written to.
let fileName: String
/// The file extension to use for log files.
let fileExtension = "log"
/// Sets the same log level for all Targets
/// - Parameter logLevel: the desired log level
/// - Parameter target: the name of the target being configured
/// - Returns: a custom tracing configuration
init(logLevel: LogLevel, target: String?) {
fileName = if let target {
"\(RustTracing.filePrefix)-\(target)"
} else {
RustTracing.filePrefix
}
if case let .custom(filter) = logLevel {
self.filter = filter
return
}
let overrides = Self.targets.keys.reduce(into: [Target: LogLevel]()) { partialResult, target in
// Keep the defaults here
let ignoredTargets: [Target] = [.common,
.hyper,
.matrix_sdk_ffi,
.matrix_sdk_oidc,
.matrix_sdk_client,
.matrix_sdk_crypto,
.matrix_sdk_crypto_account,
.matrix_sdk_http_client,
.matrix_sdk_base_read_receipts]
if ignoredTargets.contains(target) {
return
}
partialResult[target] = logLevel
}
var newTargets = Self.targets
for (target, logLevel) in overrides {
newTargets.updateValue(logLevel, forKey: target)
}
let components = newTargets.map { (target: Target, logLevel: LogLevel) in
guard !target.rawValue.isEmpty else {
return logLevel.rawValue
}
return "\(target.rawValue)=\(logLevel.rawValue)"
}
filter = components.joined(separator: ",")
}
}
@@ -23,6 +23,7 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie
private let bugReportService: BugReportServiceProtocol private let bugReportService: BugReportServiceProtocol
private let userID: String? private let userID: String?
private let deviceID: String? private let deviceID: String?
private let actionsSubject: PassthroughSubject<BugReportScreenViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<BugReportScreenViewModelAction, Never> = .init()
// periphery:ignore - when set to nil this is automatically cancelled // periphery:ignore - when set to nil this is automatically cancelled
@CancellableTask private var uploadTask: Task<Void, Never>? @CancellableTask private var uploadTask: Task<Void, Never>?
@@ -89,7 +90,6 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie
deviceID: deviceID, deviceID: deviceID,
text: context.reportText, text: context.reportText,
includeLogs: context.sendingLogsEnabled, includeLogs: context.sendingLogsEnabled,
includeCrashLog: true,
canContact: context.canContact, canContact: context.canContact,
githubLabels: [], githubLabels: [],
files: files) files: files)
@@ -19,14 +19,11 @@
import Combine import Combine
import SwiftUI import SwiftUI
struct LogViewerScreenCoordinatorParameters { }
enum LogViewerScreenCoordinatorAction { enum LogViewerScreenCoordinatorAction {
case done case done
} }
final class LogViewerScreenCoordinator: CoordinatorProtocol { final class LogViewerScreenCoordinator: CoordinatorProtocol {
private let parameters: LogViewerScreenCoordinatorParameters
private var viewModel: LogViewerScreenViewModelProtocol private var viewModel: LogViewerScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@@ -35,9 +32,7 @@ final class LogViewerScreenCoordinator: CoordinatorProtocol {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init(parameters: LogViewerScreenCoordinatorParameters) { init() {
self.parameters = parameters
viewModel = LogViewerScreenViewModel() viewModel = LogViewerScreenViewModel()
} }
@@ -27,7 +27,7 @@ class LogViewerScreenViewModel: LogViewerScreenViewModelType, LogViewerScreenVie
} }
init() { init() {
super.init(initialViewState: LogViewerScreenViewState(urls: MXLogger.logFiles)) super.init(initialViewState: LogViewerScreenViewState(urls: RustTracing.logFiles))
} }
// MARK: - Public // MARK: - Public
@@ -143,7 +143,7 @@ private struct LogLevelConfigurationView: View {
if case .custom(let configuration) = logLevel.wrappedValue { if case .custom(let configuration) = logLevel.wrappedValue {
customTracingConfiguration = configuration customTracingConfiguration = configuration
} else { } else {
customTracingConfiguration = TracingConfiguration(logLevel: .info).filter customTracingConfiguration = TracingConfiguration(logLevel: .info, target: nil).filter
} }
} }
@@ -44,9 +44,6 @@ class BugReportService: NSObject, BugReportServiceProtocol {
self.maxUploadSize = maxUploadSize self.maxUploadSize = maxUploadSize
self.session = session self.session = session
super.init() super.init()
// set build version for logger
MXLogger.buildVersion = InfoPlistReader.main.bundleShortVersionString
} }
// MARK: - BugReportServiceProtocol // MARK: - BugReportServiceProtocol
@@ -100,14 +97,12 @@ class BugReportService: NSObject, BugReportServiceProtocol {
self?.lastCrashEventId = event.eventId.sentryIdString self?.lastCrashEventId = event.eventId.sentryIdString
} }
} }
MXLogger.logCrashes(true)
MXLog.info("Started.") MXLog.info("Started.")
} }
func stop() { func stop() {
guard isRunning else { return } guard isRunning else { return }
SentrySDK.close() SentrySDK.close()
MXLogger.logCrashes(false)
MXLog.info("Stopped.") MXLog.info("Stopped.")
} }
@@ -115,7 +110,8 @@ class BugReportService: NSObject, BugReportServiceProtocol {
lastCrashEventId = nil lastCrashEventId = nil
MXLog.info("Reset.") MXLog.info("Reset.")
} }
// swiftlint:disable:next cyclomatic_complexity
func submitBugReport(_ bugReport: BugReport, func submitBugReport(_ bugReport: BugReport,
progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> { progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> {
var params = [ var params = [
@@ -136,11 +132,12 @@ class BugReportService: NSObject, BugReportServiceProtocol {
for label in bugReport.githubLabels { for label in bugReport.githubLabels {
params.append(MultipartFormData(key: "label", type: .text(value: label))) params.append(MultipartFormData(key: "label", type: .text(value: label)))
} }
let logAttachments = await zipFiles(includeLogs: bugReport.includeLogs,
includeCrashLog: bugReport.includeCrashLog)
for url in logAttachments.files { if bugReport.includeLogs {
params.append(MultipartFormData(key: "compressed-log", type: .file(url: url))) let logAttachments = await zipFiles(RustTracing.logFiles)
for url in logAttachments.files {
params.append(MultipartFormData(key: "compressed-log", type: .file(url: url)))
}
} }
if let crashEventId = lastCrashEventId { if let crashEventId = lastCrashEventId {
@@ -197,7 +194,6 @@ class BugReportService: NSObject, BugReportServiceProtocol {
let uploadResponse = try decoder.decode(SubmitBugReportResponse.self, from: data) let uploadResponse = try decoder.decode(SubmitBugReportResponse.self, from: data)
if !uploadResponse.reportUrl.isEmpty { if !uploadResponse.reportUrl.isEmpty {
MXLogger.deleteCrashLog()
lastCrashEventId = nil lastCrashEventId = nil
} }
@@ -243,21 +239,12 @@ class BugReportService: NSObject, BugReportServiceProtocol {
"\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)" "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
} }
private func zipFiles(includeLogs: Bool, private func zipFiles(_ logFiles: [URL]) async -> Logs {
includeCrashLog: Bool) async -> Logs { MXLog.info("zipFiles")
MXLog.info("zipFiles: includeLogs: \(includeLogs), includeCrashLog: \(includeCrashLog)")
var filesToCompress: [URL] = []
if includeLogs {
filesToCompress.append(contentsOf: MXLogger.logFiles)
}
if includeCrashLog, let crashLogFile = MXLogger.crashLog {
filesToCompress.append(crashLogFile)
}
var compressedLogs = Logs(maxFileSize: maxUploadSize) var compressedLogs = Logs(maxFileSize: maxUploadSize)
for url in filesToCompress { for url in logFiles {
do { do {
try attachFile(at: url, to: &compressedLogs) try attachFile(at: url, to: &compressedLogs)
} catch { } catch {
@@ -275,23 +262,15 @@ class BugReportService: NSObject, BugReportServiceProtocol {
private func attachFile(at url: URL, to zippedFiles: inout Logs) throws { private func attachFile(at url: URL, to zippedFiles: inout Logs) throws {
let fileHandle = try FileHandle(forReadingFrom: url) let fileHandle = try FileHandle(forReadingFrom: url)
var chunkIndex = -1 while let data = try fileHandle.readToEnd() {
while let data = try fileHandle.read(upToCount: 10 * 1024 * 1024) { if let zippedData = (data as NSData).gzipped() {
do { let zippedURL = URL.temporaryDirectory.appending(path: url.lastPathComponent)
chunkIndex += 1
if let zippedData = (data as NSData).gzipped() { // Remove old zipped file if exists
let zippedFilename = url.deletingPathExtension().lastPathComponent + "_\(chunkIndex).log" try? FileManager.default.removeItem(at: zippedURL)
let chunkURL = URL.temporaryDirectory.appending(path: zippedFilename)
try zippedData.write(to: zippedURL)
// Remove old zipped file if exists zippedFiles.appendFile(at: zippedURL, zippedSize: zippedData.count, originalSize: data.count)
try? FileManager.default.removeItem(at: chunkURL)
try zippedData.write(to: chunkURL)
zippedFiles.appendFile(at: chunkURL, zippedSize: zippedData.count, originalSize: data.count)
}
} catch {
MXLog.error("Failed attaching log chunk \(chunkIndex) from (\(url.lastPathComponent)")
continue
} }
} }
} }
@@ -23,7 +23,6 @@ struct BugReport: Equatable {
let deviceID: String? let deviceID: String?
let text: String let text: String
let includeLogs: Bool let includeLogs: Bool
let includeCrashLog: Bool
let canContact: Bool let canContact: Bool
let githubLabels: [String] let githubLabels: [String]
let files: [URL] let files: [URL]
@@ -39,7 +39,6 @@ class BugReportServiceTests: XCTestCase {
deviceID: nil, deviceID: nil,
text: "i cannot send message", text: "i cannot send message",
includeLogs: true, includeLogs: true,
includeCrashLog: true,
canContact: false, canContact: false,
githubLabels: [], githubLabels: [],
files: []) files: [])
@@ -70,7 +69,6 @@ class BugReportServiceTests: XCTestCase {
deviceID: nil, deviceID: nil,
text: "i cannot send message", text: "i cannot send message",
includeLogs: true, includeLogs: true,
includeCrashLog: true,
canContact: false, canContact: false,
githubLabels: [], githubLabels: [],
files: []) files: [])
@@ -85,7 +85,13 @@ class BugReportViewModelTests: XCTestCase {
try await deferred.fulfill() try await deferred.fulfill()
XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1) XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1)
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, canContact: false, githubLabels: [], files: [])) XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.userID, "@mock.client.com")
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.deviceID, nil)
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.text, "")
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.includeLogs, true)
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.canContact, false)
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.githubLabels, [])
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.files, [])
} }
func testSendReportWithError() async throws { func testSendReportWithError() async throws {
@@ -112,6 +118,12 @@ class BugReportViewModelTests: XCTestCase {
try await deferred.fulfill() try await deferred.fulfill()
XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1) XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1)
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, canContact: false, githubLabels: [], files: [])) XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.userID, "@mock.client.com")
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.deviceID, nil)
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.text, "")
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.includeLogs, true)
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.canContact, false)
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.githubLabels, [])
XCTAssertEqual(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport.files, [])
} }
} }
+122 -214
View File
@@ -24,202 +24,65 @@ class LoggingTests: XCTestCase {
} }
override func setUpWithError() throws { override func setUpWithError() throws {
// Force MXLogger to flush stderr and clean up after itself RustTracing.deleteLogFiles()
MXLog.configure(logLevel: .info, redirectToFiles: false)
MXLogger.deleteLogFiles()
} }
override func tearDownWithError() throws { func testLogging() async throws {
// Put teardown code here. This method is called after the invocation of each test method in the class. let target = "tests"
} XCTAssertTrue(RustTracing.logFiles.isEmpty)
func testFileLogging() throws {
XCTAssertTrue(MXLogger.logFiles.isEmpty)
let log = UUID().uuidString
MXLog.configure(logLevel: .info, redirectToFiles: true)
MXLog.info(log) MXLog.configure(target: target, logLevel: .info)
guard let logFile = MXLogger.logFiles.first else {
// There is something weird with Rust logging where the file writing handle doesn't
// notice that the file it is writing to was deleted, so we can't run these checks
// as separate tests. So instead we need to make sure we run all the tests that
// write logs in this single test case after configuring the log system.
try validateFileLogging()
try validateLogLevels()
try validateTargetName(target)
try validateRoomSummaryContentIsRedacted()
try validateTimelineContentIsRedacted()
try validateRustMessageContentIsRedacted()
}
func validateFileLogging() throws {
let infoLog = UUID().uuidString
MXLog.info(infoLog)
guard let logFile = RustTracing.logFiles.first else {
XCTFail(Constants.genericFailure) XCTFail(Constants.genericFailure)
return return
} }
let content = try String(contentsOf: logFile) try XCTAssertTrue(String(contentsOf: logFile).contains(infoLog))
XCTAssert(content.contains(log))
} }
func testFileRotationOnLaunch() throws {
// Given a fresh launch with no logs.
XCTAssertTrue(MXLogger.logFiles.isEmpty)
// When launching the app 5 times. func validateLogLevels() throws {
let launchCount = 5 let verboseLog = UUID().uuidString
for index in 0..<launchCount {
MXLog.configure(logLevel: .info, redirectToFiles: true)
MXLog.info("Launch \(index + 1)")
}
// Then 5 log files should be created each with the correct contents. MXLog.verbose(verboseLog)
let logFiles = MXLogger.logFiles guard let logFile = RustTracing.logFiles.first else {
XCTAssertEqual(logFiles.count, launchCount, "The number of log files should match the number of launches.")
try verifyContents(of: logFiles, after: launchCount)
}
func testMaxLogFileCount() throws {
// Given a fresh launch with no logs.
XCTAssertTrue(MXLogger.logFiles.isEmpty)
// When launching the app 10 times, with a maxLogCount of 5.
let launchCount = 10
let logFileCount = 5
for index in 0..<launchCount {
MXLog.configure(logLevel: .info, redirectToFiles: true, maxLogFileCount: UInt(logFileCount))
MXLog.info("Launch \(index + 1)")
}
// Then only 5 log files should be stored on disk, with the contents of launches 6 to 10.
let logFiles = MXLogger.logFiles
XCTAssertEqual(logFiles.count, logFileCount, "The number of log files should match the number of launches.")
try verifyContents(of: logFiles, after: launchCount)
}
func testLogFileSizeLimit() throws {
// Given a fresh launch with no logs.
XCTAssertTrue(MXLogger.logFiles.isEmpty)
// When launching the app 10 times, with a max total log size of 25KB and logging ~5KB data each time.
let launchCount = 10
let logFileSizeLimit: UInt = 25 * 1024
for index in 0..<launchCount {
MXLog.configure(logLevel: .info, redirectToFiles: true, logFileSizeLimit: logFileSizeLimit)
MXLog.info("Launch \(index + 1)")
// Add ~5KB of logs
for _ in 0..<5 {
let string = [String](repeating: "a", count: 1024).joined()
MXLog.info(string)
}
}
// Then only the most recent log files should be stored on disk.
let logFiles = MXLogger.logFiles
XCTAssertGreaterThan(logFiles.count, 0, "There should be at least one log file created.")
XCTAssertLessThan(logFiles.count, launchCount, "Some of the log files should have been removed trimmed.")
try verifyContents(of: logFiles, after: launchCount)
}
/// Verifies that the log files all contain the correct `Launch #` based on the index
/// in the file name and the number of launches of the app.
func verifyContents(of logFiles: [URL], after launchCount: Int) throws {
for logFile in logFiles {
let regex = /\d+/
let fileIndex = logFile.lastPathComponent.firstMatch(of: regex)?.0 ?? "0"
guard let index = Int(fileIndex) else {
XCTFail(Constants.genericFailure)
return
}
let content = try String(contentsOf: logFile)
XCTAssertTrue(content.contains("Launch \(launchCount - index)"), "The log files should be for the most recent launches in reverse chronological order.")
}
}
func testLogLevels() throws {
XCTAssert(MXLogger.logFiles.isEmpty)
let log = UUID().uuidString
MXLog.configure(logLevel: .info, redirectToFiles: true)
MXLog.verbose(log)
guard let logFile = MXLogger.logFiles.first else {
XCTFail(Constants.genericFailure) XCTFail(Constants.genericFailure)
return return
} }
let content = try String(contentsOf: logFile)
XCTAssertFalse(content.contains(log))
}
func testSubLogName() {
XCTAssert(MXLogger.logFiles.isEmpty)
let target = "nse"
MXLog.configure(target: target, logLevel: .info, redirectToFiles: true)
try XCTAssertFalse(String(contentsOf: logFile).contains(verboseLog))
}
func validateTargetName(_ target: String) throws {
MXLog.info(UUID().uuidString) MXLog.info(UUID().uuidString)
guard let logFile = MXLogger.logFiles.first else { guard let logFile = RustTracing.logFiles.first else {
XCTFail(Constants.genericFailure) XCTFail(Constants.genericFailure)
return return
} }
XCTAssertTrue(logFile.lastPathComponent.contains(target)) XCTAssertTrue(logFile.lastPathComponent.contains(target))
} }
func testLogFileSorting() async throws {
// Given a collection of log files.
XCTAssertTrue(MXLogger.logFiles.isEmpty)
// When creating new logs. func validateRoomSummaryContentIsRedacted() throws {
let logsFileDirectory = URL.appGroupContainerDirectory
for i in 1...5 {
let filename = "console.\(i).log"
try "console".write(to: logsFileDirectory.appending(path: filename), atomically: true, encoding: .utf8)
}
for i in 1...5 {
let nseFilename = "console-nse.\(i).log"
try "nse".write(to: logsFileDirectory.appending(path: nseFilename), atomically: true, encoding: .utf8)
}
// Then the logs should be sorted chronologically (newest first) and not alphabetically.
XCTAssertEqual(MXLogger.logFiles.map(\.lastPathComponent),
["console-nse.5.log",
"console-nse.4.log",
"console-nse.3.log",
"console-nse.2.log",
"console-nse.1.log",
"console.5.log",
"console.4.log",
"console.3.log",
"console.2.log",
"console.1.log"])
// When updating the oldest log file.
let currentLogFile = logsFileDirectory.appending(path: "console.1.log")
let fileHandle = try FileHandle(forWritingTo: currentLogFile)
try fileHandle.seekToEnd()
guard let newLineData = "newline".data(using: .utf8) else {
XCTFail("Couldn't create data to write to disk.")
return
}
try fileHandle.write(contentsOf: newLineData)
try fileHandle.close()
// Then that file should now be the first log file.
XCTAssertEqual(MXLogger.logFiles.map(\.lastPathComponent),
["console.1.log",
"console-nse.5.log",
"console-nse.4.log",
"console-nse.3.log",
"console-nse.2.log",
"console-nse.1.log",
"console.5.log",
"console.4.log",
"console.3.log",
"console.2.log"])
}
func testRoomSummaryContentIsRedacted() throws {
// Given a room summary that contains sensitive information // Given a room summary that contains sensitive information
let roomName = "Private Conversation" let roomName = "Private Conversation"
let lastMessage = "Secret information" let lastMessage = "Secret information"
@@ -240,14 +103,10 @@ class LoggingTests: XCTestCase {
isFavourite: false) isFavourite: false)
// When logging that value // When logging that value
XCTAssert(MXLogger.logFiles.isEmpty)
MXLog.configure(logLevel: .info, redirectToFiles: true)
MXLog.info(roomSummary) MXLog.info(roomSummary)
// Then the log file should not include the sensitive information // Then the log file should not include the sensitive information
guard let logFile = MXLogger.logFiles.first else { guard let logFile = RustTracing.logFiles.first else {
XCTFail(Constants.genericFailure) XCTFail(Constants.genericFailure)
return return
} }
@@ -257,8 +116,8 @@ class LoggingTests: XCTestCase {
XCTAssertFalse(content.contains(roomName)) XCTAssertFalse(content.contains(roomName))
XCTAssertFalse(content.contains(lastMessage)) XCTAssertFalse(content.contains(lastMessage))
} }
func testTimelineContentIsRedacted() throws { func validateTimelineContentIsRedacted() throws {
// Given timeline items that contain text // Given timeline items that contain text
let textAttributedString = "TextAttributed" let textAttributedString = "TextAttributed"
let textMessage = TextRoomTimelineItem(id: .random, let textMessage = TextRoomTimelineItem(id: .random,
@@ -313,9 +172,7 @@ class LoggingTests: XCTestCase {
content: .init(body: "FileString", source: nil, thumbnailSource: nil, contentType: nil)) content: .init(body: "FileString", source: nil, thumbnailSource: nil, contentType: nil))
// When logging that value // When logging that value
XCTAssert(MXLogger.logFiles.isEmpty) MXLog.configure(logLevel: .info)
MXLog.configure(logLevel: .info, redirectToFiles: true)
MXLog.info(textMessage) MXLog.info(textMessage)
MXLog.info(noticeMessage) MXLog.info(noticeMessage)
@@ -325,11 +182,11 @@ class LoggingTests: XCTestCase {
MXLog.info(fileMessage) MXLog.info(fileMessage)
// Then the log file should not include the text content // Then the log file should not include the text content
guard let logFile = MXLogger.logFiles.first else { guard let logFile = RustTracing.logFiles.first else {
XCTFail(Constants.genericFailure) XCTFail(Constants.genericFailure)
return return
} }
let content = try String(contentsOf: logFile) let content = try String(contentsOf: logFile)
XCTAssertTrue(content.contains(textMessage.id.timelineID)) XCTAssertTrue(content.contains(textMessage.id.timelineID))
XCTAssertFalse(content.contains(textMessage.body)) XCTAssertFalse(content.contains(textMessage.body))
@@ -352,38 +209,34 @@ class LoggingTests: XCTestCase {
XCTAssertTrue(content.contains(fileMessage.id.timelineID)) XCTAssertTrue(content.contains(fileMessage.id.timelineID))
XCTAssertFalse(content.contains(fileMessage.body)) XCTAssertFalse(content.contains(fileMessage.body))
} }
func testRustMessageContentIsRedacted() throws { func validateRustMessageContentIsRedacted() throws {
// Given message content that contain text // Given message content that contain text
let textString = "TextString" let textString = "TextString"
let textMessage = TextMessageContent(body: "", let rustTextMessage = TextMessageContent(body: "",
formatted: FormattedBody(format: .html, body: "<b>\(textString)</b>")) formatted: FormattedBody(format: .html, body: "<b>\(textString)</b>"))
let noticeString = "NoticeString" let noticeString = "NoticeString"
let noticeMessage = NoticeMessageContent(body: noticeString, let rustNoticeMessage = NoticeMessageContent(body: noticeString,
formatted: FormattedBody(format: .html, body: "<b>\(noticeString)</b>")) formatted: FormattedBody(format: .html, body: "<b>\(noticeString)</b>"))
let emoteString = "EmoteString" let emoteString = "EmoteString"
let emoteMessage = EmoteMessageContent(body: emoteString, let rustEmoteMessage = EmoteMessageContent(body: emoteString,
formatted: FormattedBody(format: .html, body: "<b>\(emoteString)</b>")) formatted: FormattedBody(format: .html, body: "<b>\(emoteString)</b>"))
let pointer = Unmanaged.passRetained(NSURL(fileURLWithPath: "/tmp/file")).toOpaque() let pointer = Unmanaged.passRetained(NSURL(fileURLWithPath: "/tmp/file")).toOpaque()
let imageMessage = ImageMessageContent(body: "ImageString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil) let rustImageMessage = ImageMessageContent(body: "ImageString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil)
let videoMessage = VideoMessageContent(body: "VideoString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil) let rustVideoMessage = VideoMessageContent(body: "VideoString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil)
let fileMessage = FileMessageContent(body: "FileString", filename: "FileName", source: MediaSource(unsafeFromRawPointer: pointer), info: nil) let rustFileMessage = FileMessageContent(body: "FileString", filename: "FileName", source: MediaSource(unsafeFromRawPointer: pointer), info: nil)
// When logging that value // When logging that value
XCTAssert(MXLogger.logFiles.isEmpty) MXLog.info(rustTextMessage)
MXLog.info(rustNoticeMessage)
MXLog.configure(logLevel: .info, redirectToFiles: true) MXLog.info(rustEmoteMessage)
MXLog.info(rustImageMessage)
MXLog.info(textMessage) MXLog.info(rustVideoMessage)
MXLog.info(noticeMessage) MXLog.info(rustFileMessage)
MXLog.info(emoteMessage)
MXLog.info(imageMessage)
MXLog.info(videoMessage)
MXLog.info(fileMessage)
// Then the log file should not include the text content // Then the log file should not include the text content
guard let logFile = MXLogger.logFiles.first else { guard let logFile = RustTracing.logFiles.first else {
XCTFail(Constants.genericFailure) XCTFail(Constants.genericFailure)
return return
} }
@@ -399,12 +252,67 @@ class LoggingTests: XCTestCase {
XCTAssertFalse(content.contains(emoteString)) XCTAssertFalse(content.contains(emoteString))
XCTAssertTrue(content.contains(String(describing: ImageMessageContent.self))) XCTAssertTrue(content.contains(String(describing: ImageMessageContent.self)))
XCTAssertFalse(content.contains(imageMessage.body)) XCTAssertFalse(content.contains(rustImageMessage.body))
XCTAssertTrue(content.contains(String(describing: VideoMessageContent.self))) XCTAssertTrue(content.contains(String(describing: VideoMessageContent.self)))
XCTAssertFalse(content.contains(videoMessage.body)) XCTAssertFalse(content.contains(rustVideoMessage.body))
XCTAssertTrue(content.contains(String(describing: FileMessageContent.self))) XCTAssertTrue(content.contains(String(describing: FileMessageContent.self)))
XCTAssertFalse(content.contains(fileMessage.body)) XCTAssertFalse(content.contains(rustFileMessage.body))
}
func testLogFileSorting() async throws {
// Given a collection of log files.
XCTAssertTrue(RustTracing.logFiles.isEmpty)
// When creating new logs.
let logsFileDirectory = RustTracing.logsDirectory
for i in 1...5 {
let filename = "console.\(i).log"
try "console".write(to: logsFileDirectory.appending(path: filename), atomically: true, encoding: .utf8)
}
for i in 1...5 {
let nseFilename = "console-nse.\(i).log"
try "nse".write(to: logsFileDirectory.appending(path: nseFilename), atomically: true, encoding: .utf8)
}
// Then the logs should be sorted chronologically (newest first) and not alphabetically.
XCTAssertEqual(RustTracing.logFiles.map(\.lastPathComponent),
["console-nse.5.log",
"console-nse.4.log",
"console-nse.3.log",
"console-nse.2.log",
"console-nse.1.log",
"console.5.log",
"console.4.log",
"console.3.log",
"console.2.log",
"console.1.log"])
// When updating the oldest log file.
let currentLogFile = logsFileDirectory.appending(path: "console.1.log")
let fileHandle = try FileHandle(forWritingTo: currentLogFile)
try fileHandle.seekToEnd()
guard let newLineData = "newline".data(using: .utf8) else {
XCTFail("Couldn't create data to write to disk.")
return
}
try fileHandle.write(contentsOf: newLineData)
try fileHandle.close()
// Then that file should now be the first log file.
XCTAssertEqual(RustTracing.logFiles.map(\.lastPathComponent),
["console.1.log",
"console-nse.5.log",
"console-nse.4.log",
"console-nse.3.log",
"console-nse.2.log",
"console-nse.1.log",
"console.5.log",
"console.4.log",
"console.3.log",
"console.2.log"])
} }
} }
@@ -20,7 +20,7 @@ import XCTest
class TracingConfigurationTests: XCTestCase { class TracingConfigurationTests: XCTestCase {
func testConfiguration() { func testConfiguration() {
let configuration = TracingConfiguration(logLevel: .trace) let configuration = TracingConfiguration(logLevel: .trace, target: nil)
let filterComponents = configuration.filter.components(separatedBy: ",") let filterComponents = configuration.filter.components(separatedBy: ",")
XCTAssertEqual(filterComponents.first, "info") XCTAssertEqual(filterComponents.first, "info")
+1
View File
@@ -0,0 +1 @@
Handover log file management to Rust.
+1 -1
View File
@@ -48,7 +48,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.1.42 exactVersion: 1.1.43
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
Compound: Compound:
url: https://github.com/element-hq/compound-ios url: https://github.com/element-hq/compound-ios