- OSLogger for the defined categories - TCA logger for the TCA logs - WalletLogger for the secant logs - SDKLogger passed to the SDK - unit tests for the loggers - export category OS logs - share txt files (sdk, tca, wallet logs) via native share dialog - timestamp extension so we see even milliseconds - txt files up to some X size - simple button enable/disable logic and wrapping the export work in the Task - TODO for empty catches - OSLogger refactored to OSLogger_, just temporary change - export and share divided into business logic and view logic parts - unit tests for the TCA part - async let syntax for the export logs - simple activity indicator so testers know export is in progress - static date formatters so we don't instantiate it over and over
This commit is contained in:
parent
0d2d898f4e
commit
1aca887800
|
@ -99,6 +99,11 @@
|
|||
9E01F8282833CDA0000EFC57 /* ScanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E01F8272833CDA0000EFC57 /* ScanTests.swift */; };
|
||||
9E02B56A27FED43E005B809B /* FileManagerInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E02B56927FED43E005B809B /* FileManagerInterface.swift */; };
|
||||
9E02B56C27FED475005B809B /* DatabaseFilesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */; };
|
||||
9E0F5741297E7F1D005304FA /* TCALogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0F5740297E7F1C005304FA /* TCALogger.swift */; };
|
||||
9E0F5743297EB96C005304FA /* TCALoggerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0F5742297EB96C005304FA /* TCALoggerReducer.swift */; };
|
||||
9E0F5745297EBA1B005304FA /* LogStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0F5744297EBA1B005304FA /* LogStore.swift */; };
|
||||
9E0F5747297EE5F3005304FA /* OSLogger_.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0F5746297EE5F3005304FA /* OSLogger_.swift */; };
|
||||
9E0F574B2980260D005304FA /* LoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0F574A2980260D005304FA /* LoggerTests.swift */; };
|
||||
9E153A5F2920CE2700112F41 /* MnemonicInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E153A5E2920CD5100112F41 /* MnemonicInterface.swift */; };
|
||||
9E153A602920CE2700112F41 /* MnemonicLiveKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E153A5C2920CD5100112F41 /* MnemonicLiveKey.swift */; };
|
||||
9E153A612920CE2700112F41 /* MnemonicMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E153A5B2920CD5100112F41 /* MnemonicMocks.swift */; };
|
||||
|
@ -137,6 +142,10 @@
|
|||
9E5BF648282277BE00BA3F17 /* NotificationCenterInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF647282277BE00BA3F17 /* NotificationCenterInterface.swift */; };
|
||||
9E5BF64F2823E94900BA3F17 /* TransactionAddressTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */; };
|
||||
9E5BF6502823E94900BA3F17 /* TransactionAddressTextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */; };
|
||||
9E612C6F2987A9B100D09B09 /* UIShareDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E612C6E2987A9B100D09B09 /* UIShareDialog.swift */; };
|
||||
9E612C7229880E9200D09B09 /* LogsHandlerInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E612C7129880E9200D09B09 /* LogsHandlerInterface.swift */; };
|
||||
9E612C7429880F2200D09B09 /* LogsHandlerLive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E612C7329880F2200D09B09 /* LogsHandlerLive.swift */; };
|
||||
9E612C7629880FC900D09B09 /* LogsHandlerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E612C7529880FC900D09B09 /* LogsHandlerTest.swift */; };
|
||||
9E66122A287717A900C75B70 /* HomeCircularProgressSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E661229287717A900C75B70 /* HomeCircularProgressSnapshotTests.swift */; };
|
||||
9E66122C2877188700C75B70 /* SyncStatusSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E66122B2877188700C75B70 /* SyncStatusSnapshot.swift */; };
|
||||
9E6612312878337F00C75B70 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 9E6612302878337F00C75B70 /* Lottie */; };
|
||||
|
@ -395,6 +404,11 @@
|
|||
9E01F8272833CDA0000EFC57 /* ScanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanTests.swift; sourceTree = "<group>"; };
|
||||
9E02B56927FED43E005B809B /* FileManagerInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerInterface.swift; sourceTree = "<group>"; };
|
||||
9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFilesTests.swift; sourceTree = "<group>"; };
|
||||
9E0F5740297E7F1C005304FA /* TCALogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCALogger.swift; sourceTree = "<group>"; };
|
||||
9E0F5742297EB96C005304FA /* TCALoggerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCALoggerReducer.swift; sourceTree = "<group>"; };
|
||||
9E0F5744297EBA1B005304FA /* LogStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogStore.swift; sourceTree = "<group>"; };
|
||||
9E0F5746297EE5F3005304FA /* OSLogger_.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLogger_.swift; sourceTree = "<group>"; };
|
||||
9E0F574A2980260D005304FA /* LoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerTests.swift; sourceTree = "<group>"; };
|
||||
9E153A5B2920CD5100112F41 /* MnemonicMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicMocks.swift; sourceTree = "<group>"; };
|
||||
9E153A5C2920CD5100112F41 /* MnemonicLiveKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicLiveKey.swift; sourceTree = "<group>"; };
|
||||
9E153A5D2920CD5100112F41 /* MnemonicTestKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicTestKey.swift; sourceTree = "<group>"; };
|
||||
|
@ -434,6 +448,10 @@
|
|||
9E5BF647282277BE00BA3F17 /* NotificationCenterInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenterInterface.swift; sourceTree = "<group>"; };
|
||||
9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextField.swift; sourceTree = "<group>"; };
|
||||
9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextFieldStore.swift; sourceTree = "<group>"; };
|
||||
9E612C6E2987A9B100D09B09 /* UIShareDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIShareDialog.swift; sourceTree = "<group>"; };
|
||||
9E612C7129880E9200D09B09 /* LogsHandlerInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsHandlerInterface.swift; sourceTree = "<group>"; };
|
||||
9E612C7329880F2200D09B09 /* LogsHandlerLive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsHandlerLive.swift; sourceTree = "<group>"; };
|
||||
9E612C7529880FC900D09B09 /* LogsHandlerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsHandlerTest.swift; sourceTree = "<group>"; };
|
||||
9E661229287717A900C75B70 /* HomeCircularProgressSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCircularProgressSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E66122B2877188700C75B70 /* SyncStatusSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatusSnapshot.swift; sourceTree = "<group>"; };
|
||||
9E6612322878338C00C75B70 /* LottieAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimation.swift; sourceTree = "<group>"; };
|
||||
|
@ -962,6 +980,17 @@
|
|||
path = ScanTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E0F573F297E7F00005304FA /* Logging */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E0F5740297E7F1C005304FA /* TCALogger.swift */,
|
||||
9E0F5742297EB96C005304FA /* TCALoggerReducer.swift */,
|
||||
9E0F5744297EBA1B005304FA /* LogStore.swift */,
|
||||
9E0F5746297EE5F3005304FA /* OSLogger_.swift */,
|
||||
);
|
||||
path = Logging;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E153A5A2920CCE700112F41 /* Mnemonic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1109,6 +1138,24 @@
|
|||
path = TransactionAddress;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E612C6D2987A96500D09B09 /* UIKitBridge */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E612C6E2987A9B100D09B09 /* UIShareDialog.swift */,
|
||||
);
|
||||
path = UIKitBridge;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E612C7029880E6700D09B09 /* LogsHandler */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E612C7129880E9200D09B09 /* LogsHandlerInterface.swift */,
|
||||
9E612C7329880F2200D09B09 /* LogsHandlerLive.swift */,
|
||||
9E612C7529880FC900D09B09 /* LogsHandlerTest.swift */,
|
||||
);
|
||||
path = LogsHandler;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E6612342878341F00C75B70 /* Lotties */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1226,6 +1273,7 @@
|
|||
9E7FE0BB282D1DC200C374E8 /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E0F573F297E7F00005304FA /* Logging */,
|
||||
9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */,
|
||||
F9C165B3274031F600592F76 /* Bindings.swift */,
|
||||
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
|
||||
|
@ -1260,6 +1308,7 @@
|
|||
9EB863882922CC0E003D0F8B /* FeedbackGenerator */,
|
||||
9EB863B52923C4ED003D0F8B /* FileManager */,
|
||||
9EBDF981291F91B1000A1A05 /* LocalAuthentication */,
|
||||
9E612C7029880E6700D09B09 /* LogsHandler */,
|
||||
9E153A5A2920CCE700112F41 /* Mnemonic */,
|
||||
9EB863B42923C490003D0F8B /* NotificationCenter */,
|
||||
9EB8638F2922D000003D0F8B /* NumberFormatter */,
|
||||
|
@ -1650,6 +1699,7 @@
|
|||
9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */,
|
||||
9E39112D283F91600073DD9A /* ZatoshiTests.swift */,
|
||||
0DB4E0B02881F2DB00947B78 /* WalletBalance+testing.swift */,
|
||||
9E0F574A2980260D005304FA /* LoggerTests.swift */,
|
||||
);
|
||||
path = UtilTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1726,6 +1776,7 @@
|
|||
children = (
|
||||
F9971A6227680DFE00A2DB75 /* SettingsStore.swift */,
|
||||
F9971A6427680DFE00A2DB75 /* SettingsView.swift */,
|
||||
9E612C6D2987A96500D09B09 /* UIKitBridge */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1996,6 +2047,7 @@
|
|||
2EB7758727FC67FD00269373 /* TransactionAmountTextFieldStore.swift in Sources */,
|
||||
669FDAE9272C23B3007B9422 /* CircularFrame.swift in Sources */,
|
||||
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */,
|
||||
9E612C7229880E9200D09B09 /* LogsHandlerInterface.swift in Sources */,
|
||||
9EBDF960291E657B000A1A05 /* DeeplinkTestKey.swift in Sources */,
|
||||
34E0AF1128DEE5220034CF37 /* Wedge.swift in Sources */,
|
||||
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
|
||||
|
@ -2013,9 +2065,11 @@
|
|||
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
|
||||
9EAB467A2861EA6A002904A0 /* TransactionRowView.swift in Sources */,
|
||||
9EB8638C2922CD4A003D0F8B /* FeedbackGeneratorTestKey.swift in Sources */,
|
||||
9E0F5741297E7F1D005304FA /* TCALogger.swift in Sources */,
|
||||
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidationFlowStore.swift in Sources */,
|
||||
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */,
|
||||
9E9ADA7D2938F4C00071767B /* RootInitialization.swift in Sources */,
|
||||
9E612C7429880F2200D09B09 /* LogsHandlerLive.swift in Sources */,
|
||||
9EBDF967291ECDA2000A1A05 /* AudioServicesTestKey.swift in Sources */,
|
||||
0D535FE2271F9476009A9E3E /* EnumeratedChip.swift in Sources */,
|
||||
9EBDF97E291F7EB0000A1A05 /* AppVersionInterface.swift in Sources */,
|
||||
|
@ -2035,6 +2089,7 @@
|
|||
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */,
|
||||
F9971A5327680DD000A2DB75 /* ProfileStore.swift in Sources */,
|
||||
346D41E428DF0B8600963F36 /* CheckCircle.swift in Sources */,
|
||||
9E0F5745297EBA1B005304FA /* LogStore.swift in Sources */,
|
||||
9EB863AA29239EB2003D0F8B /* RecoveryPhraseRandomizer.swift in Sources */,
|
||||
9EB863C52923C8AF003D0F8B /* FileManagerTest.swift in Sources */,
|
||||
9EB863BF2923C72C003D0F8B /* SecItemLive.swift in Sources */,
|
||||
|
@ -2053,6 +2108,7 @@
|
|||
0D18581B272728D60046B928 /* PhraseChip.swift in Sources */,
|
||||
9E7FE0F92832824C00C374E8 /* QRCodeScanView.swift in Sources */,
|
||||
9E153A6F292167FF00112F41 /* ZcashSDKEnvironmentTestKey.swift in Sources */,
|
||||
9E0F5743297EB96C005304FA /* TCALoggerReducer.swift in Sources */,
|
||||
0DF482BA2787ADA800EB37D6 /* ConditionalModifier.swift in Sources */,
|
||||
9E7225F3288AB6DD00DF7F17 /* MultipleLineTextField.swift in Sources */,
|
||||
3448CB3228E47666006ADEDB /* NotEnoughFreeSpaceView.swift in Sources */,
|
||||
|
@ -2110,6 +2166,7 @@
|
|||
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */,
|
||||
663FABA0271D876200E495F8 /* PrimaryButton.swift in Sources */,
|
||||
663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */,
|
||||
9E612C6F2987A9B100D09B09 /* UIShareDialog.swift in Sources */,
|
||||
9EBDF956291E5E86000A1A05 /* DatabaseFilesLiveKey.swift in Sources */,
|
||||
9E2F1C842809B606004E65FE /* DebugMenu.swift in Sources */,
|
||||
9E153A5F2920CE2700112F41 /* MnemonicInterface.swift in Sources */,
|
||||
|
@ -2148,6 +2205,7 @@
|
|||
F9971A6627680DFE00A2DB75 /* SettingsView.swift in Sources */,
|
||||
F96B41EB273B50520021B49A /* Strings.swift in Sources */,
|
||||
9EB863D02923D3FC003D0F8B /* SDKSynchronizerMocks.swift in Sources */,
|
||||
9E612C7629880FC900D09B09 /* LogsHandlerTest.swift in Sources */,
|
||||
2EDA07A227EDE1AE00D6F09B /* TextFieldFooter.swift in Sources */,
|
||||
F9971A5427680DD000A2DB75 /* ProfileView.swift in Sources */,
|
||||
F9971A6027680DF600A2DB75 /* ScanStore.swift in Sources */,
|
||||
|
@ -2174,6 +2232,7 @@
|
|||
34E5F2F328E46DB700C17E5F /* DiskSpaceChecker.swift in Sources */,
|
||||
F9C165C42740403600592F76 /* TransactionSentView.swift in Sources */,
|
||||
9E153A6E292167FF00112F41 /* ZcashSDKEnvironmentLiveKey.swift in Sources */,
|
||||
9E0F5747297EE5F3005304FA /* OSLogger_.swift in Sources */,
|
||||
F9971A5927680DDE00A2DB75 /* RequestStore.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -2205,6 +2264,7 @@
|
|||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
|
||||
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||
9E9ECC9A28589E150099D5A2 /* RecoveryPhraseValidationFlowSnapshotTests.swift in Sources */,
|
||||
9E0F574B2980260D005304FA /* LoggerTests.swift in Sources */,
|
||||
9EAFEB822805793200199FC9 /* RootTests.swift in Sources */,
|
||||
9E391132284644580073DD9A /* AppInitializationTests.swift in Sources */,
|
||||
9E9ECC9928589E150099D5A2 /* RecoveryPhraseDisplaySnapshotTests.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// LogsHandlerInterface.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 30.01.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
|
||||
extension DependencyValues {
|
||||
var logsHandler: LogsHandlerClient {
|
||||
get { self[LogsHandlerClient.self] }
|
||||
set { self[LogsHandlerClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
struct LogsHandlerClient {
|
||||
let exportAndStoreLogs: (URL, URL, URL) async throws -> Void
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// LogsHandlerLive.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 30.01.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
|
||||
extension LogsHandlerClient: DependencyKey {
|
||||
static let liveValue = LogsHandlerClient(
|
||||
exportAndStoreLogs: { tempSDKDir, tempTCADir, tempWalletDir in
|
||||
async let sdkLogs = LogsHandlerClient.exportAndStoreLogsFor(key: LoggerConstants.sdkLogs, atURL: tempSDKDir)
|
||||
async let tcaLogs = LogsHandlerClient.exportAndStoreLogsFor(key: LoggerConstants.tcaLogs, atURL: tempTCADir)
|
||||
async let walletLogs = LogsHandlerClient.exportAndStoreLogsFor(key: LoggerConstants.walletLogs, atURL: tempWalletDir)
|
||||
|
||||
let logs = try await [sdkLogs, tcaLogs, walletLogs]
|
||||
|
||||
try logs.forEach { logsHandler in
|
||||
try logsHandler.result.write(to: logsHandler.dir, atomically: true, encoding: String.Encoding.utf8)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private extension LogsHandlerClient {
|
||||
static func exportAndStoreLogsFor(key: String, atURL: URL) async throws -> (result: String, dir: URL) {
|
||||
let logsStr = try await LogStore.exportCategory(key)
|
||||
|
||||
var result = ""
|
||||
logsStr?.forEach({ line in
|
||||
result.append(line)
|
||||
result.append("\n\n")
|
||||
})
|
||||
|
||||
return (result: result, dir: atURL)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// LogsHandlerTest.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 30.01.2023.
|
||||
//
|
||||
|
||||
import ComposableArchitecture
|
||||
import XCTestDynamicOverlay
|
||||
|
||||
extension LogsHandlerClient: TestDependencyKey {
|
||||
static let testValue = Self(
|
||||
exportAndStoreLogs: XCTUnimplemented("\(Self.self).exportAndStoreLogs")
|
||||
)
|
||||
}
|
|
@ -25,6 +25,8 @@ extension RootReducer {
|
|||
Reduce { state, action in
|
||||
switch action {
|
||||
case .initialization(.appDelegate(.didFinishLaunching)):
|
||||
// TODO: [#524] finish all the wallet events according to definition, https://github.com/zcash/secant-ios-wallet/issues/524
|
||||
LoggerProxy.event(".appDelegate(.didFinishLaunching)")
|
||||
/// We need to fetch data from keychain, in order to be 100% sure the kecyhain can be read we delay the check a bit
|
||||
return Effect(value: .initialization(.checkWalletInitialization))
|
||||
.delay(for: 0.02, scheduler: mainQueue)
|
||||
|
|
|
@ -139,7 +139,8 @@ extension RootReducer {
|
|||
spendParamsURL: try databaseFiles.spendParamsURLFor(network),
|
||||
outputParamsURL: try databaseFiles.outputParamsURLFor(network),
|
||||
viewingKeys: [viewingKey],
|
||||
walletBirthday: birthday
|
||||
walletBirthday: birthday,
|
||||
loggerProxy: OSLogger_(logLevel: .debug, category: LoggerConstants.sdkLogs)
|
||||
)
|
||||
|
||||
return initializer
|
||||
|
@ -173,7 +174,7 @@ extension RootStore {
|
|||
static var placeholder: RootStore {
|
||||
RootStore(
|
||||
initialState: .placeholder,
|
||||
reducer: RootReducer()._printChanges()
|
||||
reducer: RootReducer().logging()._printChanges()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,25 +10,49 @@ struct SettingsReducer: ReducerProtocol {
|
|||
case backupPhrase
|
||||
}
|
||||
|
||||
var destination: Destination?
|
||||
var exportLogsDisabled = false
|
||||
var isSharingLogs = false
|
||||
var phraseDisplayState: RecoveryPhraseDisplayReducer.State
|
||||
var rescanDialog: ConfirmationDialogState<SettingsReducer.Action>?
|
||||
var destination: Destination?
|
||||
|
||||
var tempSDKDir: URL {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
let sdkFileName = "sdkLogs.txt"
|
||||
return tempDir.appendingPathComponent(sdkFileName)
|
||||
}
|
||||
|
||||
var tempTCADir: URL {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
let sdkFileName = "tcaLogs.txt"
|
||||
return tempDir.appendingPathComponent(sdkFileName)
|
||||
}
|
||||
|
||||
var tempWalletDir: URL {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
let sdkFileName = "walletLogs.txt"
|
||||
return tempDir.appendingPathComponent(sdkFileName)
|
||||
}
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
case backupWallet
|
||||
case backupWalletAccessRequest
|
||||
case cancelRescan
|
||||
case exportLogs
|
||||
case fullRescan
|
||||
case logsExported
|
||||
case logsShareFinished
|
||||
case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
|
||||
case quickRescan
|
||||
case rescanBlockchain
|
||||
case updateDestination(SettingsReducer.State.Destination?)
|
||||
}
|
||||
|
||||
|
||||
@Dependency(\.localAuthentication) var localAuthentication
|
||||
@Dependency(\.mnemonic) var mnemonic
|
||||
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
|
||||
@Dependency(\.logsHandler) var logsHandler
|
||||
@Dependency(\.walletStorage) var walletStorage
|
||||
|
||||
var body: some ReducerProtocol<State, Action> {
|
||||
|
@ -57,6 +81,26 @@ struct SettingsReducer: ReducerProtocol {
|
|||
state.rescanDialog = nil
|
||||
return .none
|
||||
|
||||
case .exportLogs:
|
||||
state.exportLogsDisabled = true
|
||||
return .run { [state] send in
|
||||
do {
|
||||
try await logsHandler.exportAndStoreLogs(state.tempSDKDir, state.tempTCADir, state.tempWalletDir)
|
||||
await send(.logsExported)
|
||||
} catch {
|
||||
// TODO: [#527] address the error here https://github.com/zcash/secant-ios-wallet/issues/527
|
||||
}
|
||||
}
|
||||
|
||||
case .logsExported:
|
||||
state.exportLogsDisabled = false
|
||||
state.isSharingLogs = true
|
||||
return .none
|
||||
|
||||
case .logsShareFinished:
|
||||
state.isSharingLogs = false
|
||||
return .none
|
||||
|
||||
case .rescanBlockchain:
|
||||
state.rescanDialog = .init(
|
||||
title: TextState("Rescan"),
|
||||
|
@ -94,7 +138,7 @@ extension SettingsViewStore {
|
|||
send: SettingsReducer.Action.updateDestination
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
var bindingForBackupPhrase: Binding<Bool> {
|
||||
self.destinationBinding.map(
|
||||
extract: { $0 == .backupPhrase },
|
||||
|
|
|
@ -3,7 +3,7 @@ import ComposableArchitecture
|
|||
|
||||
struct SettingsView: View {
|
||||
let store: SettingsStore
|
||||
|
||||
|
||||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
VStack {
|
||||
|
@ -22,7 +22,26 @@ struct SettingsView: View {
|
|||
.primaryButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal, 30)
|
||||
|
||||
|
||||
Button(
|
||||
action: { viewStore.send(.exportLogs) },
|
||||
label: {
|
||||
if viewStore.exportLogsDisabled {
|
||||
HStack {
|
||||
ProgressView()
|
||||
Text("Exporting...")
|
||||
}
|
||||
} else {
|
||||
Text("Export & share logs")
|
||||
}
|
||||
}
|
||||
)
|
||||
.primaryButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal, 30)
|
||||
.padding(.top, 30)
|
||||
.disabled(viewStore.exportLogsDisabled)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
|
@ -37,6 +56,17 @@ struct SettingsView: View {
|
|||
RecoveryPhraseDisplayView(store: store.backupPhraseStore())
|
||||
}
|
||||
)
|
||||
|
||||
if viewStore.isSharingLogs {
|
||||
UIShareDialogView(
|
||||
activityItems: [viewStore.tempSDKDir, viewStore.tempWalletDir, viewStore.tempTCADir]
|
||||
) {
|
||||
viewStore.send(.logsShareFinished)
|
||||
}
|
||||
// UIShareDialogView only wraps UIActivityViewController presentation
|
||||
// so frame is set to 0 to not break SwiftUIs layout
|
||||
.frame(width: 0, height: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// UIShareDialog.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 30.01.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
public class UIShareDialog: UIView {
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIShareDialog {
|
||||
public func doInitialSetup(activityItems: [Any], completion: @escaping () -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
let activityVC = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
|
||||
|
||||
UIApplication.shared.connectedScenes.map({ $0 as? UIWindowScene })
|
||||
.compactMap({ $0 })
|
||||
.first?.windows.first?.rootViewController?.present(
|
||||
activityVC,
|
||||
animated: true,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UIShareDialogView: UIViewRepresentable {
|
||||
let activityItems: [Any]
|
||||
let completion: () -> Void
|
||||
|
||||
public func makeUIView(context: UIViewRepresentableContext<UIShareDialogView>) -> UIShareDialog {
|
||||
let view = UIShareDialog()
|
||||
view.doInitialSetup(activityItems: activityItems, completion: completion)
|
||||
return view
|
||||
}
|
||||
|
||||
public func updateUIView(_ uiView: UIShareDialog, context: UIViewRepresentableContext<UIShareDialogView>) {
|
||||
// We can leave it empty here because the view is just handler how to bridge UIKit's UIActivityViewController
|
||||
// presentation into SwiftUI. The view itself is not visible, only instantiated, therefore no updates needed.
|
||||
}
|
||||
|
||||
public typealias UIViewType = UIShareDialog
|
||||
}
|
|
@ -20,6 +20,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||
) -> Bool {
|
||||
walletLogger = OSLogger_(logLevel: .debug, category: LoggerConstants.walletLogs)
|
||||
// set the default behavior for the NSDecimalNumber
|
||||
NSDecimalNumber.defaultBehavior = Zatoshi.decimalHandler
|
||||
rootViewStore.send(.initialization(.appDelegate(.didFinishLaunching)))
|
||||
|
|
|
@ -8,12 +8,25 @@
|
|||
import Foundation
|
||||
|
||||
extension Date {
|
||||
static let timestampFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.dateFormat = "yyyy/MM/dd HH:mm:ss.SSSS"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
static let humanReadableFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .short
|
||||
return formatter
|
||||
}()
|
||||
|
||||
func timestamp() -> String {
|
||||
return String(format: "%@", Date.timestampFormatter.string(from: self))
|
||||
}
|
||||
|
||||
func asHumanReadable() -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
|
||||
dateFormatter.dateStyle = .short
|
||||
dateFormatter.timeStyle = .short
|
||||
|
||||
return dateFormatter.string(from: self)
|
||||
return Date.humanReadableFormatter.string(from: self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// LogStore.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 23.01.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
enum LogStore {
|
||||
static func exportCategory(
|
||||
_ category: String,
|
||||
hoursToThePast: TimeInterval = 168,
|
||||
fileSize: Int = 1_000_000
|
||||
) async throws -> [String]? {
|
||||
guard let bundle = Bundle.main.bundleIdentifier else { return nil }
|
||||
|
||||
let store = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let date = Date.now.addingTimeInterval(-hoursToThePast * 3600)
|
||||
let position = store.position(date: date)
|
||||
var res: [String] = []
|
||||
var size = 0
|
||||
|
||||
let entries = try store.getEntries(at: position).reversed()
|
||||
for entry in entries {
|
||||
guard let logEntry = entry as? OSLogEntryLog else { continue }
|
||||
guard logEntry.subsystem == bundle && logEntry.category == category else { continue }
|
||||
|
||||
guard size < fileSize else { break }
|
||||
|
||||
size += logEntry.composedMessage.utf8.count
|
||||
res.append("[\(logEntry.date.timestamp())] \(logEntry.composedMessage)")
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// OSLogger_.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 23.01.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ZcashLightClientKit
|
||||
import os
|
||||
|
||||
enum LoggerConstants {
|
||||
static let sdkLogs = "sdkLogs"
|
||||
static let tcaLogs = "tcaLogs"
|
||||
static let walletLogs = "walletLogs"
|
||||
}
|
||||
|
||||
var walletLogger: ZcashLightClientKit.Logger?
|
||||
|
||||
enum LoggerProxy {
|
||||
static func debug(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
|
||||
walletLogger?.debug(message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
static func info(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
|
||||
walletLogger?.info(message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
static func event(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
|
||||
walletLogger?.event(message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
static func warn(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
|
||||
walletLogger?.warn(message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
static func error(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
|
||||
walletLogger?.error(message, file: file, function: function, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [#529] the swiftlint rule as well as OSLogger_ will be removed once secant adopts latest SDK changes https://github.com/zcash/secant-ios-wallet/issues/529
|
||||
// swiftlint:disable:next type_name
|
||||
class OSLogger_: ZcashLightClientKit.Logger {
|
||||
enum LogLevel: Int {
|
||||
case debug
|
||||
case error
|
||||
case warning
|
||||
case event
|
||||
case info
|
||||
}
|
||||
|
||||
private(set) var oslog: OSLog?
|
||||
|
||||
var level: LogLevel
|
||||
|
||||
init(logLevel: LogLevel, category: String) {
|
||||
self.level = logLevel
|
||||
if let bundleName = Bundle.main.bundleIdentifier {
|
||||
self.oslog = OSLog(subsystem: bundleName, category: category)
|
||||
}
|
||||
}
|
||||
|
||||
func debug(
|
||||
_ message: String,
|
||||
file: StaticString = #file,
|
||||
function: StaticString = #function,
|
||||
line: Int = #line
|
||||
) {
|
||||
guard level.rawValue == LogLevel.debug.rawValue else { return }
|
||||
log(level: "DEBUG 🐞", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func error(
|
||||
_ message: String,
|
||||
file: StaticString = #file,
|
||||
function: StaticString = #function,
|
||||
line: Int = #line
|
||||
) {
|
||||
guard level.rawValue <= LogLevel.error.rawValue else { return }
|
||||
log(level: "ERROR 💥", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func warn(
|
||||
_ message: String,
|
||||
file: StaticString = #file,
|
||||
function: StaticString = #function,
|
||||
line: Int = #line
|
||||
) {
|
||||
guard level.rawValue <= LogLevel.warning.rawValue else { return }
|
||||
log(level: "WARNING ⚠️", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func event(
|
||||
_ message: String,
|
||||
file: StaticString = #file,
|
||||
function: StaticString = #function,
|
||||
line: Int = #line
|
||||
) {
|
||||
guard level.rawValue <= LogLevel.event.rawValue else { return }
|
||||
log(level: "EVENT ⏱", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func info(
|
||||
_ message: String,
|
||||
file: StaticString = #file,
|
||||
function: StaticString = #function,
|
||||
line: Int = #line
|
||||
) {
|
||||
guard level.rawValue <= LogLevel.info.rawValue else { return }
|
||||
log(level: "INFO ℹ️", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
private func log(
|
||||
level: String,
|
||||
message: String,
|
||||
file: StaticString = #file,
|
||||
function: StaticString = #function,
|
||||
line: Int = #line
|
||||
) {
|
||||
guard let oslog else { return }
|
||||
|
||||
let fileName = (String(describing: file) as NSString).lastPathComponent
|
||||
|
||||
os_log(
|
||||
"[%{public}@] %{public}@ - %{public}@ - Line: %{public}d -> %{public}@",
|
||||
log: oslog,
|
||||
level,
|
||||
fileName,
|
||||
String(describing: function),
|
||||
line,
|
||||
message
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// TCALogger.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 23.01.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
class TCALogger: OSLogger_ { }
|
||||
|
||||
extension TCALogger {
|
||||
static let live = TCALogger(logLevel: .debug, category: LoggerConstants.tcaLogs)
|
||||
|
||||
func tcaDebug(_ message: String) {
|
||||
guard let oslog else { return }
|
||||
|
||||
os_log(
|
||||
"%{public}@",
|
||||
log: oslog,
|
||||
message
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// TCALoggerReducer.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 23.01.2023.
|
||||
//
|
||||
|
||||
import ComposableArchitecture
|
||||
|
||||
extension ReducerProtocol {
|
||||
@inlinable
|
||||
public func logging(
|
||||
_ logger: ReducerLogger<State, Action>? = .tcaLogger
|
||||
) -> LogChangesReducer<Self> {
|
||||
LogChangesReducer<Self>(base: self, logger: logger)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ReducerLogger<State, Action> {
|
||||
private let _logChange: (_ receivedAction: Action, _ oldState: State, _ newState: State) -> Void
|
||||
|
||||
public init(
|
||||
logChange: @escaping (_ receivedAction: Action, _ oldState: State, _ newState: State) -> Void
|
||||
) {
|
||||
self._logChange = logChange
|
||||
}
|
||||
|
||||
public func logChange(receivedAction: Action, oldState: State, newState: State) {
|
||||
self._logChange(receivedAction, oldState, newState)
|
||||
}
|
||||
}
|
||||
|
||||
extension ReducerLogger {
|
||||
public static var tcaLogger: Self {
|
||||
Self { receivedAction, oldState, newState in
|
||||
var target = ""
|
||||
target.write("received action:\n")
|
||||
CustomDump.customDump(receivedAction, to: &target, indent: 2)
|
||||
target.write("\n")
|
||||
target.write(diff(oldState, newState).map { "\($0)\n" } ?? " (No state changes)\n")
|
||||
TCALogger.live.tcaDebug("\(target)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct LogChangesReducer<Base: ReducerProtocol>: ReducerProtocol {
|
||||
@usableFromInline let base: Base
|
||||
|
||||
@usableFromInline let logger: ReducerLogger<Base.State, Base.Action>?
|
||||
|
||||
@usableFromInline
|
||||
init(base: Base, logger: ReducerLogger<Base.State, Base.Action>?) {
|
||||
self.base = base
|
||||
self.logger = logger
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func reduce(
|
||||
into state: inout Base.State, action: Base.Action
|
||||
) -> EffectTask<Base.Action> {
|
||||
guard let logger else {
|
||||
return self.base.reduce(into: &state, action: action)
|
||||
}
|
||||
|
||||
let oldState = state
|
||||
let effects = self.base.reduce(into: &state, action: action)
|
||||
return effects.merge(
|
||||
with: .fireAndForget { [newState = state] in
|
||||
logger.logChange(receivedAction: action, oldState: oldState, newState: newState)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -99,6 +99,7 @@ class SettingsTests: XCTestCase {
|
|||
func testRescanBlockchain_Cancelling() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SettingsReducer.State(
|
||||
destination: nil,
|
||||
phraseDisplayState: .init(),
|
||||
rescanDialog: .init(
|
||||
title: TextState("Rescan"),
|
||||
|
@ -108,8 +109,7 @@ class SettingsTests: XCTestCase {
|
|||
.default(TextState("Full rescan"), action: .send(.fullRescan)),
|
||||
.cancel(TextState("Cancel"))
|
||||
]
|
||||
),
|
||||
destination: nil
|
||||
)
|
||||
),
|
||||
reducer: SettingsReducer()
|
||||
)
|
||||
|
@ -122,6 +122,7 @@ class SettingsTests: XCTestCase {
|
|||
func testRescanBlockchain_QuickRescanClearance() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SettingsReducer.State(
|
||||
destination: nil,
|
||||
phraseDisplayState: .init(),
|
||||
rescanDialog: .init(
|
||||
title: TextState("Rescan"),
|
||||
|
@ -131,8 +132,7 @@ class SettingsTests: XCTestCase {
|
|||
.default(TextState("Full rescan"), action: .send(.fullRescan)),
|
||||
.cancel(TextState("Cancel"))
|
||||
]
|
||||
),
|
||||
destination: nil
|
||||
)
|
||||
),
|
||||
reducer: SettingsReducer()
|
||||
)
|
||||
|
@ -145,6 +145,7 @@ class SettingsTests: XCTestCase {
|
|||
func testRescanBlockchain_FullRescanClearance() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SettingsReducer.State(
|
||||
destination: nil,
|
||||
phraseDisplayState: .init(),
|
||||
rescanDialog: .init(
|
||||
title: TextState("Rescan"),
|
||||
|
@ -154,8 +155,7 @@ class SettingsTests: XCTestCase {
|
|||
.default(TextState("Full rescan"), action: .send(.fullRescan)),
|
||||
.cancel(TextState("Cancel"))
|
||||
]
|
||||
),
|
||||
destination: nil
|
||||
)
|
||||
),
|
||||
reducer: SettingsReducer()
|
||||
)
|
||||
|
@ -164,4 +164,58 @@ class SettingsTests: XCTestCase {
|
|||
state.rescanDialog = nil
|
||||
}
|
||||
}
|
||||
|
||||
func testExportLogs_ButtonDisableShareEnable() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SettingsReducer.State(
|
||||
destination: nil,
|
||||
phraseDisplayState: .init(),
|
||||
rescanDialog: .init(
|
||||
title: TextState("Rescan"),
|
||||
message: TextState("Select the rescan you want"),
|
||||
buttons: [
|
||||
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
|
||||
.default(TextState("Full rescan"), action: .send(.fullRescan)),
|
||||
.cancel(TextState("Cancel"))
|
||||
]
|
||||
)
|
||||
),
|
||||
reducer: SettingsReducer()
|
||||
)
|
||||
|
||||
store.dependencies.logsHandler = LogsHandlerClient(exportAndStoreLogs: { _, _, _ in })
|
||||
|
||||
_ = await store.send(.exportLogs) { state in
|
||||
state.exportLogsDisabled = true
|
||||
}
|
||||
|
||||
await store.receive(.logsExported) { state in
|
||||
state.exportLogsDisabled = false
|
||||
state.isSharingLogs = true
|
||||
}
|
||||
}
|
||||
|
||||
func testLogShareFinished() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SettingsReducer.State(
|
||||
destination: nil,
|
||||
isSharingLogs: true,
|
||||
phraseDisplayState: .init(),
|
||||
rescanDialog: .init(
|
||||
title: TextState("Rescan"),
|
||||
message: TextState("Select the rescan you want"),
|
||||
buttons: [
|
||||
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
|
||||
.default(TextState("Full rescan"), action: .send(.fullRescan)),
|
||||
.cancel(TextState("Cancel"))
|
||||
]
|
||||
)
|
||||
),
|
||||
reducer: SettingsReducer()
|
||||
)
|
||||
|
||||
_ = await store.send(.logsShareFinished) { state in
|
||||
state.isSharingLogs = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
//
|
||||
// LoggerTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 24.01.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
import OSLog
|
||||
|
||||
class LoggerTests: XCTestCase {
|
||||
func testOSLogger_loggingAndExport() throws {
|
||||
let category = "testOSLogger_loggingAndExport"
|
||||
let osLogger = OSLogger_(logLevel: .debug, category: category)
|
||||
let testMessage = "test message"
|
||||
|
||||
osLogger.debug(testMessage)
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
XCTAssertNotNil(logs)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
|
||||
let loggedMessage = logs[0].osLoggedMessage()
|
||||
|
||||
XCTAssertEqual(testMessage, loggedMessage)
|
||||
}
|
||||
|
||||
func testOSLogger_DebugLevel_DebugLog() throws {
|
||||
let category = "testOSLogger_DebugLevel_DebugLog"
|
||||
let osLogger = OSLogger_(logLevel: .debug, category: category)
|
||||
let testMessage = "debug message"
|
||||
|
||||
osLogger.debug(testMessage)
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
XCTAssertNotNil(logs)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
|
||||
let loggedMessage = logs[0].osLoggedMessage()
|
||||
|
||||
XCTAssertEqual(testMessage, loggedMessage)
|
||||
}
|
||||
|
||||
func testOSLogger_ErrorLevel_ErrorLog() throws {
|
||||
let category = "testOSLogger_ErrorLevel_ErrorLog"
|
||||
let osLogger = OSLogger_(logLevel: .debug, category: category)
|
||||
let testMessage = "error message"
|
||||
|
||||
osLogger.error(testMessage)
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
XCTAssertNotNil(logs)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
|
||||
let loggedMessage = logs[0].osLoggedMessage()
|
||||
|
||||
XCTAssertEqual(testMessage, loggedMessage)
|
||||
}
|
||||
|
||||
func testOSLogger_WarningLevel_WarningLog() throws {
|
||||
let category = "testOSLogger_WarningLevel_WarningLog"
|
||||
let osLogger = OSLogger_(logLevel: .warning, category: category)
|
||||
let testMessage = "warning message"
|
||||
|
||||
osLogger.warn(testMessage)
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
XCTAssertNotNil(logs)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
|
||||
let loggedMessage = logs[0].osLoggedMessage()
|
||||
|
||||
XCTAssertEqual(testMessage, loggedMessage)
|
||||
}
|
||||
|
||||
func testOSLogger_EventLevel_EventLog() throws {
|
||||
let category = "testOSLogger_EventLevel_EventLog"
|
||||
let osLogger = OSLogger_(logLevel: .event, category: category)
|
||||
let testMessage = "event message"
|
||||
|
||||
osLogger.event(testMessage)
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
XCTAssertNotNil(logs)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
|
||||
let loggedMessage = logs[0].osLoggedMessage()
|
||||
|
||||
XCTAssertEqual(testMessage, loggedMessage)
|
||||
}
|
||||
|
||||
func testOSLogger_InfoLevel_InfoLog() throws {
|
||||
let category = "testOSLogger_InfoLevel_InfoLog"
|
||||
let osLogger = OSLogger_(logLevel: .info, category: category)
|
||||
let testMessage = "info message"
|
||||
|
||||
osLogger.info(testMessage)
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
XCTAssertNotNil(logs)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
|
||||
let loggedMessage = logs[0].osLoggedMessage()
|
||||
|
||||
XCTAssertEqual(testMessage, loggedMessage)
|
||||
}
|
||||
|
||||
func testOSLogger_DebugLevel_OtherLogs() throws {
|
||||
let category = "testOSLogger_DebugLevel_OtherLogs"
|
||||
let osLogger = OSLogger_(logLevel: .debug, category: category)
|
||||
let testMessage = "debug message"
|
||||
|
||||
osLogger.debug(testMessage)
|
||||
osLogger.error(testMessage)
|
||||
osLogger.warn(testMessage)
|
||||
osLogger.event(testMessage)
|
||||
osLogger.info(testMessage)
|
||||
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 5)
|
||||
}
|
||||
|
||||
func testOSLogger_ErrorLevel_OtherLogs() throws {
|
||||
let category = "testOSLogger_ErrorLevel_OtherLogs"
|
||||
let osLogger = OSLogger_(logLevel: .error, category: category)
|
||||
let testMessage = "debug message"
|
||||
|
||||
osLogger.debug(testMessage)
|
||||
osLogger.error(testMessage)
|
||||
osLogger.warn(testMessage)
|
||||
osLogger.event(testMessage)
|
||||
osLogger.info(testMessage)
|
||||
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 4)
|
||||
}
|
||||
|
||||
func testOSLogger_WarningLevel_OtherLogs() throws {
|
||||
let category = "testOSLogger_WarningLevel_OtherLogs"
|
||||
let osLogger = OSLogger_(logLevel: .warning, category: category)
|
||||
let testMessage = "debug message"
|
||||
|
||||
osLogger.debug(testMessage)
|
||||
osLogger.error(testMessage)
|
||||
osLogger.warn(testMessage)
|
||||
osLogger.event(testMessage)
|
||||
osLogger.info(testMessage)
|
||||
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 3)
|
||||
}
|
||||
|
||||
func testOSLogger_EventLevel_OtherLogs() throws {
|
||||
let category = "testOSLogger_EventLevel_OtherLogs"
|
||||
let osLogger = OSLogger_(logLevel: .event, category: category)
|
||||
let testMessage = "debug message"
|
||||
|
||||
osLogger.debug(testMessage)
|
||||
osLogger.error(testMessage)
|
||||
osLogger.warn(testMessage)
|
||||
osLogger.event(testMessage)
|
||||
osLogger.info(testMessage)
|
||||
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 2)
|
||||
}
|
||||
|
||||
func testOSLogger_InfoLevel_OtherLogs() throws {
|
||||
let category = "testOSLogger_InfoLevel_OtherLogs"
|
||||
let osLogger = OSLogger_(logLevel: .info, category: category)
|
||||
let testMessage = "debug message"
|
||||
|
||||
osLogger.debug(testMessage)
|
||||
osLogger.error(testMessage)
|
||||
osLogger.warn(testMessage)
|
||||
osLogger.event(testMessage)
|
||||
osLogger.info(testMessage)
|
||||
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
}
|
||||
|
||||
func testWalletLogger() throws {
|
||||
let category = "testWalletLogger"
|
||||
walletLogger = OSLogger_(logLevel: .info, category: category)
|
||||
let testMessage = "wallet test message"
|
||||
|
||||
LoggerProxy.info(testMessage)
|
||||
let logs = TestLogStore.exportCategory(category)
|
||||
|
||||
XCTAssertNotNil(logs)
|
||||
|
||||
guard let logs else { return }
|
||||
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
|
||||
let loggedMessage = logs[0].osLoggedMessage()
|
||||
|
||||
XCTAssertEqual(testMessage, loggedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func osLoggedMessage() -> String? {
|
||||
let split = components(separatedBy: "-> ")
|
||||
|
||||
if split.count == 2 {
|
||||
return split[1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
enum TestLogStore {
|
||||
static func exportCategory(_ category: String, hoursToThePast: TimeInterval = 24) -> [String]? {
|
||||
guard let bundle = Bundle.main.bundleIdentifier else { return nil }
|
||||
|
||||
do {
|
||||
let store = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let date = Date.now.addingTimeInterval(-hoursToThePast * 3600)
|
||||
let position = store.position(date: date)
|
||||
var entries: [String] = []
|
||||
|
||||
entries = try store
|
||||
.getEntries(at: position)
|
||||
.compactMap { $0 as? OSLogEntryLog }
|
||||
.filter { $0.subsystem == bundle && $0.category == category }
|
||||
.map { "[\($0.date.formatted())] \($0.composedMessage)" }
|
||||
|
||||
return entries
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue