From ba684d8efb502d0b65cc782a990c588ba54c6a4b Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:02:01 +0000 Subject: [PATCH] #166: Re-write MXLogger in Swift * Add logging tests that cover file rotation. * Re-write MXLogger in Swift. --- ElementX.xcodeproj/project.pbxproj | 26 +- ElementX/Sources/Other/Logging/MXLog.h | 49 --- .../Sources/Other/Logging/MXLogObjcWrapper.h | 40 -- .../Sources/Other/Logging/MXLogObjcWrapper.m | 52 --- ElementX/Sources/Other/Logging/MXLogger.h | 114 ----- ElementX/Sources/Other/Logging/MXLogger.m | 392 ------------------ ElementX/Sources/Other/Logging/MXLogger.swift | 327 +++++++++++++++ .../Services/BugReport/BugReportService.swift | 11 +- .../Services/Client/MockClientProxy.swift | 1 + .../Sources/Services/Room/MockRoomProxy.swift | 1 + .../Timeline/RoomTimelineProvider.swift | 1 + .../Services/Timeline/TimelineItemProxy.swift | 1 + .../ElementX-Bridging-Header.h | 1 - UnitTests/Sources/LoggingTests.swift | 123 ++++-- changelog.d/166.change | 1 + 15 files changed, 443 insertions(+), 697 deletions(-) delete mode 100644 ElementX/Sources/Other/Logging/MXLog.h delete mode 100644 ElementX/Sources/Other/Logging/MXLogObjcWrapper.h delete mode 100644 ElementX/Sources/Other/Logging/MXLogObjcWrapper.m delete mode 100644 ElementX/Sources/Other/Logging/MXLogger.h delete mode 100644 ElementX/Sources/Other/Logging/MXLogger.m create mode 100644 ElementX/Sources/Other/Logging/MXLogger.swift create mode 100644 changelog.d/166.change diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index f66d40726..6dd7c8c58 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -74,7 +74,6 @@ 297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */; }; 29E20505F321071E8375F99B /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B3B811C2B900F12C6F695 /* BuildSettings.swift */; }; 29EE1791E0AFA1ABB7F23D2F /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */; }; - 2A90D9F91A836E30B7D78838 /* MXLogObjcWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */; }; 2B9AEEC12B1BBE5BD61D0F5E /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3429142FE11930422E7CC1A0 /* UserSessionFlowCoordinatorStateMachine.swift */; }; 2BA59D0AEFB4B82A2EC2A326 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; }; 2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; }; @@ -139,10 +138,10 @@ 563A05B43207D00A6B698211 /* OIDCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9010EE0CC913D095887EF36E /* OIDCService.swift */; }; 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; }; 5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; }; + 5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; }; 5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */; }; 5D7960B32C350FA93F48D02B /* OnboardingModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB33A751BFDA223BDD106EC0 /* OnboardingModels.swift */; }; 5D9F0695DC6C0057F85C12B6 /* UserNotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113CA0A67B4AA227AAFB63B /* UserNotificationController.swift */; }; - 5DA73F4329252C3A00651C22 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA73F4229252C3A00651C22 /* TimelineDeliveryStatusView.swift */; }; 5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; }; 5E540CAEF764D7FBD8D80776 /* VideoPlayerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3FC45B7643298BF361CEB1 /* VideoPlayerModels.swift */; }; 5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B80895CE021B49847BD7D74 /* TemplateViewModelProtocol.swift */; }; @@ -288,6 +287,7 @@ B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; }; B5111BAF5F601C139EBBD8BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; }; B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */; }; + B66757D0254843162595B25D /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; }; B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; }; B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; }; B6F92EBE04D4AABF30B9E73A /* AnalyticsPromptModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8BA82CF99D843FEF680E91 /* AnalyticsPromptModels.swift */; }; @@ -297,7 +297,6 @@ BB01CC19C3D3322308D1B2CF /* ServerSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167521635A1CC27624FCEB7F /* ServerSelectionViewModel.swift */; }; BB4C6F362F75933DDDE30F3E /* InfoPlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A901D95158B02CA96C79C7F /* InfoPlist.swift */; }; BB6B0B91CE11E06330017000 /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC1A01C3A745BDF1D3697D3 /* SessionVerificationScreen.swift */; }; - BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = EF188681D6B6068CFAEAFC3F /* MXLogger.m */; }; BCEC41FB1F2BB663183863E4 /* LoginServerInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D379E13DD9D987470A3C70C /* LoginServerInfoSection.swift */; }; BFD1AC03B6F8C5F5897D5B55 /* ReversedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DE30233B57761F8AFEB415 /* ReversedScrollView.swift */; }; C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */; }; @@ -409,7 +408,6 @@ 04BBC9E08250EF92ADE89CFD /* sr-Latn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sr-Latn"; path = "sr-Latn.lproj/Localizable.strings"; sourceTree = ""; }; 054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionUITests.swift; sourceTree = ""; }; 057B747CF045D3C6C30EAB2C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = ""; }; - 0776771332259AB1C9661430 /* MXLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLog.h; sourceTree = ""; }; 077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = ""; }; 086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = ""; }; 08F64963396A6A23538EFCEC /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = is; path = is.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -515,7 +513,6 @@ 39EBB6903EFD4236B8D11A42 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-CA"; path = "fr-CA.lproj/Localizable.stringsdict"; sourceTree = ""; }; 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = ""; }; 3B5B535DA49C54523FF7A412 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nn; path = nn.lproj/Localizable.strings; sourceTree = ""; }; - 3BFEC43A1A0769F1EAC62873 /* MXLogObjcWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLogObjcWrapper.h; sourceTree = ""; }; 3CDF9E55650D6035D6536538 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "nb-NO"; path = "nb-NO.lproj/Localizable.stringsdict"; sourceTree = ""; }; 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = ""; }; 3D4DD336905C72F95EAF34B7 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = ""; }; @@ -571,18 +568,15 @@ 536E72DCBEEC4A1FE66CFDCE /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; 541542F5AC323709D8563458 /* AnalyticsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPrompt.swift; sourceTree = ""; }; 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = ""; }; - 54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogObjcWrapper.m; sourceTree = ""; }; 55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = ""; }; 55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = ""; }; 55EA4B03F92F31EAA83B3F7B /* FilePreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewModels.swift; sourceTree = ""; }; 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = ""; }; 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedRoomTimelineView.swift; sourceTree = ""; }; 5773C86AF04AEF26515AD00C /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = ""; }; - 5872785B9C7934940146BFBA /* MXLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLogger.h; sourceTree = ""; }; 5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelProtocol.swift; sourceTree = ""; }; 5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = ""; }; 5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinatorUITests.swift; sourceTree = ""; }; - 5DA73F4229252C3A00651C22 /* TimelineDeliveryStatusView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineDeliveryStatusView.swift; sourceTree = ""; }; 5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = ""; }; 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = ""; }; 5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = ""; }; @@ -699,6 +693,7 @@ A2B6433F516F1E6DFA0E2D89 /* vls */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vls; path = vls.lproj/Localizable.strings; sourceTree = ""; }; A3004DFA1B10951962787D90 /* VideoPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModelTests.swift; sourceTree = ""; }; A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = ""; }; + A34A814CBD56230BC74FFCF4 /* MXLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLogger.swift; sourceTree = ""; }; A40C19719687984FD9478FBE /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = ""; }; A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; @@ -801,6 +796,7 @@ D31DC8105C6233E5FFD9B84C /* element-x-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "element-x-ios"; path = .; sourceTree = SOURCE_ROOT; }; D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; + D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineDeliveryStatusView.swift; sourceTree = ""; }; D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = ""; }; D67CBAFA48ED0B6FCE74F88F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; }; D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = ""; }; @@ -844,7 +840,6 @@ EEAF1C75771D9DC75877F4B4 /* MessageTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTimelineItem.swift; sourceTree = ""; }; EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactoryProtocol.swift; sourceTree = ""; }; EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = ""; }; - EF188681D6B6068CFAEAFC3F /* MXLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = ""; }; EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = ""; }; EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = ""; }; F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; @@ -943,12 +938,8 @@ 06501F0E978B2D5C92771DC7 /* Logging */ = { isa = PBXGroup; children = ( - 0776771332259AB1C9661430 /* MXLog.h */, 111B698739E3410E2CDB7144 /* MXLog.swift */, - 5872785B9C7934940146BFBA /* MXLogger.h */, - EF188681D6B6068CFAEAFC3F /* MXLogger.m */, - 3BFEC43A1A0769F1EAC62873 /* MXLogObjcWrapper.h */, - 54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */, + A34A814CBD56230BC74FFCF4 /* MXLogger.swift */, ); path = Logging; sourceTree = ""; @@ -1832,7 +1823,7 @@ CCD48459CA34A1928EC7A26A /* Supplementary */ = { isa = PBXGroup; children = ( - 5DA73F4229252C3A00651C22 /* TimelineDeliveryStatusView.swift */, + D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */, 351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */, ); path = Supplementary; @@ -2531,14 +2522,12 @@ 49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */, 2F30EFEB7BD39242D1AD96F3 /* LoginViewModelProtocol.swift in Sources */, B94368839BDB69172E28E245 /* MXLog.swift in Sources */, - 2A90D9F91A836E30B7D78838 /* MXLogObjcWrapper.m in Sources */, - BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */, + B66757D0254843162595B25D /* MXLogger.swift in Sources */, 67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */, 656427D3C59554E03ECD898E /* MediaPlayerCoordinator.swift in Sources */, 485A7A97076C7D19104BDC1D /* MediaPlayerModels.swift in Sources */, 8D332A24CD23B4216E33EC5C /* MediaPlayerScreen.swift in Sources */, 46F8817A235DC41228128BE7 /* MediaPlayerViewModel.swift in Sources */, - 5DA73F4329252C3A00651C22 /* TimelineDeliveryStatusView.swift in Sources */, 80997E933A5B2C0868D80B45 /* MediaPlayerViewModelProtocol.swift in Sources */, EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */, 7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */, @@ -2646,6 +2635,7 @@ D85D4FA590305180B4A41795 /* Tests.swift in Sources */, 7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */, 5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */, + 5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */, 157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */, 01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */, 4D970CB606276717B43E2332 /* TimelineItemList.swift in Sources */, diff --git a/ElementX/Sources/Other/Logging/MXLog.h b/ElementX/Sources/Other/Logging/MXLog.h deleted file mode 100644 index cfb92f662..000000000 --- a/ElementX/Sources/Other/Logging/MXLog.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright 2021 The Matrix.org Foundation C.I.C -// -// 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 "MXLogObjcWrapper.h" - -#define MXLogVerbose(message, ...) { \ - [MXLogObjcWrapper logVerbose:[NSString stringWithFormat: message, ##__VA_ARGS__] file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__]; \ -} - -#define MXLogDebug(message, ...) { \ - [MXLogObjcWrapper logDebug:[NSString stringWithFormat: message, ##__VA_ARGS__] file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__]; \ -} - -#define MXLogInfo(message, ...) { \ - [MXLogObjcWrapper logInfo:[NSString stringWithFormat: message, ##__VA_ARGS__] file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__]; \ -} - -#define MXLogWarning(message, ...) { \ - [MXLogObjcWrapper logWarning:[NSString stringWithFormat: message, ##__VA_ARGS__] file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__]; \ -} - -#define MXLogError(message) { \ - [MXLogObjcWrapper logError:message file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__ context:nil]; \ -} - -#define MXLogErrorDetails(message, details) { \ - [MXLogObjcWrapper logError:message file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__ context:details]; \ -} - -#define MXLogFailure(message) { \ - [MXLogObjcWrapper logFailure:message file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__ context:nil]; \ -} - -#define MXLogFailureDetails(message, details) { \ - [MXLogObjcWrapper logFailure:message file:@__FILE__ function:[NSString stringWithFormat:@"%s", __FUNCTION__] line:__LINE__ context:details]; \ -} diff --git a/ElementX/Sources/Other/Logging/MXLogObjcWrapper.h b/ElementX/Sources/Other/Logging/MXLogObjcWrapper.h deleted file mode 100644 index 60dd29d11..000000000 --- a/ElementX/Sources/Other/Logging/MXLogObjcWrapper.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright 2021 The Matrix.org Foundation C.I.C -// -// 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 - -NS_ASSUME_NONNULL_BEGIN - -/** - An `MXLog` Objective-C wrapper used to allow calling Swift methods from the variadic macros defined in `MXLog.h` - */ -@interface MXLogObjcWrapper : NSObject - -+ (void)logVerbose:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line; - -+ (void)logDebug:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line; - -+ (void)logInfo:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line; - -+ (void)logWarning:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line; - -+ (void)logError:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line context:(nullable id)context; - -+ (void)logFailure:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line context:(nullable id)context; - -@end - -NS_ASSUME_NONNULL_END diff --git a/ElementX/Sources/Other/Logging/MXLogObjcWrapper.m b/ElementX/Sources/Other/Logging/MXLogObjcWrapper.m deleted file mode 100644 index 9948431d0..000000000 --- a/ElementX/Sources/Other/Logging/MXLogObjcWrapper.m +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright 2021 The Matrix.org Foundation C.I.C -// -// 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 "MXLogObjcWrapper.h" -#import "GeneratedInterface-Swift.h" - -@implementation MXLogObjcWrapper - -+ (void)logVerbose:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line -{ - [MXLog logVerbose:message file:file function:function line:line]; -} - -+ (void)logDebug:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line -{ - [MXLog logDebug:message file:file function:function line:line]; -} - -+ (void)logInfo:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line -{ - [MXLog logInfo:message file:file function:function line:line]; -} - -+ (void)logWarning:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line -{ - [MXLog logWarning:message file:file function:function line:line]; -} - -+ (void)logError:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line context:(id)context -{ - [MXLog logError:message file:file function:function line:line context:context]; -} - -+ (void)logFailure:(NSString *)message file:(NSString *)file function:(NSString *)function line:(NSUInteger)line context:(id)context -{ - [MXLog logFailure:message file:file function:function line:line context:context]; -} - -@end diff --git a/ElementX/Sources/Other/Logging/MXLogger.h b/ElementX/Sources/Other/Logging/MXLogger.h deleted file mode 100644 index 56eebc26d..000000000 --- a/ElementX/Sources/Other/Logging/MXLogger.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - Copyright 2015 OpenMarket Ltd - Copyright 2020 The Matrix.org Foundation C.I.C - - 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 - -/** - The `MXLogger` tool redirects NSLog output into a fixed pool of files. - Another log file is used every time [MXLogger redirectNSLogToFiles:YES] - is called. The pool contains 3 files. - - `MXLogger` can track and log uncatched exceptions or crashes. - */ -@interface MXLogger : NSObject - -/** - 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. - - @param redirectNSLogToFiles YES to enable the redirection. - */ -+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles; - -/** - 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. - - @param redirectNSLogToFiles YES to enable the redirection. - @param numberOfFiles number of files to keep (default is 10). - */ -+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles; - -/** - Redirect NSLog output to MXLogger files. - - @param redirectNSLogToFiles YES to enable the redirection. - @param numberOfFiles number of files to keep (default is 10). - @param sizeLimit size limit of log files in bytes. 0 means no limitation, the default value for other methods - */ -+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles sizeLimit:(NSUInteger)sizeLimit; - -/** - Delete all log files. - */ -+ (void)deleteLogFiles; - -/** - Get the list of all log files. - - @return files of - */ -+ (NSArray*)logFiles; - -/** - 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]. - - @param logCrashes YES to enable the catch. - */ -+ (void)logCrashes:(BOOL)logCrashes; - -/** - Set the app build version. - It will be reported in crash report. - */ -+ (void)setBuildVersion:(NSString*)buildVersion; - -/** - 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 `redirectNSLogToFiles`. - - @param subLogName the subname for log files. Files will be named as 'console-[subLogName].log' - Default is nil. - */ -+ (void)setSubLogName:(NSString*)subLogName; - -/** - If any, get the file containing the last application crash log. - - Only one crash log is stored at a time. The best moment for the app to handle it is the - at its next startup. - - @return the crash log file. nil if there is none. - */ -+ (NSString*)crashLog; - -/** - Delete the crash log file. - */ -+ (void)deleteCrashLog; - -@end diff --git a/ElementX/Sources/Other/Logging/MXLogger.m b/ElementX/Sources/Other/Logging/MXLogger.m deleted file mode 100644 index b66faba46..000000000 --- a/ElementX/Sources/Other/Logging/MXLogger.m +++ /dev/null @@ -1,392 +0,0 @@ -/* - Copyright 2015 OpenMarket Ltd - Copyright 2020 The Matrix.org Foundation C.I.C - - 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 "MXLogger.h" -#import "MXLog.h" -#import "GeneratedInterface-Swift.h" - -// stderr so it can be restored -int stderrSave = 0; - -static NSString *buildVersion; -static NSString *subLogName; - -#define MXLOGGER_CRASH_LOG @"crash.log" - -@implementation MXLogger - -#pragma mark - NSLog redirection -+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles -{ - [self redirectNSLogToFiles:redirectNSLogToFiles numberOfFiles:10]; -} - -+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles -{ - [self redirectNSLogToFiles:redirectNSLogToFiles numberOfFiles:numberOfFiles sizeLimit:0]; -} - -+ (void)redirectNSLogToFiles:(BOOL)redirectNSLogToFiles numberOfFiles:(NSUInteger)numberOfFiles sizeLimit:(NSUInteger)sizeLimit -{ - if (redirectNSLogToFiles) - { - NSMutableString *log = [NSMutableString string]; - - // Default subname - if (!subLogName) - { - subLogName = @""; - } - - // Set log location - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *logsFolderPath = [MXLogger logsFolderPath]; - - // Do a circular buffer based on X files - for (NSInteger index = numberOfFiles - 2; index >= 0; index--) - { - NSString *nsLogPathOlder; - NSString *nsLogPathCurrent; - - if (index == 0) - { - nsLogPathOlder = [NSString stringWithFormat:@"console%@.1.log", subLogName]; - nsLogPathCurrent = [NSString stringWithFormat:@"console%@.log", subLogName]; - } - else - { - nsLogPathOlder = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index + 1]; - nsLogPathCurrent = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index]; - } - - nsLogPathOlder = [logsFolderPath stringByAppendingPathComponent:nsLogPathOlder]; - nsLogPathCurrent = [logsFolderPath stringByAppendingPathComponent:nsLogPathCurrent]; - - if ([fileManager fileExistsAtPath:nsLogPathCurrent]) - { - if ([fileManager fileExistsAtPath:nsLogPathOlder]) - { - // Temp log - [log appendFormat:@"removeItemAtPath: %@\n", nsLogPathOlder]; - - NSError *error; - [fileManager removeItemAtPath:nsLogPathOlder error:&error]; - if (error) - { - [log appendFormat:@"removeItemAtPath: %@. Error: %@\n", nsLogPathOlder, error]; - } - } - - // Temp log - [log appendFormat:@"moveItemAtPath: %@ toPath: %@\n", nsLogPathCurrent, nsLogPathOlder]; - - NSError *error; - [fileManager moveItemAtPath:nsLogPathCurrent toPath:nsLogPathOlder error:&error]; - if (error) - { - [log appendFormat:@"moveItemAtPath: %@ toPath: %@. Error: %@\n", nsLogPathCurrent, nsLogPathOlder, error]; - } - } - } - - // Save stderr so it can be restored. - stderrSave = dup(STDERR_FILENO); - - NSString *nsLogPath = [logsFolderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"console%@.log", subLogName]]; - freopen([nsLogPath fileSystemRepresentation], "w+", stderr); - - MXLogDebug(@"redirectNSLogToFiles: YES"); - if (log.length) - { - // We can now log into files - MXLogDebug(@"%@", log); - } - - [self removeExtraFilesFromCount:numberOfFiles]; - - if (sizeLimit > 0) - { - [self removeFilesAfterSizeLimit:sizeLimit]; - } - } - else if (stderrSave) - { - // Flush before restoring stderr - fflush(stderr); - - // Now restore stderr, so new output goes to console. - dup2(stderrSave, STDERR_FILENO); - close(stderrSave); - } -} - -+ (void)deleteLogFiles -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - for (NSString *logFile in [self logFiles]) - { - [fileManager removeItemAtPath:logFile error:nil]; - } -} - -+ (NSArray*)logFiles -{ - NSMutableArray *logFiles = [NSMutableArray array]; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *logsFolderPath = [MXLogger logsFolderPath]; - - NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:logsFolderPath]; - - // Find all *.log files - NSString *file = nil; - while ((file = [dirEnum nextObject])) - { - if ([[file lastPathComponent] hasPrefix:@"console"]) - { - NSString *logPath = [logsFolderPath stringByAppendingPathComponent:file]; - [logFiles addObject:logPath]; - } - } - - MXLogDebug(@"logFiles: %@", logFiles); - - return logFiles; -} - - -#pragma mark - Exceptions and crashes -// Exceptions uncaught by try catch block are handled here -static void handleUncaughtException(NSException *exception) -{ - [MXLogger logCrashes:NO]; - - // Extract running app information - NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary]; - NSString* appVersion; - NSString* app, *appId; - - app = infoDict[@"CFBundleExecutable"]; - appId = infoDict[@"CFBundleIdentifier"]; - - if ([infoDict objectForKey:@"CFBundleVersion"]) - { - appVersion = [NSString stringWithFormat:@"%@ (r%@)", [infoDict objectForKey:@"CFBundleShortVersionString"], [infoDict objectForKey:@"CFBundleVersion"]]; - } - else - { - appVersion = [infoDict objectForKey:@"CFBundleShortVersionString"]; - } - - // Build the crash log -#if TARGET_OS_IPHONE - NSString *model = [[UIDevice currentDevice] model]; - NSString *version = [[UIDevice currentDevice] systemVersion]; -#elif TARGET_OS_OSX - NSString *model = @"Mac"; - NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString]; -#endif - NSArray *backtrace = [exception callStackSymbols]; - NSString *description = [NSString stringWithFormat:@"%.0f - %@\n%@\nApplication: %@ (%@)\nApplication version: %@\nBuild: %@\n%@ %@\n\nMain thread: %@\n%@\n", - [[NSDate date] timeIntervalSince1970], - [NSDate date], - exception.description, - app, appId, - appVersion, - buildVersion, - model, version, - [NSThread isMainThread] ? @"YES" : @"NO", - backtrace]; - - // Write to the crash log file - [MXLogger deleteCrashLog]; - NSString *crashLog = crashLogPath(); - [description writeToFile:crashLog - atomically:NO - encoding:NSStringEncodingConversionAllowLossy - error:nil]; - - MXLogErrorDetails(@"handleUncaughtException", @{ - @"description": description ?: @"unknown" - }); -} - -// Signals emitted by the app are handled here -static void handleSignal(int signalValue) -{ - // Throw a custom Objective-C exception - // The Objective-C runtime will then be able to build a readable call stack in handleUncaughtException - [NSException raise:@"Signal detected" format:@"Signal detected: %d", signalValue]; -} - -+ (void)logCrashes:(BOOL)logCrashes -{ - if (logCrashes) - { - // Handle not managed exceptions by ourselves - NSSetUncaughtExceptionHandler(&handleUncaughtException); - - // Register signal event (seg fault & cie) - signal(SIGABRT, handleSignal); - signal(SIGILL, handleSignal); - signal(SIGSEGV, handleSignal); - signal(SIGFPE, handleSignal); - signal(SIGBUS, handleSignal); - } - else - { - // Disable crash handling - NSSetUncaughtExceptionHandler(NULL); - signal(SIGABRT, SIG_DFL); - signal(SIGILL, SIG_DFL); - signal(SIGSEGV, SIG_DFL); - signal(SIGFPE, SIG_DFL); - signal(SIGBUS, SIG_DFL); - } -} - -+ (void)setBuildVersion:(NSString *)theBuildVersion -{ - buildVersion = theBuildVersion; -} - -+ (void)setSubLogName:(NSString *)theSubLogName -{ - subLogName = [NSString stringWithFormat:@"-%@", theSubLogName]; -} - -// Return the path of the crash log file -static NSString* crashLogPath(void) -{ - return [[MXLogger logsFolderPath] stringByAppendingPathComponent:MXLOGGER_CRASH_LOG]; -} - -+ (NSString*)crashLog -{ - NSString *exceptionLog; - - NSString *crashLog = crashLogPath(); - NSFileManager *fileManager = [NSFileManager defaultManager]; - if([fileManager fileExistsAtPath:crashLog]) - { - exceptionLog = crashLog; - } - return exceptionLog; -} - -+ (void)deleteCrashLog -{ - NSString *crashLog = crashLogPath(); - NSFileManager *fileManager = [NSFileManager defaultManager]; - if([fileManager fileExistsAtPath:crashLog]) - { - [fileManager removeItemAtPath:crashLog error:nil]; - } -} - -// The folder where logs are stored -+ (NSString*)logsFolderPath -{ - NSString *logsFolderPath = nil; - - NSURL *sharedContainerURL = [[NSFileManager defaultManager] appGroupContainerURL]; - if (sharedContainerURL) - { - logsFolderPath = [sharedContainerURL path]; - } - else - { - NSArray *paths = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; - logsFolderPath = paths[0].path; - } - - return logsFolderPath; -} - - -// If [self redirectNSLogToFiles: numberOfFiles:] is called with a lower numberOfFiles we need to do some cleanup -+ (void)removeExtraFilesFromCount:(NSUInteger)count -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *logsFolderPath = [MXLogger logsFolderPath]; - - NSUInteger index = count; - do - { - NSString *fileName = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index]; - NSString *logFile = [logsFolderPath stringByAppendingPathComponent:fileName]; - - if ([fileManager fileExistsAtPath:logFile]) - { - [fileManager removeItemAtPath:logFile error:nil]; - MXLogDebug(@"removeExtraFilesFromCount: %@. removeItemAtPath: %@\n", @(count), logFile); - } - else - { - break; - } - } - while (index++); -} - -+ (void)removeFilesAfterSizeLimit:(NSUInteger)sizeLimit -{ - NSUInteger logSize = 0; - BOOL removeFiles = NO; - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *logsFolderPath = [MXLogger logsFolderPath]; - - // Start from console.1.log. Do not consider console.log. It should be almost empty - NSUInteger index = 0; - while (++index) - { - NSString *fileName = [NSString stringWithFormat:@"console%@.%tu.log", subLogName, index]; - NSString *logFile = [logsFolderPath stringByAppendingPathComponent:fileName]; - - if ([fileManager fileExistsAtPath:logFile]) - { - logSize += [fileManager attributesOfItemAtPath:logFile error:nil].fileSize; - - if (logSize >= sizeLimit) - { - removeFiles = YES; - break; - } - } - else - { - break; - } - } - - if (removeFiles) - { - MXLogDebug(@"removeFilesAfterSizeLimit: Remove files from index %@ because logs are too large (%@ for a limit of %@)\n", - @(index), - [NSByteCountFormatter stringFromByteCount:logSize countStyle:NSByteCountFormatterCountStyleBinary], - [NSByteCountFormatter stringFromByteCount:sizeLimit countStyle:NSByteCountFormatterCountStyleBinary]); - [self removeExtraFilesFromCount:index]; - } - else - { - MXLogDebug(@"removeFilesAfterSizeLimit: No need: %@ for a limit of %@\n", - [NSByteCountFormatter stringFromByteCount:logSize countStyle:NSByteCountFormatterCountStyleBinary], - [NSByteCountFormatter stringFromByteCount:sizeLimit countStyle:NSByteCountFormatterCountStyleBinary]); - } - -} -@end diff --git a/ElementX/Sources/Other/Logging/MXLogger.swift b/ElementX/Sources/Other/Logging/MXLogger.swift new file mode 100644 index 000000000..9d732f0cf --- /dev/null +++ b/ElementX/Sources/Other/Logging/MXLogger.swift @@ -0,0 +1,327 @@ +// +// 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. +class 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. + /// - numberOfFiles: number of files to keep (default is 10). + /// - sizeLimit: size limit of log files in bytes. 0 means no limitation, the default value for other methods + static func redirectNSLog(toFiles redirectToFiles: Bool, numberOfFiles: UInt = 10, sizeLimit: UInt = 0) { + if redirectToFiles { + var tempLog = "" + + // Do a circular buffer based on X files + for index in (0...(numberOfFiles - 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.debug("redirectNSLogToFiles: true") + if !tempLog.isEmpty { + // We can now log into files + MXLog.debug(tempLog) + } + + removeExtraFiles(from: numberOfFiles) + + if sizeLimit > 0 { + removeFiles(after: sizeLimit) + } + } 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. + static var logFiles: [URL] { + var logFiles = [URL]() + + let fileManager = FileManager.default + let enumerator = fileManager.enumerator(at: logsFolderURL, includingPropertiesForKeys: nil) + + // Find all *.log files + while let logURL = enumerator?.nextObject() as? URL { + if logURL.lastPathComponent.hasPrefix("console") { + logFiles.append(logURL) + } + } + + MXLog.debug("logFiles: \(logFiles)") + + return logFiles + } + + // 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 = ElementInfoPlist.cfBundleExecutable + let appId = ElementInfoPlist.cfBundleIdentifier + let appVersion = "\(ElementInfoPlist.cfBundleShortVersionString) (r\(ElementInfoPlist.cfBundleVersion))" + + // 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 { + FileManager.default.appGroupContainerURL ?? URL.documentsDirectory + } + + /// 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.debug("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.debug("removeFilesAfterSizeLimit: Remove files from index \(indexExceedingSizeLimit) because logs are too large (\(logSizeString) for a limit of \(sizeLimitString)\n") + removeExtraFiles(from: UInt(indexExceedingSizeLimit)) + } else { + MXLog.debug("removeFilesAfterSizeLimit: No need: \(logSizeString) for a limit of \(sizeLimitString)\n") + } + } +} diff --git a/ElementX/Sources/Services/BugReport/BugReportService.swift b/ElementX/Sources/Services/BugReport/BugReportService.swift index db68e6549..19bff588f 100644 --- a/ElementX/Sources/Services/BugReport/BugReportService.swift +++ b/ElementX/Sources/Services/BugReport/BugReportService.swift @@ -61,7 +61,7 @@ class BugReportService: BugReportServiceProtocol { // also enable logging crashes, to send them with bug reports MXLogger.logCrashes(true) // set build version for logger - MXLogger.setBuildVersion(ElementInfoPlist.cfBundleShortVersionString) + MXLogger.buildVersion = ElementInfoPlist.cfBundleShortVersionString } // MARK: - BugReportServiceProtocol @@ -180,12 +180,11 @@ class BugReportService: BugReportServiceProtocol { MXLog.debug("zipFiles: includeLogs: \(includeLogs), includeCrashLog: \(includeCrashLog)") var filesToCompress: [URL] = [] - if includeLogs, let logFiles = MXLogger.logFiles() { - let urls = logFiles.compactMap { URL(fileURLWithPath: $0) } - filesToCompress.append(contentsOf: urls) + if includeLogs { + filesToCompress.append(contentsOf: MXLogger.logFiles) } - if includeCrashLog, let crashLogFile = MXLogger.crashLog() { - filesToCompress.append(URL(fileURLWithPath: crashLogFile)) + if includeCrashLog, let crashLogFile = MXLogger.crashLog { + filesToCompress.append(crashLogFile) } var totalSize = 0 diff --git a/ElementX/Sources/Services/Client/MockClientProxy.swift b/ElementX/Sources/Services/Client/MockClientProxy.swift index b6b9e8ba2..3ca2f909a 100644 --- a/ElementX/Sources/Services/Client/MockClientProxy.swift +++ b/ElementX/Sources/Services/Client/MockClientProxy.swift @@ -15,6 +15,7 @@ // import Combine +import Foundation import MatrixRustSDK struct MockClientProxy: ClientProxyProtocol { diff --git a/ElementX/Sources/Services/Room/MockRoomProxy.swift b/ElementX/Sources/Services/Room/MockRoomProxy.swift index bc6408974..57bb31945 100644 --- a/ElementX/Sources/Services/Room/MockRoomProxy.swift +++ b/ElementX/Sources/Services/Room/MockRoomProxy.swift @@ -15,6 +15,7 @@ // import Combine +import Foundation import MatrixRustSDK struct MockRoomProxy: RoomProxyProtocol { diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift index cedd2902d..a1a2bf93c 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -15,6 +15,7 @@ // import Combine +import Foundation import MatrixRustSDK private class RoomTimelineListener: TimelineListener { diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index c9c978ec3..e594d2825 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Foundation import MatrixRustSDK /// A light wrapper around timeline items returned from Rust. diff --git a/ElementX/SupportingFiles/ElementX-Bridging-Header.h b/ElementX/SupportingFiles/ElementX-Bridging-Header.h index 572883ea7..e11d920b1 100644 --- a/ElementX/SupportingFiles/ElementX-Bridging-Header.h +++ b/ElementX/SupportingFiles/ElementX-Bridging-Header.h @@ -1,4 +1,3 @@ // // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "MXLogger.h" diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 046745b05..d415e7298 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -23,7 +23,7 @@ class LoggingTests: XCTestCase { } override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. + MXLogger.deleteLogFiles() } override func tearDownWithError() throws { @@ -31,12 +31,7 @@ class LoggingTests: XCTestCase { } func testFileLogging() throws { - MXLogger.deleteLogFiles() - guard let logFiles = MXLogger.logFiles() else { - XCTFail(Constants.genericFailure) - return - } - XCTAssertTrue(logFiles.isEmpty) + XCTAssertTrue(MXLogger.logFiles.isEmpty) let log = UUID().uuidString @@ -44,22 +39,105 @@ class LoggingTests: XCTestCase { configuration.redirectLogsToFiles = true MXLog.configure(configuration) MXLog.debug(log) - guard let logFile = MXLogger.logFiles().first else { + guard let logFile = MXLogger.logFiles.first else { XCTFail(Constants.genericFailure) return } - let content = try String(contentsOfFile: logFile) + let content = try String(contentsOf: logFile) XCTAssert(content.contains(log)) } + + func testFileRotationOnLaunch() throws { + // Given a fresh launch with no logs. + XCTAssertTrue(MXLogger.logFiles.isEmpty) + + // When launching the app 5 times. + let launchCount = 5 + for index in 0..