diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-07-30 11:31:29 +0300 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-08-13 15:55:14 +0200 |
| commit | 30466abf6356d87e51b46b139caf441f4e54f759 (patch) | |
| tree | 64cab4e2253728ec031536a1a31a94ccf501d514 | |
| parent | 10da3af6d2a12e11f8f46dbd28116bc5a13e1196 (diff) | |
| download | mullvadvpn-30466abf6356d87e51b46b139caf441f4e54f759.tar.xz mullvadvpn-30466abf6356d87e51b46b139caf441f4e54f759.zip | |
Add SwiftLog
26 files changed, 478 insertions, 166 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index 13cb39d70b..9e0e294233 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -24,6 +24,7 @@ Line wrap the file at 100 chars. Th ## [Unreleased] ### Added +- Save application logs to file. - Add button to reconnect the tunnel. - Add support for iOS 12. - Ship the initial relay list with the app, and do once an hour periodic refresh in background. diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 0c188fa520..de75dd4a2f 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -39,6 +39,16 @@ 580EE22824B3289300F9D8A1 /* AssociatedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580EE22724B3289300F9D8A1 /* AssociatedValue.swift */; }; 580EE22924B3289300F9D8A1 /* AssociatedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580EE22724B3289300F9D8A1 /* AssociatedValue.swift */; }; 5811DE50239014550011EB53 /* NEVPNStatus+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */; }; + 5815039724D6ECAE00C9C50E /* CustomFormatLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039624D6ECAE00C9C50E /* CustomFormatLogHandler.swift */; }; + 5815039824D6ECAE00C9C50E /* CustomFormatLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039624D6ECAE00C9C50E /* CustomFormatLogHandler.swift */; }; + 5815039D24D6ECE600C9C50E /* TextFileOutputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039C24D6ECE600C9C50E /* TextFileOutputStream.swift */; }; + 5815039E24D6ECE600C9C50E /* TextFileOutputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039C24D6ECE600C9C50E /* TextFileOutputStream.swift */; }; + 581503A024D6F01E00C9C50E /* LogRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039324D6EB7200C9C50E /* LogRotation.swift */; }; + 581503A124D6F01F00C9C50E /* LogRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5815039324D6EB7200C9C50E /* LogRotation.swift */; }; + 581503A324D6F1EC00C9C50E /* ChainedError+Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581503A224D6F1EC00C9C50E /* ChainedError+Logger.swift */; }; + 581503A424D6F1EC00C9C50E /* ChainedError+Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581503A224D6F1EC00C9C50E /* ChainedError+Logger.swift */; }; + 581503A624D6F4AE00C9C50E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581503A524D6F4AE00C9C50E /* Logging.swift */; }; + 581503A724D6F4AE00C9C50E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581503A524D6F4AE00C9C50E /* Logging.swift */; }; 581CBCE62296B97300727D7F /* ViewControllerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */; }; 581CBCEC2298041B00727D7F /* SettingsAppVersionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581CBCEB2298041B00727D7F /* SettingsAppVersionCell.swift */; }; 581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */; }; @@ -77,9 +87,10 @@ 5857F23F24C844AD00CF6F47 /* Locking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BA692D23E99EFF009DC256 /* Locking.swift */; }; 5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5857F24224C8662600CF6F47 /* SelectLocationHeaderView.swift */; }; 5857F24724C882D700CF6F47 /* SelectLocationNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5857F24624C882D700CF6F47 /* SelectLocationNavigationController.swift */; }; + 585834F824D2BC1F00A8AF56 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 585834F724D2BC1F00A8AF56 /* Logging */; }; + 585834FC24D2BC9500A8AF56 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 585834FB24D2BC9500A8AF56 /* Logging */; }; 5860F1C223A785C600CEA666 /* WireguardDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860F1C123A785C600CEA666 /* WireguardDevice.swift */; }; 5860F1C423A8D25F00CEA666 /* WireguardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860F1C323A8D25F00CEA666 /* WireguardConfiguration.swift */; }; - 5860F1EB23AA4CF300CEA666 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860F1EA23AA4CF300CEA666 /* Logging.swift */; }; 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; }; 5867A51C2248F26A005513C0 /* SegueIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */; }; 5868585524054096000B8131 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5868585424054096000B8131 /* AppButton.swift */; }; @@ -258,6 +269,11 @@ 580EE22324B3243100F9D8A1 /* AsyncBlockOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncBlockOperation.swift; sourceTree = "<group>"; }; 580EE22724B3289300F9D8A1 /* AssociatedValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociatedValue.swift; sourceTree = "<group>"; }; 5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEVPNStatus+Debug.swift"; sourceTree = "<group>"; }; + 5815039324D6EB7200C9C50E /* LogRotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRotation.swift; sourceTree = "<group>"; }; + 5815039624D6ECAE00C9C50E /* CustomFormatLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFormatLogHandler.swift; sourceTree = "<group>"; }; + 5815039C24D6ECE600C9C50E /* TextFileOutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFileOutputStream.swift; sourceTree = "<group>"; }; + 581503A224D6F1EC00C9C50E /* ChainedError+Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChainedError+Logger.swift"; sourceTree = "<group>"; }; + 581503A524D6F4AE00C9C50E /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; }; 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerIdentifier.swift; sourceTree = "<group>"; }; 581CBCEB2298041B00727D7F /* SettingsAppVersionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionCell.swift; sourceTree = "<group>"; }; 581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTableViewDataSource.swift; sourceTree = "<group>"; }; @@ -277,7 +293,6 @@ 5857F24624C882D700CF6F47 /* SelectLocationNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationNavigationController.swift; sourceTree = "<group>"; }; 5860F1C123A785C600CEA666 /* WireguardDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardDevice.swift; sourceTree = "<group>"; }; 5860F1C323A8D25F00CEA666 /* WireguardConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardConfiguration.swift; sourceTree = "<group>"; }; - 5860F1EA23AA4CF300CEA666 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; }; 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; }; 5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MullvadVPN.entitlements; sourceTree = "<group>"; }; 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegueIdentifier.swift; sourceTree = "<group>"; }; @@ -387,6 +402,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 585834F824D2BC1F00A8AF56 /* Logging in Frameworks */, 58F3C0A0249BBF1E003E76BE /* DiffableDataSources in Frameworks */, 586BD68422B7BBE400BB7F9F /* NetworkExtension.framework in Frameworks */, ); @@ -396,6 +412,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 585834FC24D2BC9500A8AF56 /* Logging in Frameworks */, 586BD68322B7BBD800BB7F9F /* NetworkExtension.framework in Frameworks */, 58723E7522A54CB2009837F5 /* libwg-go.a in Frameworks */, ); @@ -442,6 +459,18 @@ path = Operations; sourceTree = "<group>"; }; + 5815039F24D6ECF200C9C50E /* Logging */ = { + isa = PBXGroup; + children = ( + 581503A224D6F1EC00C9C50E /* ChainedError+Logger.swift */, + 5815039624D6ECAE00C9C50E /* CustomFormatLogHandler.swift */, + 5815039C24D6ECE600C9C50E /* TextFileOutputStream.swift */, + 581503A524D6F4AE00C9C50E /* Logging.swift */, + 5815039324D6EB7200C9C50E /* LogRotation.swift */, + ); + path = Logging; + sourceTree = "<group>"; + }; 58B0A2A1238EE67E00BC001D /* MullvadVPNTests */ = { isa = PBXGroup; children = ( @@ -483,6 +512,7 @@ 58CE5E62224146200008646E /* MullvadVPN */ = { isa = PBXGroup; children = ( + 5815039F24D6ECF200C9C50E /* Logging */, 587AD7C92342283900E93A53 /* Account.swift */, 582BB1B42295780F0055B6EF /* AccountExpiry.swift */, 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */, @@ -590,7 +620,6 @@ 58C6B36622C106FC003C19AD /* WireguardCommand.swift */, 5860F1C123A785C600CEA666 /* WireguardDevice.swift */, 5860F1C323A8D25F00CEA666 /* WireguardConfiguration.swift */, - 5860F1EA23AA4CF300CEA666 /* Logging.swift */, ); path = PacketTunnel; sourceTree = "<group>"; @@ -676,6 +705,7 @@ name = MullvadVPN; packageProductDependencies = ( 58F3C09F249BBF1E003E76BE /* DiffableDataSources */, + 585834F724D2BC1F00A8AF56 /* Logging */, ); productName = MullvadVPN; productReference = 58CE5E60224146200008646E /* MullvadVPN.app */; @@ -696,6 +726,9 @@ 58FBDAA222A52A6800EB69A3 /* PBXTargetDependency */, ); name = PacketTunnel; + packageProductDependencies = ( + 585834FB24D2BC9500A8AF56 /* Logging */, + ); productName = PacketTunnel; productReference = 58CE5E79224146470008646E /* PacketTunnel.appex */; productType = "com.apple.product-type.app-extension"; @@ -772,6 +805,7 @@ mainGroup = 58CE5E57224146200008646E; packageReferences = ( 58F3C09E249BBF1E003E76BE /* XCRemoteSwiftPackageReference "DiffableDataSources" */, + 585834F624D2BC1F00A8AF56 /* XCRemoteSwiftPackageReference "swift-log" */, ); productRefGroup = 58CE5E61224146200008646E /* Products */; projectDirPath = ""; @@ -952,6 +986,7 @@ 5845F842236CBACD00B2D93C /* PacketTunnelIpc.swift in Sources */, 58781CC922AE7CA8009B9D8E /* RelayConstraints.swift in Sources */, 584E96BC240FD4DA00D3334F /* Location.swift in Sources */, + 581503A124D6F01F00C9C50E /* LogRotation.swift in Sources */, 580EE20F24B322E700F9D8A1 /* TransformOperation.swift in Sources */, 58B8743222B25A7600015324 /* WireguardAssociatedAddresses.swift in Sources */, 58C6B34F22BB7AC0003C19AD /* IPAddressRange.swift in Sources */, @@ -972,12 +1007,15 @@ 5877152E23981C5B001F8237 /* SettingsBasicCell.swift in Sources */, 58FD5BE724192A2C00112C88 /* AppStoreReceipt.swift in Sources */, 5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */, + 5815039724D6ECAE00C9C50E /* CustomFormatLogHandler.swift in Sources */, + 5815039D24D6ECE600C9C50E /* TextFileOutputStream.swift in Sources */, 581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, 58C6B35E22BBBFE3003C19AD /* Data+HexCoding.swift in Sources */, 5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */, 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */, 580EE22824B3289300F9D8A1 /* AssociatedValue.swift in Sources */, + 581503A624D6F4AE00C9C50E /* Logging.swift in Sources */, 58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */, 58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */, 580EE22424B3243100F9D8A1 /* AsyncBlockOperation.swift in Sources */, @@ -993,6 +1031,7 @@ 5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */, 580EE21824B3235100F9D8A1 /* AnyOperationObserver.swift in Sources */, 587AD7C623421D7000E93A53 /* TunnelSettings.swift in Sources */, + 581503A324D6F1EC00C9C50E /* ChainedError+Logger.swift in Sources */, 58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */, 58561C99239A5D1500BD6B5E /* IPEndpoint.swift in Sources */, 58FD5BF22424F7D700112C88 /* UserInterfaceInteractionRestriction.swift in Sources */, @@ -1034,18 +1073,21 @@ 586AA296234B696B00502875 /* WireguardAssociatedAddresses.swift in Sources */, 58BA692F23E99F5B009DC256 /* Locking.swift in Sources */, 58B8743B22B788D200015324 /* PacketTunnelSettingsGenerator.swift in Sources */, - 5860F1EB23AA4CF300CEA666 /* Logging.swift in Sources */, 5860F1C223A785C600CEA666 /* WireguardDevice.swift in Sources */, 580EE21624B3231200F9D8A1 /* OperationBlockObserver.swift in Sources */, 58CC40F024A602780019D96E /* ObserverList.swift in Sources */, 58C6B35522BB87C4003C19AD /* WireguardPrivateKey.swift in Sources */, + 581503A724D6F4AE00C9C50E /* Logging.swift in Sources */, 58FAEE0424533AC000CB0F5B /* KeychainClass.swift in Sources */, 58AEEF6C2344A49D00C9BBD5 /* TunnelSettingsManager.swift in Sources */, 58C6B35F22BBBFE3003C19AD /* Data+HexCoding.swift in Sources */, + 581503A424D6F1EC00C9C50E /* ChainedError+Logger.swift in Sources */, + 5815039824D6ECAE00C9C50E /* CustomFormatLogHandler.swift in Sources */, 5840250522B11AB700E4CFEC /* MullvadEndpoint.swift in Sources */, 58BFA5C722A7C97F00A6173D /* RelayCache.swift in Sources */, 580EE21024B322E700F9D8A1 /* TransformOperation.swift in Sources */, 58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */, + 5815039E24D6ECE600C9C50E /* TextFileOutputStream.swift in Sources */, 584E96BD240FD4DA00D3334F /* Location.swift in Sources */, 58FAEDF8245088E100CB0F5B /* Keychain.swift in Sources */, 58C6B36122C0EC82003C19AD /* AnyIPEndpoint+DNS64.swift in Sources */, @@ -1057,6 +1099,7 @@ 580EE20D24B3225F00F9D8A1 /* DelayOperation.swift in Sources */, 588534BF246193D90018B744 /* AutomaticKeyRotationManager.swift in Sources */, 58781CCE22AE8918009B9D8E /* RelayConstraints.swift in Sources */, + 581503A024D6F01E00C9C50E /* LogRotation.swift in Sources */, 58781CD522AFBA39009B9D8E /* RelaySelector.swift in Sources */, 580EE21C24B3236900F9D8A1 /* InputOperation.swift in Sources */, 580EE22224B3240100F9D8A1 /* TransformOperationObserver.swift in Sources */, @@ -1515,6 +1558,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 585834F624D2BC1F00A8AF56 /* XCRemoteSwiftPackageReference "swift-log" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-log.git"; + requirement = { + kind = exactVersion; + version = 1.4.0; + }; + }; 58F3C09E249BBF1E003E76BE /* XCRemoteSwiftPackageReference "DiffableDataSources" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ra1028/DiffableDataSources.git"; @@ -1526,6 +1577,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 585834F724D2BC1F00A8AF56 /* Logging */ = { + isa = XCSwiftPackageProductDependency; + package = 585834F624D2BC1F00A8AF56 /* XCRemoteSwiftPackageReference "swift-log" */; + productName = Logging; + }; + 585834FB24D2BC9500A8AF56 /* Logging */ = { + isa = XCSwiftPackageProductDependency; + package = 585834F624D2BC1F00A8AF56 /* XCRemoteSwiftPackageReference "swift-log" */; + productName = Logging; + }; 58F3C09F249BBF1E003E76BE /* DiffableDataSources */ = { isa = XCSwiftPackageProductDependency; package = 58F3C09E249BBF1E003E76BE /* XCRemoteSwiftPackageReference "DiffableDataSources" */; diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 497878448d..e49461b65b 100644 --- a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ "revision": "14c66681e12a38b81045f44c6c29724a0d4b0e72", "version": "1.1.5" } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "173f567a2dfec11d74588eea82cecea555bdc0bc", + "version": "1.4.0" + } } ] }, diff --git a/ios/MullvadVPN/Account.swift b/ios/MullvadVPN/Account.swift index 2dee095be1..3f96d9308b 100644 --- a/ios/MullvadVPN/Account.swift +++ b/ios/MullvadVPN/Account.swift @@ -9,7 +9,7 @@ import Foundation import NetworkExtension import StoreKit -import os +import Logging /// A enum holding the `UserDefaults` string keys private enum UserDefaultsKeys: String { @@ -41,6 +41,8 @@ class Account { /// A shared instance of `Account` static let shared = Account() + private let logger = Logger(label: "Account") + /// Returns true if user agreed to terms of service, otherwise false var isAgreedToTermsOfService: Bool { return UserDefaults.standard.bool(forKey: UserDefaultsKeys.isAgreedToTermsOfService.rawValue) @@ -169,7 +171,7 @@ class Account { self.postExpiryUpdateNotification(newExpiry: response.expires) case .failure(let error): - error.logChain(message: "Failed to update account expiry") + self.logger.error(chainedError: error, message: "Failed to update account expiry") } } diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index 1c0ed4177c..5c81d561d4 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -8,7 +8,7 @@ import StoreKit import UIKit -import os +import Logging class AccountViewController: UIViewController, AppStorePaymentObserver { @@ -24,6 +24,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver { private var pendingPayment: SKPayment? private let alertPresenter = AlertPresenter() + private let logger = Logger(label: "AccountViewController") private lazy var purchaseButtonInteractionRestriction = UserInterfaceInteractionRestriction { [weak self] (enableUserInteraction, _) in @@ -236,7 +237,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver { alertController.dismiss(animated: true) { switch result { case .failure(let error): - error.logChain(message: "Failed to log out") + self.logger.error(chainedError: error, message: "Failed to log out") let errorAlertController = UIAlertController( title: NSLocalizedString("Failed to log out", comment: ""), diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 16062c16b9..2ee783dad0 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit import StoreKit +import Logging @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -21,6 +22,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #endif func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + initLoggingSystem(bundleIdentifier: Bundle.main.bundleIdentifier!) + #if targetEnvironment(simulator) SimulatorTunnelProvider.shared.delegate = simulatorTunnelProvider #endif diff --git a/ios/MullvadVPN/AppStorePaymentManager.swift b/ios/MullvadVPN/AppStorePaymentManager.swift index 690c6bb1cd..ed8a1ad1f9 100644 --- a/ios/MullvadVPN/AppStorePaymentManager.swift +++ b/ios/MullvadVPN/AppStorePaymentManager.swift @@ -8,7 +8,7 @@ import Foundation import StoreKit -import os +import Logging enum AppStoreSubscription: String { /// Thirty days non-renewable subscription @@ -122,6 +122,8 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { /// A shared instance of `AppStorePaymentManager` static let shared = AppStorePaymentManager(queue: SKPaymentQueue.default()) + private let logger = Logger(label: "AppStorePaymentManager") + private let operationQueue = OperationQueue() private lazy var exclusivityController = ExclusivityController<ExlcusivityCategory>(operationQueue: operationQueue) @@ -254,8 +256,7 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { createApplePaymentOperation.addDidFinishBlockObserver { (operation, result) in switch result { case .success(let response): - os_log(.info, "AppStore Receipt was processed. Time added: %{public}.2f, New expiry: %{private}s", - response.timeAdded, "\(response.newExpiry)") + self.logger.info("AppStore Receipt was processed. Time added: \(response.timeAdded), New expiry: \(response.newExpiry)") completionHandler(.success(response)) @@ -267,6 +268,7 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { self.exclusivityController.addOperation(createApplePaymentOperation, categories: [.sendReceipt]) case .failure(let error): + self.logger.error(chainedError: error, message: "Failed to fetch the AppStore receipt") completionHandler(.failure(.readReceipt(error))) } } @@ -275,31 +277,28 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { private func handleTransaction(_ transaction: SKPaymentTransaction) { switch transaction.transactionState { case .deferred: - os_log(.debug, "Deferred %{public}s", transaction.payment.productIdentifier) + logger.info("Deferred \(transaction.payment.productIdentifier)") case .failed: - os_log(.debug, "Failed to purchase %{public}s: %{public}s", - transaction.payment.productIdentifier, - transaction.error?.localizedDescription ?? "No error") + logger.error("Failed to purchase \(transaction.payment.productIdentifier): \(transaction.error?.localizedDescription ?? "No error")") didFailPurchase(transaction: transaction) case .purchased: - os_log(.debug, "Purchased %{public}s", transaction.payment.productIdentifier) + logger.info("Purchased \(transaction.payment.productIdentifier)") didFinishOrRestorePurchase(transaction: transaction) case .purchasing: - os_log(.debug, "Purchasing %{public}s", transaction.payment.productIdentifier) + logger.info("Purchasing \(transaction.payment.productIdentifier)") case .restored: - os_log(.debug, "Restored %{public}s", transaction.payment.productIdentifier) + logger.info("Restored \(transaction.payment.productIdentifier)") didFinishOrRestorePurchase(transaction: transaction) @unknown default: - os_log(.debug, "Unknown transactionState = %{public}d", - transaction.transactionState.rawValue) + logger.warning("Unknown transactionState = \(transaction.transactionState.rawValue)") } } @@ -354,7 +353,7 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { } case .failure(let error): - error.logChain(message: "Failed to upload the AppStore receipt") + self.logger.error(chainedError: error, message: "Failed to upload the AppStore receipt") self.observerList.forEach { (observer) in observer.appStorePaymentManager( diff --git a/ios/MullvadVPN/AutomaticKeyRotationManager.swift b/ios/MullvadVPN/AutomaticKeyRotationManager.swift index b9f706d11d..79e435a2a6 100644 --- a/ios/MullvadVPN/AutomaticKeyRotationManager.swift +++ b/ios/MullvadVPN/AutomaticKeyRotationManager.swift @@ -7,7 +7,7 @@ // import Foundation -import os +import Logging /// A private key rotation retry interval on failure (in seconds) private let kRetryIntervalOnFailure = 300 @@ -46,6 +46,8 @@ class AutomaticKeyRotationManager { } } + private let logger = Logger(label: "AutomaticKeyRotationManager") + private let rest = MullvadRest(session: URLSession(configuration: .ephemeral)) private let persistentKeychainReference: Data @@ -89,7 +91,7 @@ class AutomaticKeyRotationManager { dispatchQueue.async { guard !self.isAutomaticRotationEnabled else { return } - os_log(.default, log: tunnelProviderLog, "Start automatic key rotation") + self.logger.info("Start automatic key rotation") self.isAutomaticRotationEnabled = true self.performKeyRotation() @@ -102,7 +104,7 @@ class AutomaticKeyRotationManager { dispatchQueue.async { guard self.isAutomaticRotationEnabled else { return } - os_log(.default, log: tunnelProviderLog, "Stop automatic key rotation") + self.logger.info("Stop automatic key rotation") self.isAutomaticRotationEnabled = false @@ -211,7 +213,7 @@ class AutomaticKeyRotationManager { switch result { case .success(let event): if event.isNew { - os_log(.default, log: tunnelProviderLog, "Finished private key rotation") + logger.info("Finished private key rotation") eventHandler?(event) } @@ -219,26 +221,20 @@ class AutomaticKeyRotationManager { if let rotationDate = Self.nextRotation(creationDate: event.creationDate) { let interval = rotationDate.timeIntervalSinceNow - os_log(.default, log: tunnelProviderLog, - "Next private key rotation on %{public}s", "\(rotationDate)") + logger.info("Next private key rotation on \(rotationDate)") nextRotationTime = .now() + .seconds(Int(interval)) } else { - os_log(.error, log: tunnelProviderLog, - "Failed to compute the next private rotation date. Retry in %d seconds.") + logger.error("Failed to compute the next private rotation date. Retry in \(kRetryIntervalOnFailure) seconds.") nextRotationTime = .now() + .seconds(kRetryIntervalOnFailure) } case .failure(.rest(.network(URLError.cancelled))): - os_log(.default, log: tunnelProviderLog, "Key rotation was cancelled") - break + logger.info("Key rotation was cancelled") case .failure(let error): - os_log(.error, log: tunnelProviderLog, - "Failed to rotate the private key: %{public}s. Retry in %d seconds.", - error.localizedDescription, - kRetryIntervalOnFailure) + logger.error("Failed to rotate the private key. Retry in \(kRetryIntervalOnFailure) seconds. Error: \(error.displayChain())") nextRotationTime = .now() + .seconds(kRetryIntervalOnFailure) } diff --git a/ios/MullvadVPN/ChainedError.swift b/ios/MullvadVPN/ChainedError.swift index 71e0fff44d..cadad2609c 100644 --- a/ios/MullvadVPN/ChainedError.swift +++ b/ios/MullvadVPN/ChainedError.swift @@ -7,7 +7,6 @@ // import Foundation -import os /// A protocol describing errors that can be chained together protocol ChainedError: LocalizedError { @@ -44,10 +43,6 @@ extension ChainedError { return s } - func logChain(message: String? = nil, log: OSLog = .default) { - os_log(.error, log: log, "%{public}s", displayChain(message: message)) - } - private func makeChainIterator() -> AnyIterator<Error> { var current: Error? = self return AnyIterator { () -> Error? in diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift index 53e5fd47ab..16eafc26c9 100644 --- a/ios/MullvadVPN/ConnectViewController.swift +++ b/ios/MullvadVPN/ConnectViewController.swift @@ -8,7 +8,7 @@ import UIKit import NetworkExtension -import os +import Logging class ConnectViewController: UIViewController, RootContainment, TunnelObserver, SelectLocationDelegate @@ -19,6 +19,8 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver, @IBOutlet var connectionPanel: ConnectionPanelView! @IBOutlet var buttonsStackView: UIStackView! + private let logger = Logger(label: "ConnectViewController") + private let connectButton = makeButton(style: .success) private let selectLocationButton = makeButton(style: .translucentNeutral) private lazy var selectLocationBlurView = Self.makeBlurButton(button: selectLocationButton) @@ -95,13 +97,15 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver, TunnelManager.shared.setRelayConstraints(relayConstraints) { [weak self] (result) in DispatchQueue.main.async { + guard let self = self else { return } + switch result { case .success: - os_log(.debug, "Updated relay constraints: %{public}s", "\(relayConstraints)") - self?.connectTunnel() + self.logger.debug("Updated relay constraints: \(relayConstraints)") + self.connectTunnel() case .failure(let error): - os_log(.error, "Failed to update relay constraints: %{public}s", error.localizedDescription) + self.logger.error(chainedError: error, message: "Failed to update relay constraints") } } } @@ -214,7 +218,7 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver, break case .failure(let error): - error.logChain(message: "Failed to start the VPN tunnel") + self.logger.error(chainedError: error, message: "Failed to start the VPN tunnel") let alertController = UIAlertController( title: NSLocalizedString("Failed to start the VPN tunnel", comment: ""), @@ -234,7 +238,7 @@ class ConnectViewController: UIViewController, RootContainment, TunnelObserver, private func disconnectTunnel() { TunnelManager.shared.stopTunnel { (result) in if case .failure(let error) = result { - error.logChain(message: "Failed to stop the VPN tunnel") + self.logger.error(chainedError: error, message: "Failed to stop the VPN tunnel") let alertController = UIAlertController( title: NSLocalizedString("Failed to stop the VPN tunnel", comment: ""), diff --git a/ios/MullvadVPN/Logging/ChainedError+Logger.swift b/ios/MullvadVPN/Logging/ChainedError+Logger.swift new file mode 100644 index 0000000000..a0f396767a --- /dev/null +++ b/ios/MullvadVPN/Logging/ChainedError+Logger.swift @@ -0,0 +1,24 @@ +// +// ChainedError+Logger.swift +// MullvadVPN +// +// Created by pronebird on 02/08/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import Logging + +extension Logger { + + func error<T: ChainedError>(chainedError: T, + message: @autoclosure () -> String? = nil, + metadata: @autoclosure () -> Logger.Metadata? = nil, + source: @autoclosure () -> String? = nil, + file: String = #file, function: String = #function, line: UInt = #line) + { + let s = Message(stringLiteral: chainedError.displayChain(message: message())) + log(level: .error, s, metadata: metadata(), source: source(), file: file, function: function, line: line) + } + +} diff --git a/ios/MullvadVPN/Logging/CustomFormatLogHandler.swift b/ios/MullvadVPN/Logging/CustomFormatLogHandler.swift new file mode 100644 index 0000000000..e8c93a7c42 --- /dev/null +++ b/ios/MullvadVPN/Logging/CustomFormatLogHandler.swift @@ -0,0 +1,68 @@ +// +// CustomFormatLogHandler.swift +// MullvadVPN +// +// Created by pronebird on 02/08/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import Logging + +struct CustomFormatLogHandler: LogHandler { + var metadata: Logger.Metadata = [:] + var logLevel: Logger.Level = .debug + + private let label: String + private let streams: [TextOutputStream] + + init(label: String, streams: [TextOutputStream]) { + self.label = label + self.streams = streams + } + + subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { + get { + return metadata[metadataKey] + } + set(newValue) { + metadata[metadataKey] = newValue + } + } + + func log(level: Logger.Level, + message: Logger.Message, + metadata: Logger.Metadata?, + source: String, + file: String, + function: String, + line: UInt) + { + let mergedMetadata = self.metadata.merging(metadata ?? [:]) { (lhs, rhs) -> Logger.MetadataValue in + return rhs + } + let prettyMetadata = Self.formatMetadata(mergedMetadata) + let metadataOutput = prettyMetadata.isEmpty ? "" : " \(prettyMetadata)" + let formattedMessage = "[\(Self.timestamp())][\(self.label)][\(level)]\(metadataOutput) \(message)\n" + + for var stream in streams { + stream.write(formattedMessage) + } + } + + private static func formatMetadata(_ metadata: Logger.Metadata) -> String { + return metadata.map { "\($0)=\($1)" }.joined(separator: " ") + } + + private static func timestamp() -> String { + var buffer = [Int8](repeating: 0, count: 255) + var timestamp = time(nil) + let localTime = localtime(×tamp) + strftime(&buffer, buffer.count, "%Y-%m-%dT%H:%M:%S%z", localTime) + return buffer.withUnsafeBufferPointer { + $0.withMemoryRebound(to: CChar.self) { + String(cString: $0.baseAddress!) + } + } + } +} diff --git a/ios/MullvadVPN/Logging/LogRotation.swift b/ios/MullvadVPN/Logging/LogRotation.swift new file mode 100644 index 0000000000..fdc66c20b6 --- /dev/null +++ b/ios/MullvadVPN/Logging/LogRotation.swift @@ -0,0 +1,59 @@ +// +// LogRotation.swift +// MullvadVPN +// +// Created by pronebird on 02/08/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +enum LogRotation { + + enum Error: ChainedError { + case noSourceLogFile + case moveSourceLogFile(Swift.Error) + + var errorDescription: String? { + switch self { + case .noSourceLogFile: + return "Source log file does not exist" + case .moveSourceLogFile: + return "Failure to move the source log file to backup" + } + } + } + + static func rotateLog(logsDirectory: URL, logFileName: String) -> Result<(), Error> { + let fileManager = FileManager.default + let source = logsDirectory.appendingPathComponent(logFileName) + let backup = source.deletingPathExtension().appendingPathExtension("old.log") + + return Result { _ = try fileManager.replaceItemAt(backup, withItemAt: source) } + .mapError { (error) -> Error in + // FileManager returns a very obscure error chain so we need to traverse it to find + // the root cause of the error. + var errorCursor: Swift.Error? = error + let cocoaErrorIterator = AnyIterator { () -> CocoaError? in + if let cocoaError = errorCursor as? CocoaError { + errorCursor = cocoaError.underlying + return cocoaError + } else { + errorCursor = nil + return nil + } + } + + while let fileError = cocoaErrorIterator.next() { + // .fileNoSuchFile is returned when both backup and source log files do not exist + // .fileReadNoSuchFile is returned when backup exists but source log file does not + if fileError.code == .fileNoSuchFile || fileError.code == .fileReadNoSuchFile, + fileError.url == source { + return .noSourceLogFile + } + } + + return .moveSourceLogFile(error) + } + } +} diff --git a/ios/MullvadVPN/Logging/Logging.swift b/ios/MullvadVPN/Logging/Logging.swift new file mode 100644 index 0000000000..e21c5a5b24 --- /dev/null +++ b/ios/MullvadVPN/Logging/Logging.swift @@ -0,0 +1,50 @@ +// +// Logging.swift +// MullvadVPN +// +// Created by pronebird on 02/08/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import Logging + +func initLoggingSystem(bundleIdentifier: String) { + let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier)! + let logsDirectoryURL = containerURL.appendingPathComponent("Logs", isDirectory: true) + let logFileName = "\(bundleIdentifier).log" + let logFileURL = logsDirectoryURL.appendingPathComponent(logFileName) + + // Create Logs folder within container if it doesn't exist + try? FileManager.default.createDirectory(at: logsDirectoryURL, withIntermediateDirectories: false, attributes: nil) + + // Rotate log + let logRotationResult = LogRotation.rotateLog(logsDirectory: logsDirectoryURL, logFileName: logFileName) + + // Create an array of log output streams + var streams: [TextOutputStream] = [] + + #if DEBUG + // Add standard output logging in debug + streams.append(TextFileOutputStream.standardOutputStream()) + #endif + + // Create output stream to file + if let fileLogStream = TextFileOutputStream(fileURL: logFileURL, createFile: true) { + streams.append(fileLogStream) + } + + // Configure Logging system + LoggingSystem.bootstrap { (label) -> LogHandler in + if streams.isEmpty { + return SwiftLogNoOpLogHandler() + } else { + return CustomFormatLogHandler(label: label, streams: streams) + } + } + + if case .failure(let logRotationError) = logRotationResult { + Logger(label: "LogRotation") + .error(chainedError: logRotationError, message: "Failed to rotate log") + } +} diff --git a/ios/MullvadVPN/Logging/TextFileOutputStream.swift b/ios/MullvadVPN/Logging/TextFileOutputStream.swift new file mode 100644 index 0000000000..006e79a6e1 --- /dev/null +++ b/ios/MullvadVPN/Logging/TextFileOutputStream.swift @@ -0,0 +1,61 @@ +// +// TextFileOutputStream.swift +// MullvadVPN +// +// Created by pronebird on 02/08/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +class TextFileOutputStream: TextOutputStream { + private let writer: DispatchIO + private let encoding: String.Encoding + private let queue = DispatchQueue.global(qos: .utility) + + class func standardOutputStream(encoding: String.Encoding = .utf8) -> TextFileOutputStream { + return TextFileOutputStream(fileDescriptor: FileHandle.standardOutput.fileDescriptor, encoding: encoding) + } + + init(fileDescriptor: Int32, encoding: String.Encoding = .utf8) { + self.encoding = encoding + writer = DispatchIO(type: .stream, fileDescriptor: fileDescriptor, queue: queue) { (errno) in + if errno != 0 { + print("TextFileOutputStream: closed channel with error: \(errno)") + } + } + } + + init?(fileURL: URL, createFile: Bool, filePermissions: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, encoding: String.Encoding = .utf8) { + var oflag: Int32 = O_WRONLY + var mode: mode_t = .zero + if createFile { + oflag |= O_CREAT + mode = filePermissions + } + + let filePath = fileURL.path.utf8CString.map { $0 } + guard let writer = DispatchIO(type: .stream, path: filePath, oflag: oflag, mode: mode, queue: queue, cleanupHandler: { (errno) in + if errno != 0 { + print("TextFileOutputStream: closed channel with error: \(errno)") + } + }) else { return nil } + + self.writer = writer + self.encoding = encoding + } + + deinit { + writer.close() + } + + func write(_ string: String) { + string.data(using: encoding)?.withUnsafeBytes { (bytes) in + writer.write(offset: .zero, data: DispatchData(bytes: bytes), queue: queue) { (done, data, errno) in + if errno != 0 { + print("TextFileOutputStream: write error: \(errno)") + } + } + } + } +} diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index b6cb895389..9138e15bbb 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -7,7 +7,7 @@ // import UIKit -import os +import Logging private let kMinimumAccountTokenLength = 10 @@ -36,6 +36,8 @@ class LoginViewController: UIViewController, RootContainment { @IBOutlet var statusImageView: UIImageView! @IBOutlet var createAccountButton: AppButton! + private let logger = Logger(label: "LoginViewController") + private var loginState = LoginState.default { didSet { loginStateDidChange() @@ -159,7 +161,7 @@ class LoginViewController: UIViewController, RootContainment { self.endLogin(.success(.existingAccount)) case .failure(let error): - error.logChain(message: "Failed to log in with existing account") + self.logger.error(chainedError: error, message: "Failed to log in with existing account") self.endLogin(.failure(error)) } @@ -179,7 +181,7 @@ class LoginViewController: UIViewController, RootContainment { self.endLogin(.success(.newAccount)) case .failure(let error): - error.logChain(message: "Failed to log in with new account") + self.logger.error(chainedError: error, message: "Failed to log in with new account") self.endLogin(.failure(error)) } diff --git a/ios/MullvadVPN/RelayCache.swift b/ios/MullvadVPN/RelayCache.swift index bf15933187..802e278c14 100644 --- a/ios/MullvadVPN/RelayCache.swift +++ b/ios/MullvadVPN/RelayCache.swift @@ -7,7 +7,7 @@ // import Foundation -import os +import Logging /// Periodic update interval private let kUpdateIntervalSeconds = 3600 @@ -66,6 +66,8 @@ private class AnyRelayCacheObserver: WeakObserverBox, RelayCacheObserver { } class RelayCache { + private let logger = Logger(label: "RelayCache") + /// Mullvad REST client private let rest: MullvadRest @@ -125,7 +127,7 @@ class RelayCache { } case .failure(let readError): - readError.logChain(message: "Failed to read the relay cache") + self.logger.error(chainedError: readError, message: "Failed to read the relay cache") if Self.shouldDownloadRelaysOnReadFailure(readError) { self.scheduleRepeatingTimer(startTime: .now()) @@ -192,7 +194,7 @@ class RelayCache { } case .failure(let readError): - readError.logChain(message: "Failed to read the relay cache") + self.logger.error(chainedError: readError, message: "Failed to read the relay cache") if Self.shouldDownloadRelaysOnReadFailure(readError) { self.downloadRelays() @@ -211,14 +213,16 @@ class RelayCache { switch result { case .success(let cachedRelays): - os_log(.default, "Downloaded %d relays", cachedRelays.relays.wireguard.relays.count) + let numRelays = cachedRelays.relays.wireguard.relays.count + + self.logger.info("Downloaded \(numRelays) relays") self.observerList.forEach { (observer) in observer.relayCache(self, didUpdateCachedRelays: cachedRelays) } case .failure(let error): - error.logChain(message: "Failed to update the relays") + self.logger.error(chainedError: error, message: "Failed to update the relays") } } @@ -230,7 +234,7 @@ class RelayCache { newDownloadTask.resume() case .failure(let restError): - restError.logChain(message: "Failed to create a REST request for updating relays", log: .default) + self.logger.error(chainedError: restError, message: "Failed to create a REST request for updating relays") downloadTask = nil } } diff --git a/ios/MullvadVPN/SelectLocationController.swift b/ios/MullvadVPN/SelectLocationController.swift index d996421b20..44e8751919 100644 --- a/ios/MullvadVPN/SelectLocationController.swift +++ b/ios/MullvadVPN/SelectLocationController.swift @@ -8,7 +8,7 @@ import DiffableDataSources import UIKit -import os +import Logging private let kCellIdentifier = "Cell" @@ -28,6 +28,7 @@ class SelectLocationController: UITableViewController, RelayCacheObserver { } } + private let logger = Logger(label: "SelectLocationController") private var cachedRelays: CachedRelays? private var relayConstraints: RelayConstraints? private var expandedItems = [RelayLocation]() @@ -114,7 +115,7 @@ class SelectLocationController: UITableViewController, RelayCacheObserver { self.didReceiveCachedRelays(cachedRelays, relayConstraints: relayConstraints) case .failure(let error): - error.logChain() + self.logger.error(chainedError: error) } } } @@ -130,7 +131,7 @@ class SelectLocationController: UITableViewController, RelayCacheObserver { self.didReceiveCachedRelays(cachedRelays, relayConstraints: relayConstraints) case .failure(let error): - error.logChain() + self.logger.error(chainedError: error) } completionHandler() @@ -422,7 +423,8 @@ extension ServerRelaysResponse { for (cityCode, relays) in relaysByCity { guard let location = locations[cityCode] else { - os_log(.info, "Location is not found: %{public}s", cityCode) + // TODO: log to file? + print("Location not found: \(cityCode)") continue } diff --git a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift index 5b85acefa9..1e418c2d77 100644 --- a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift +++ b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift @@ -11,10 +11,12 @@ import Foundation import Network import NetworkExtension +import Logging class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { private var connectionInfo: TunnelConnectionInfo? + private let logger = Logger(label: "SimulatorTunnelProviderHost") func startTunnel(options: [String: Any]?, completionHandler: @escaping (Error?) -> Void) { DispatchQueue.main.async { @@ -51,10 +53,10 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { case .success(let request): switch request { case .reloadTunnelSettings: - Self.replyAppMessage(true, completionHandler: completionHandler) + self.replyAppMessage(true, completionHandler: completionHandler) case .tunnelInformation: - Self.replyAppMessage(self.connectionInfo, completionHandler: completionHandler) + self.replyAppMessage(self.connectionInfo, completionHandler: completionHandler) } case .failure: @@ -63,14 +65,14 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { } } - private static func replyAppMessage<T: Encodable>(_ response: T, completionHandler: ((Data?) -> Void)?) + private func replyAppMessage<T: Encodable>(_ response: T, completionHandler: ((Data?) -> Void)?) { switch PacketTunnelIpcHandler.encodeResponse(response: response) { case .success(let data): completionHandler?(data) case .failure(let error): - error.logChain() + self.logger.error(chainedError: error) completionHandler?(nil) } } diff --git a/ios/MullvadVPN/TunnelManager.swift b/ios/MullvadVPN/TunnelManager.swift index f45daca22a..412fea9bf5 100644 --- a/ios/MullvadVPN/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager.swift @@ -8,7 +8,7 @@ import Foundation import NetworkExtension -import os +import Logging enum MapConnectionStatusError: ChainedError { /// A failure to perform the IPC request because the tunnel IPC is already deallocated @@ -239,6 +239,7 @@ class TunnelManager { // MARK: - Internal variables + private let logger = Logger(label: "TunnelManager") private let dispatchQueue = DispatchQueue(label: "net.mullvad.MullvadVPN.TunnelManager") private let rest = MullvadRest(session: URLSession(configuration: .ephemeral)) @@ -266,7 +267,7 @@ class TunnelManager { stateLock.withCriticalBlock { guard _tunnelState != newValue else { return } - os_log(.default, "Set tunnel state: %{public}s", String(reflecting: newValue)) + logger.info("Set tunnel state: \(newValue)") _tunnelState = newValue @@ -315,7 +316,7 @@ class TunnelManager { } else { if let accountToken = self.accountToken { // Migrate the tunnel settings if needed - Self.migrateTunnelSettings(accountToken: accountToken) + self.migrateTunnelSettings(accountToken: accountToken) // Load last known public key self.loadPublicKey(accountToken: accountToken) @@ -444,7 +445,7 @@ class TunnelManager { tunnelIpc.reloadTunnelSettings { (result) in if case .failure(let error) = result { - error.logChain(message: "Failed to reconnect the tunnel") + self.logger.error(chainedError: error, message: "Failed to reconnect the tunnel") } finish() } @@ -525,7 +526,7 @@ class TunnelManager { // Ignore Keychain errors because that normally means that the Keychain // configuration was already removed and we shouldn't be blocking the // user from logging out - error.logChain(message: "Unset account error") + self.logger.error(chainedError: error, message: "Unset account error") } guard let tunnelProvider = self.tunnelProvider else { @@ -563,10 +564,10 @@ class TunnelManager { self.removeWireguardKeyFromServer(accountToken: accountToken, publicKey: publicKey) { (result) in switch result { case .success(let isRemoved): - os_log(.debug, "Removed the WireGuard key from server: %{public}s", "\(isRemoved)") + self.logger.warning("Removed the WireGuard key from server: \(isRemoved)") case .failure(let error): - error.logChain(message: "Unset account error") + self.logger.error(chainedError: error, message: "Unset account error") } removeTunnel() @@ -576,7 +577,7 @@ class TunnelManager { // Ignore Keychain errors because that normally means that the Keychain // configuration was already removed and we shouldn't be blocking the // user from logging out - error.logChain(message: "Unset account error") + self.logger.error(chainedError: error, message: "Unset account error") removeTunnel() } @@ -662,7 +663,7 @@ class TunnelManager { tunnelIpc.reloadTunnelSettings { (ipcResult) in if case .failure(let error) = ipcResult { // Ignore Packet Tunnel IPC errors but log them - error.logChain(message: "Failed to IPC the tunnel to reload configuration") + self.logger.error(chainedError: error, message: "Failed to IPC the tunnel to reload configuration") } finish(.success(())) @@ -701,7 +702,7 @@ class TunnelManager { tunnelIpc.reloadTunnelSettings { (ipcResult) in // Ignore Packet Tunnel IPC errors but log them if case .failure(let error) = ipcResult { - error.logChain(message: "Failed to reload tunnel settings") + self.logger.error(chainedError: error, message: "Failed to reload tunnel settings") } finish(.success(())) @@ -811,7 +812,7 @@ class TunnelManager { self.publicKey = entry.tunnelSettings.interface.privateKey.publicKey case .failure(let error): - error.logChain(message: "Failed to load the public key") + self.logger.error(chainedError: error, message: "Failed to load the public key") self.publicKey = nil } @@ -912,7 +913,7 @@ class TunnelManager { self.tunnelState = tunnelState case .failure(let error): - error.logChain(message: "Failed to map the tunnel state") + self.logger.error(chainedError: error, message: "Failed to map the tunnel state") } finish() @@ -1080,20 +1081,20 @@ class TunnelManager { } } - private class func migrateTunnelSettings(accountToken: String) { + private func migrateTunnelSettings(accountToken: String) { let result = TunnelSettingsManager .migrateKeychainEntry(searchTerm: .accountToken(accountToken)) switch result { case .success(let migrated): if migrated { - os_log("Migrated Keychain tunnel configuration") + self.logger.info("Migrated Keychain tunnel configuration") } else { - os_log("Tunnel settings are up to date. No migration needed.") + self.logger.info("Tunnel settings are up to date. No migration needed.") } case .failure(let error): - error.logChain(message: "Failed to migrate tunnel settings") + self.logger.error(chainedError: error, message: "Failed to migrate tunnel settings") } } diff --git a/ios/MullvadVPN/WireguardKeysViewController.swift b/ios/MullvadVPN/WireguardKeysViewController.swift index d7ed4dfe03..66cd8abf33 100644 --- a/ios/MullvadVPN/WireguardKeysViewController.swift +++ b/ios/MullvadVPN/WireguardKeysViewController.swift @@ -8,7 +8,7 @@ import Foundation import UIKit -import os +import Logging /// A UI refresh interval for the public key creation date (in seconds) private let kCreationDateRefreshInterval = Int(60) @@ -35,6 +35,7 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { private var copyToPasteboardWork: DispatchWorkItem? private let alertPresenter = AlertPresenter() + private let logger = Logger(label: "WireguardKeysViewController") private var state: WireguardKeysViewState = .default { didSet { @@ -207,7 +208,7 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel) ) - error.logChain(message: "Failed to regenerate the private key") + self.logger.error(chainedError: error, message: "Failed to regenerate the private key") self.alertPresenter.enqueue(alertController, presentingController: self) } diff --git a/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift b/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift index 238db4b253..6d55db6612 100644 --- a/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift +++ b/ios/PacketTunnel/AnyIPEndpoint+DNS64.swift @@ -8,7 +8,6 @@ import Foundation import Network -import os extension AnyIPEndpoint { diff --git a/ios/PacketTunnel/Logging.swift b/ios/PacketTunnel/Logging.swift deleted file mode 100644 index 60d20d7c58..0000000000 --- a/ios/PacketTunnel/Logging.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Logging.swift -// PacketTunnel -// -// Created by pronebird on 18/12/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import os - -private let kLogSubsystem = "net.mullvad.vpn.packet-tunnel" - -/// A Wireguard event log -let wireguardLog = OSLog(subsystem: kLogSubsystem, category: "WireGuard") - -/// A general tunnel provider log -let tunnelProviderLog = OSLog(subsystem: kLogSubsystem, category: "Tunnel Provider") - -/// A WireguardDevice log -let wireguardDeviceLog = OSLog(subsystem: kLogSubsystem, category: "WireGuard Device") diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift index 0e71c6aa14..e5249cfb20 100644 --- a/ios/PacketTunnel/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider.swift @@ -9,7 +9,7 @@ import Foundation import Network import NetworkExtension -import os +import Logging enum PacketTunnelProviderError: ChainedError { /// Failure to read the relay cache @@ -109,12 +109,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider { case exclusive } + /// Tunnel provider logger + private let logger: Logger + /// Active wireguard device private var wireguardDevice: WireguardDevice? /// Active tunnel connection information private var connectionInfo: TunnelConnectionInfo? + /// Internal queue private let dispatchQueue = DispatchQueue(label: "net.mullvad.MullvadVPN.PacketTunnel", qos: .utility) private lazy var operationQueue: OperationQueue = { @@ -122,6 +126,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { operationQueue.underlyingQueue = self.dispatchQueue return operationQueue }() + private lazy var exclusivityController: ExclusivityController<OperationCategory> = { return ExclusivityController(operationQueue: self.operationQueue) }() @@ -129,25 +134,26 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private var keyRotationManager: AutomaticKeyRotationManager? override init() { - super.init() + initLoggingSystem(bundleIdentifier: Bundle.main.bundleIdentifier!) + WireguardDevice.setTunnelLogger(Logger(label: "WireGuard")) - self.configureLogger() + logger = Logger(label: "PacketTunnelProvider") } // MARK: - Subclass override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - os_log(.default, log: tunnelProviderLog, "Start the tunnel") + logger.info("Start the tunnel") let operation = AsyncBlockOperation { (finish) in self.doStartTunnel { (result) in switch result { case .success: - os_log(.default, log: tunnelProviderLog, "Started the tunnel") + self.logger.info("Started the tunnel") completionHandler(nil) case .failure(let error): - error.logChain(message: "Failed to start the tunnel", log: tunnelProviderLog) + self.logger.error(chainedError: error, message: "Failed to start the tunnel") completionHandler(error) } @@ -159,15 +165,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - os_log(.default, log: tunnelProviderLog, "Stop the tunnel. Reason: %{public}s", "\(reason)") + logger.info("Stop the tunnel. Reason: \(reason)") let operation = AsyncBlockOperation { (finish) in self.doStopTunnel { (result) in switch result { case .success: - os_log(.default, log: tunnelProviderLog, "Stopped the tunnel") + self.logger.info("Stopped the tunnel") case .failure(let error): - error.logChain(message: "Failed to stop the tunnel", log: tunnelProviderLog) + self.logger.error(chainedError: error, message: "Failed to stop the tunnel") } completionHandler() @@ -188,15 +194,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider { switch request { case .reloadTunnelSettings: self.reloadTunnelSettings { (result) in - Self.replyAppMessage(result.map { true }, completionHandler: completionHandler) + self.replyAppMessage(result.map { true }, completionHandler: completionHandler) } case .tunnelInformation: - Self.replyAppMessage(.success(self.connectionInfo), completionHandler: completionHandler) + self.replyAppMessage(.success(self.connectionInfo), completionHandler: completionHandler) } case .failure(let error): - Self.replyAppMessage(Result<String, PacketTunnelProviderError>.failure(error), completionHandler: completionHandler) + self.replyAppMessage(Result<String, PacketTunnelProviderError>.failure(error), completionHandler: completionHandler) } } } @@ -225,7 +231,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return } - Self.startWireguardDevice(packetFlow: self.packetFlow, configuration: packetTunnelConfig.wireguardConfig) { (result) in + self.startWireguardDevice(packetFlow: self.packetFlow, configuration: packetTunnelConfig.wireguardConfig) { (result) in self.dispatchQueue.async { guard case .success(let device) = result else { completionHandler(result.map { _ in () }) @@ -242,7 +248,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { break case .failure(let error): - error.logChain(message: "Failed to reload tunnel settings", log: tunnelProviderLog) + self.logger.error(chainedError: error, message: "Failed to reload tunnel settings") } } } @@ -290,13 +296,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private func doReloadTunnelSettings(completionHandler: @escaping (Result<(), PacketTunnelProviderError>) -> Void) { guard let device = self.wireguardDevice else { - os_log(.default, log: tunnelProviderLog, "Ignore reloading tunnel settings. The WireguardDevice is not set yet.") + logger.warning("Ignore reloading tunnel settings. The WireguardDevice is not set yet.") completionHandler(.success(())) return } - os_log(.default, log: tunnelProviderLog, "Reload tunnel settings") + logger.info("Reload tunnel settings") makePacketTunnelConfig { (result) in guard case .success(let packetTunnelConfig) = result else { @@ -331,13 +337,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // MARK: - Private - private func configureLogger() { - WireguardDevice.setLogger { (level, message) in - os_log(level.osLogType, log: wireguardLog, "%{public}s", message) - } - } - - private static func replyAppMessage<T: Encodable>( + private func replyAppMessage<T: Encodable>( _ result: Result<T, PacketTunnelProviderError>, completionHandler: ((Data?) -> Void)?) { let result = result.flatMap { (response) -> Result<Data, PacketTunnelProviderError> in @@ -350,7 +350,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler?(data) case .failure(let error): - error.logChain(log: tunnelProviderLog) + self.logger.error(chainedError: error) completionHandler?(nil) } } @@ -363,8 +363,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { location: selectorResult.location ) - os_log(.default, log: tunnelProviderLog, "Tunnel connection info: %{public}s", - selectorResult.relay.hostname) + logger.info("Tunnel connection info: \(selectorResult.relay.hostname)") } private func makePacketTunnelConfig(completionHandler: @escaping (Result<PacketTunnelConfiguration, PacketTunnelProviderError>) -> Void) { @@ -393,18 +392,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider { tunnelSettings: packetTunnelConfig.tunnelSettings ) - os_log(.default, log: tunnelProviderLog, "Updating network settings...") + logger.info("Updating network settings...") setTunnelNetworkSettings(settingsGenerator.networkSettings()) { (error) in self.dispatchQueue.async { if let error = error { - os_log(.error, log: tunnelProviderLog, - "Cannot update network settings: %{public}s", - error.localizedDescription) + self.logger.error("Cannot update network settings: \(error.localizedDescription)") completionHandler(.failure(.setNetworkSettings(error))) } else { - os_log(.default, log: tunnelProviderLog, "Updated network settings") + self.logger.info("Updated network settings") completionHandler(.success(())) } @@ -473,7 +470,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } - private class func startWireguardDevice(packetFlow: NEPacketTunnelFlow, configuration: WireguardConfiguration, completionHandler: @escaping (Result<WireguardDevice, PacketTunnelProviderError>) -> Void) { + private func startWireguardDevice(packetFlow: NEPacketTunnelFlow, configuration: WireguardConfiguration, completionHandler: @escaping (Result<WireguardDevice, PacketTunnelProviderError>) -> Void) { let result = WireguardDevice.fromPacketFlow(packetFlow) guard case .success(let device) = result else { @@ -483,7 +480,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let tunnelDeviceName = device.getInterfaceName() ?? "unknown" - os_log(.default, log: tunnelProviderLog, "Tunnel interface is %{public}s", tunnelDeviceName) + logger.info("Tunnel interface is \(tunnelDeviceName)") device.start(configuration: configuration) { (result) in let result = result.map { device } diff --git a/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift b/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift index b91c7bc326..b4b118a0a0 100644 --- a/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift +++ b/ios/PacketTunnel/PacketTunnelSettingsGenerator.swift @@ -9,7 +9,6 @@ import Foundation import Network import NetworkExtension -import os struct PacketTunnelSettingsGenerator { let mullvadEndpoint: MullvadEndpoint diff --git a/ios/PacketTunnel/WireguardDevice.swift b/ios/PacketTunnel/WireguardDevice.swift index 86af84473c..71f8fe2e80 100644 --- a/ios/PacketTunnel/WireguardDevice.swift +++ b/ios/PacketTunnel/WireguardDevice.swift @@ -8,7 +8,7 @@ import Foundation import NetworkExtension -import os +import Logging /// A class describing the `wireguard-go` interactions /// @@ -16,8 +16,6 @@ import os /// This class is thread safe. class WireguardDevice { - typealias WireguardLogHandler = (WireguardLogLevel, String) -> Void - /// An error type describing the errors returned by `WireguardDevice` enum Error: ChainedError { /// A failure to obtain the tunnel device file descriptor @@ -51,9 +49,12 @@ class WireguardDevice { } } - /// A global Wireguard log handler + /// A global Wireguard logger /// It should only be accessed from the `loggingQueue` - private static var wireguardLogHandler: WireguardLogHandler? + private static var tunnelLogger: Logger? + + /// A logger used by WireguardDevice + private let logger = Logger(label: "WireguardDevice") /// A private queue used for Wireguard logging private static let loggingQueue = DispatchQueue( @@ -94,17 +95,17 @@ class WireguardDevice { /// /// - Thread safety: /// This function is thread safe - class func setLogger(with handler: @escaping WireguardLogHandler) { + class func setTunnelLogger(_ logger: Logger) { WireguardDevice.loggingQueue.async { - WireguardDevice.wireguardLogHandler = handler + WireguardDevice.tunnelLogger = logger } wgSetLogger { (level, messagePtr) in guard let message = messagePtr.map({ String(cString: $0) }) else { return } - let logType = WireguardLogLevel(rawValue: level) ?? .debug + let logLevel = WireguardLogLevel(rawValue: level) ?? .debug WireguardDevice.loggingQueue.async { - WireguardDevice.wireguardLogHandler?(logType, message) + WireguardDevice.tunnelLogger?.log(level: logLevel.loggerLevel, Logger.Message(stringLiteral: message)) } } } @@ -138,7 +139,7 @@ class WireguardDevice { return } - let resolvedConfiguration = Self.resolveConfiguration(configuration) + let resolvedConfiguration = self.resolveConfiguration(configuration) let handle = resolvedConfiguration .uapiConfiguration() .toRawWireguardConfigString() @@ -176,7 +177,7 @@ class WireguardDevice { func setConfiguration(_ newConfiguration: WireguardConfiguration, completionHandler: @escaping (Result<(), Error>) -> Void) { workQueue.async { if let handle = self.wireguardHandle { - let resolvedConfiguration = Self.resolveConfiguration(newConfiguration) + let resolvedConfiguration = self.resolveConfiguration(newConfiguration) let commands = resolvedConfiguration.uapiConfiguration() Self.setWireguardConfig(handle: handle, commands: commands) @@ -222,7 +223,7 @@ class WireguardDevice { .withCString { wgSetConfig(handle, $0) } } - private class func resolveConfiguration(_ configuration: WireguardConfiguration) + private func resolveConfiguration(_ configuration: WireguardConfiguration) -> WireguardConfiguration { return WireguardConfiguration( @@ -232,11 +233,11 @@ class WireguardDevice { ) } - private class func resolvePeers(_ peers: [WireguardPeer]) -> [WireguardPeer] { + private func resolvePeers(_ peers: [WireguardPeer]) -> [WireguardPeer] { var newPeers = [WireguardPeer]() for peer in peers { - switch self.resolvePeer(peer) { + switch resolvePeer(peer) { case .success(let resolvedPeer): newPeers.append(resolvedPeer) case .failure(_): @@ -248,24 +249,19 @@ class WireguardDevice { return newPeers } - private class func resolvePeer(_ peer: WireguardPeer) -> Result<WireguardPeer, Error> { + private func resolvePeer(_ peer: WireguardPeer) -> Result<WireguardPeer, Error> { switch peer.withReresolvedEndpoint() { case .success(let resolvedPeer): if "\(peer.endpoint.ip)" == "\(resolvedPeer.endpoint.ip)" { - os_log(.debug, log: wireguardDeviceLog, - "DNS64: mapped %{public}s to itself", "\(resolvedPeer.endpoint.ip)") + logger.debug("DNS64: mapped \(resolvedPeer.endpoint.ip) to itself") } else { - os_log(.debug, log: wireguardDeviceLog, - "DNS64: mapped %{public}s to %{public}s", - "\(peer.endpoint.ip)", "\(resolvedPeer.endpoint.ip)") + logger.debug("DNS64: mapped \(peer.endpoint.ip) to \(resolvedPeer.endpoint.ip)") } return .success(resolvedPeer) case .failure(let error): - os_log(.error, log: wireguardDeviceLog, - "Failed to re-resolve the peer: %{public}s. Error: %{public}s", - "\(peer.endpoint.ip)", error.localizedDescription) + logger.error("Failed to re-resolve the peer: \(peer.endpoint.ip). Error: \(error.localizedDescription)") return .failure(.resolveEndpoint(peer.endpoint, error)) } @@ -288,14 +284,11 @@ class WireguardDevice { workQueue.async { guard let handle = self.wireguardHandle else { return } - os_log(.debug, log: wireguardDeviceLog, - "Network change detected. Status: %{public}s, interfaces %{public}s.", - String(describing: path.status), - String(describing: path.availableInterfaces)) + self.logger.debug("Network change detected. Status: \(path.status), interfaces \(path.availableInterfaces).") // Re-resolve endpoints on network changes if let currentConfiguration = self.configuration { - let resolvedConfiguration = Self.resolveConfiguration(currentConfiguration) + let resolvedConfiguration = self.resolveConfiguration(currentConfiguration) let commands = resolvedConfiguration.endpointUapiConfiguration() Self.setWireguardConfig(handle: handle, commands: commands) @@ -313,7 +306,7 @@ enum WireguardLogLevel: Int32 { case info = 1 case error = 2 - var osLogType: OSLogType { + var loggerLevel: Logger.Level { switch self { case .debug: return .debug |
