#166: Re-write MXLogger in Swift

* Add logging tests that cover file rotation.

* Re-write MXLogger in Swift.
This commit is contained in:
Doug 2022-11-18 15:02:01 +00:00 committed by GitHub
parent 77a0d5a58f
commit ba684d8efb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 443 additions and 697 deletions

View File

@ -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 = "<group>"; };
054F469E433864CC6FE6EE8E /* ServerSelectionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionUITests.swift; sourceTree = "<group>"; };
057B747CF045D3C6C30EAB2C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
0776771332259AB1C9661430 /* MXLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLog.h; sourceTree = "<group>"; };
077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = "<group>"; };
086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = "<group>"; };
08F64963396A6A23538EFCEC /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = is; path = is.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -515,7 +513,6 @@
39EBB6903EFD4236B8D11A42 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-CA"; path = "fr-CA.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = "<group>"; };
3B5B535DA49C54523FF7A412 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nn; path = nn.lproj/Localizable.strings; sourceTree = "<group>"; };
3BFEC43A1A0769F1EAC62873 /* MXLogObjcWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLogObjcWrapper.h; sourceTree = "<group>"; };
3CDF9E55650D6035D6536538 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "nb-NO"; path = "nb-NO.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = "<group>"; };
3D4DD336905C72F95EAF34B7 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = "<group>"; };
@ -571,18 +568,15 @@
536E72DCBEEC4A1FE66CFDCE /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
541542F5AC323709D8563458 /* AnalyticsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPrompt.swift; sourceTree = "<group>"; };
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
54E438DBCBDC7A41B95DDDD9 /* MXLogObjcWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogObjcWrapper.m; sourceTree = "<group>"; };
55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = "<group>"; };
55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = "<group>"; };
55EA4B03F92F31EAA83B3F7B /* FilePreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewModels.swift; sourceTree = "<group>"; };
55F30E764BED111C81739844 /* SoftLogoutUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutUITests.swift; sourceTree = "<group>"; };
56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedRoomTimelineView.swift; sourceTree = "<group>"; };
5773C86AF04AEF26515AD00C /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = "<group>"; };
5872785B9C7934940146BFBA /* MXLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXLogger.h; sourceTree = "<group>"; };
5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelProtocol.swift; sourceTree = "<group>"; };
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinatorUITests.swift; sourceTree = "<group>"; };
5DA73F4229252C3A00651C22 /* TimelineDeliveryStatusView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineDeliveryStatusView.swift; sourceTree = "<group>"; };
5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
@ -699,6 +693,7 @@
A2B6433F516F1E6DFA0E2D89 /* vls */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vls; path = vls.lproj/Localizable.strings; sourceTree = "<group>"; };
A3004DFA1B10951962787D90 /* VideoPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModelTests.swift; sourceTree = "<group>"; };
A30A1758E2B73EF38E7C42F8 /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = "<group>"; };
A34A814CBD56230BC74FFCF4 /* MXLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLogger.swift; sourceTree = "<group>"; };
A40C19719687984FD9478FBE /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -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 = "<group>"; };
D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineDeliveryStatusView.swift; sourceTree = "<group>"; };
D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = "<group>"; };
D67CBAFA48ED0B6FCE74F88F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
@ -844,7 +840,6 @@
EEAF1C75771D9DC75877F4B4 /* MessageTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTimelineItem.swift; sourceTree = "<group>"; };
EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactoryProtocol.swift; sourceTree = "<group>"; };
EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = "<group>"; };
EF188681D6B6068CFAEAFC3F /* MXLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = "<group>"; };
EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = "<group>"; };
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceTests.swift; sourceTree = "<group>"; };
F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View File

@ -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]; \
}

View File

@ -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 <Foundation/Foundation.h>
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

View File

@ -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

View File

@ -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 <Foundation/Foundation.h>
/**
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<NSString*>*)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

View File

@ -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<NSString*>*)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<NSURL *> *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

View File

@ -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")
}
}
}

View File

@ -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

View File

@ -15,6 +15,7 @@
//
import Combine
import Foundation
import MatrixRustSDK
struct MockClientProxy: ClientProxyProtocol {

View File

@ -15,6 +15,7 @@
//
import Combine
import Foundation
import MatrixRustSDK
struct MockRoomProxy: RoomProxyProtocol {

View File

@ -15,6 +15,7 @@
//
import Combine
import Foundation
import MatrixRustSDK
private class RoomTimelineListener: TimelineListener {

View File

@ -14,6 +14,7 @@
// limitations under the License.
//
import Foundation
import MatrixRustSDK
/// A light wrapper around timeline items returned from Rust.

View File

@ -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"

View File

@ -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 testLogLevels() throws {
MXLogger.deleteLogFiles()
guard let logFiles = MXLogger.logFiles() else {
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..<launchCount {
let configuration = MXLogConfiguration()
configuration.redirectLogsToFiles = true
MXLog.configure(configuration) // This call is only made at app launch.
MXLog.debug("Launch \(index + 1)")
}
// Then 5 log files should be created each with the correct contents.
let logFiles = MXLogger.logFiles
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 {
let configuration = MXLogConfiguration()
configuration.maxLogFilesCount = UInt(logFileCount)
configuration.redirectLogsToFiles = true
MXLog.configure(configuration) // This call is only made at app launch.
MXLog.debug("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 = 25 * 1024
for index in 0..<launchCount {
let configuration = MXLogConfiguration()
configuration.logFilesSizeLimit = UInt(logFileSizeLimit)
configuration.redirectLogsToFiles = true
MXLog.configure(configuration) // This call is only made at app launch.
MXLog.debug("Launch \(index + 1)")
// Add ~5KB of logs
for _ in 0..<5 {
let string = [String](repeating: "a", count: 1024).joined()
MXLog.debug(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
}
XCTAssert(logFiles.isEmpty)
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
@ -68,22 +146,17 @@ 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)
XCTAssertFalse(content.contains(log))
}
func testSubLogName() {
MXLogger.deleteLogFiles()
guard let logFiles = MXLogger.logFiles() else {
XCTFail(Constants.genericFailure)
return
}
XCTAssert(logFiles.isEmpty)
XCTAssert(MXLogger.logFiles.isEmpty)
let subLogName = "nse"
@ -92,11 +165,11 @@ class LoggingTests: XCTestCase {
configuration.redirectLogsToFiles = true
MXLog.configure(configuration)
MXLog.debug(UUID().uuidString)
guard let logFile = MXLogger.logFiles().first else {
guard let logFile = MXLogger.logFiles.first else {
XCTFail(Constants.genericFailure)
return
}
XCTAssertTrue(logFile.contains(subLogName))
XCTAssertTrue(logFile.lastPathComponent.contains(subLogName))
}
}

1
changelog.d/166.change Normal file
View File

@ -0,0 +1 @@
Re-write MXLogger in Swift.