From 0dd4689bf0b56ada264b8cb61cf145e210850035 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 3 Aug 2022 15:37:12 +0800 Subject: [PATCH] Logging changes - Adds `Logging` protocol - Moves to Swift-style `OSLog` usage os_log to Logger os_log audit Replacment of os.log with Logging --- Account/Package.swift | 2 +- Account/Sources/Account/Account.swift | 3 - .../Sources/Account/AccountMetadataFile.swift | 9 +-- .../CloudKit/CloudKitAccountDelegate.swift | 29 +++++----- .../CloudKitArticlesZoneDelegate.swift | 19 +++---- .../CloudKitReceiveStatusOperation.swift | 11 ++-- .../CloudKitRemoteNotificationOperation.swift | 12 ++-- .../CloudKitSendStatusOperation.swift | 18 +++--- .../Reddit/RedditFeedProvider.swift | 3 - ...ditFeedProviderTokenRefreshOperation.swift | 8 +-- .../Feedbin/FeedbinAccountDelegate.swift | 56 +++++++++---------- .../LocalAccount/LocalAccountDelegate.swift | 7 +-- Account/Sources/Account/OPMLFile.swift | 13 ++--- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 40 +++++++------ .../Sources/Account/WebFeedMetadataFile.swift | 9 +-- Articles/Package.swift | 2 +- ArticlesDatabase/Package.swift | 2 +- Mac/AppDelegate.swift | 19 +++---- Mac/ErrorHandler.swift | 8 +-- .../Timeline/TimelineViewController.swift | 1 - .../AccountsFeedbinWindowController.swift | 4 +- .../AccountsNewsBlurWindowController.swift | 4 +- .../AccountsReaderAPIWindowController.swift | 4 +- .../AppDelegate+Scriptability.swift | 3 +- Mac/ShareExtension/ShareViewController.swift | 2 - NetNewsWire.xcodeproj/project.pbxproj | 18 ++++++ .../xcshareddata/swiftpm/Package.resolved | 8 +-- Secrets/Package.swift | 2 +- .../Article Extractor/ArticleExtractor.swift | 4 +- .../ArticleThemeDownloader.swift | 6 +- .../ArticleStyles/ArticleThemesManager.swift | 4 +- Shared/Extensions/CacheCleaner.swift | 10 ++-- Shared/Favicons/FaviconDownloader.swift | 4 +- Shared/Favicons/SingleFaviconDownloader.swift | 15 +++-- Shared/Images/ImageDownloader.swift | 13 ++--- .../ExtensionContainersFile.swift | 13 ++--- .../ExtensionFeedAddRequestFile.swift | 16 +++--- Shared/Widget/WidgetDataDecoder.swift | 8 ++- Shared/Widget/WidgetDataEncoder.swift | 13 ++--- SyncDatabase/Package.swift | 2 +- Widget/TimelineProvider.swift | 23 +++++--- .../Widget Views/SmartFeedSummaryWidget.swift | 2 +- .../FeedbinAccountViewController.swift | 8 ++- .../NewsBlurAccountViewController.swift | 8 ++- .../ReaderAPIAccountViewController.swift | 4 +- iOS/AppDelegate.swift | 41 +++++++------- iOS/ErrorHandler.swift | 9 +-- iOS/SceneCoordinator.swift | 3 +- iOS/SceneDelegate.swift | 6 +- iOS/Settings/ArticleThemeImporter.swift | 4 +- .../ArticleThemesTableViewController.swift | 5 +- iOS/Settings/SettingsViewController.swift | 4 +- 52 files changed, 270 insertions(+), 271 deletions(-) diff --git a/Account/Package.swift b/Account/Package.swift index b6bbb5587..e80cec9d3 100644 --- a/Account/Package.swift +++ b/Account/Package.swift @@ -26,7 +26,7 @@ dependencies.append(contentsOf: [ let package = Package( name: "Account", - platforms: [.macOS(SupportedPlatform.MacOSVersion.v10_15), .iOS(SupportedPlatform.IOSVersion.v13)], + platforms: [.macOS(SupportedPlatform.MacOSVersion.v11), .iOS(SupportedPlatform.IOSVersion.v14)], products: [ .library( name: "Account", diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index d490b6d1a..13584afe7 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -17,7 +17,6 @@ import RSParser import RSDatabase import ArticlesDatabase import RSWeb -import os.log import Secrets // Main thread only. @@ -91,8 +90,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return defaultName }() - - var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "account") public var isDeleted = false diff --git a/Account/Sources/Account/AccountMetadataFile.swift b/Account/Sources/Account/AccountMetadataFile.swift index d3bc67d65..4f52d2728 100644 --- a/Account/Sources/Account/AccountMetadataFile.swift +++ b/Account/Sources/Account/AccountMetadataFile.swift @@ -7,12 +7,9 @@ // import Foundation -import os.log import RSCore -final class AccountMetadataFile { - - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "accountMetadataFile") +final class AccountMetadataFile: Logging { private let fileURL: URL private let account: Account @@ -50,8 +47,8 @@ final class AccountMetadataFile { do { let data = try encoder.encode(account.metadata) try data.write(to: fileURL) - } catch let error as NSError { - os_log(.error, log: log, "Save to disk failed: %@.", error.localizedDescription) + } catch let error { + logger.error("Save to disk failed: \(error.localizedDescription)") } } diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 98baf7565..bcd4689bf 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -9,7 +9,6 @@ import Foundation import CloudKit import SystemConfiguration -import os.log import SyncDatabase import RSCore import RSParser @@ -27,9 +26,7 @@ enum CloudKitAccountDelegateError: LocalizedError { } } -final class CloudKitAccountDelegate: AccountDelegate { - - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") +final class CloudKitAccountDelegate: AccountDelegate, Logging { private let database: SyncDatabase @@ -318,10 +315,10 @@ final class CloudKitAccountDelegate: AccountDelegate { for webFeed in webFeeds { group.enter() - self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { result in + self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { [weak self] result in group.leave() if case .failure(let error) = result { - os_log(.error, log: self.log, "Remove folder, remove webfeed error: %@.", error.localizedDescription) + self?.logger.error("Remove folder, remove webfeed error: \(error.localizedDescription, privacy: .public)") errorOccurred = true } } @@ -380,14 +377,14 @@ final class CloudKitAccountDelegate: AccountDelegate { folder.topLevelWebFeeds.remove(feed) group.enter() - self.restoreWebFeed(for: account, feed: feed, container: folder) { result in - self.refreshProgress.completeTask() + self.restoreWebFeed(for: account, feed: feed, container: folder) { [weak self] result in + self?.refreshProgress.completeTask() group.leave() switch result { case .success: break case .failure(let error): - os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription) + self?.logger.error("Restore folder feed error: \(error.localizedDescription, privacy: .public)") } } @@ -437,13 +434,13 @@ final class CloudKitAccountDelegate: AccountDelegate { // Check to see if this is a new account and initialize anything we need if account.externalID == nil { - accountZone.findOrCreateAccount() { result in + accountZone.findOrCreateAccount() { [weak self] result in switch result { case .success(let externalID): account.externalID = externalID - self.initialRefreshAll(for: account) { _ in } + self?.initialRefreshAll(for: account) { _ in } case .failure(let error): - os_log(.error, log: self.log, "Error adding account container: %@", error.localizedDescription) + self?.logger.error("Error adding account container: \(error.localizedDescription, privacy: .public)") } } accountZone.subscribeToZoneChanges() @@ -589,7 +586,7 @@ private extension CloudKitAccountDelegate { group.leave() } case .failure(let error): - os_log(.error, log: self.log, "CloudKit Feed refresh update error: %@.", error.localizedDescription) + self.logger.error("CloudKit Feed refresh update error: \(error.localizedDescription, privacy: .public)") self.refreshProgress.completeTask() group.leave() } @@ -597,7 +594,7 @@ private extension CloudKitAccountDelegate { } case .failure(let error): - os_log(.error, log: self.log, "CloudKit Feed refresh error: %@.", error.localizedDescription) + self.logger.error("CloudKit Feed refresh error: \(error.localizedDescription, privacy: .public)") feedProviderError = error self.refreshProgress.completeTask() group.leave() @@ -804,12 +801,12 @@ private extension CloudKitAccountDelegate { case .success: self.articlesZone.fetchChangesInZone() { _ in } case .failure(let error): - os_log(.error, log: self.log, "CloudKit Feed send articles error: %@.", error.localizedDescription) + self.logger.error("CloudKit Feed send articles error: \(error.localizedDescription, privacy: .public)") } } } case .failure(let error): - os_log(.error, log: self.log, "CloudKit Feed send articles error: %@.", error.localizedDescription) + self.logger.error("CloudKit Feed send articles error: \(error.localizedDescription, privacy: .public)") } } } diff --git a/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift index 9f006c7bc..825de657b 100644 --- a/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitArticlesZoneDelegate.swift @@ -7,7 +7,6 @@ // import Foundation -import os.log import RSCore import RSParser import RSWeb @@ -16,10 +15,8 @@ import SyncDatabase import Articles import ArticlesDatabase -class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate { +class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate, Logging { - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") - weak var account: Account? var database: SyncDatabase weak var articlesZone: CloudKitArticlesZone? @@ -49,12 +46,12 @@ class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate { } case .failure(let error): - os_log(.error, log: self.log, "Error occurred getting pending starred records: %@", error.localizedDescription) + self.logger.error("Error occurred getting pending starred records: \(error.localizedDescription)") completion(.failure(CloudKitZoneError.unknown)) } } case .failure(let error): - os_log(.error, log: self.log, "Error occurred getting pending read status records: %@", error.localizedDescription) + self.logger.error("Error occurred getting pending read status records: \(error.localizedDescription)") completion(.failure(CloudKitZoneError.unknown)) } @@ -102,7 +99,7 @@ private extension CloudKitArticlesZoneDelegate { account?.markAsUnread(updateableUnreadArticleIDs) { result in if case .failure(let databaseError) = result { errorOccurred = true - os_log(.error, log: self.log, "Error occurred while storing unread statuses: %@", databaseError.localizedDescription) + self.logger.error("Error occurred while storing unread statuses: \(databaseError.localizedDescription)") } group.leave() } @@ -111,7 +108,7 @@ private extension CloudKitArticlesZoneDelegate { account?.markAsRead(updateableReadArticleIDs) { result in if case .failure(let databaseError) = result { errorOccurred = true - os_log(.error, log: self.log, "Error occurred while storing read statuses: %@", databaseError.localizedDescription) + self.logger.error("Error occurred while storing read statuses: \(databaseError.localizedDescription)") } group.leave() } @@ -120,7 +117,7 @@ private extension CloudKitArticlesZoneDelegate { account?.markAsUnstarred(updateableUnstarredArticleIDs) { result in if case .failure(let databaseError) = result { errorOccurred = true - os_log(.error, log: self.log, "Error occurred while storing unstarred statuses: %@", databaseError.localizedDescription) + self.logger.error("Error occurred while storing unstarred statuses: \(databaseError.localizedDescription)") } group.leave() } @@ -129,7 +126,7 @@ private extension CloudKitArticlesZoneDelegate { account?.markAsStarred(updateableStarredArticleIDs) { result in if case .failure(let databaseError) = result { errorOccurred = true - os_log(.error, log: self.log, "Error occurred while storing starred statuses: %@", databaseError.localizedDescription) + self.logger.error("Error occurred while stroing starred records: \(databaseError.localizedDescription)") } group.leave() } @@ -155,7 +152,7 @@ private extension CloudKitArticlesZoneDelegate { } case .failure(let databaseError): errorOccurred = true - os_log(.error, log: self.log, "Error occurred while storing articles: %@", databaseError.localizedDescription) + self.logger.error("Error occurred while storing articles: \(databaseError.localizedDescription)") group.leave() } } diff --git a/Account/Sources/Account/CloudKit/CloudKitReceiveStatusOperation.swift b/Account/Sources/Account/CloudKit/CloudKitReceiveStatusOperation.swift index 012942db5..5892b7164 100644 --- a/Account/Sources/Account/CloudKit/CloudKitReceiveStatusOperation.swift +++ b/Account/Sources/Account/CloudKit/CloudKitReceiveStatusOperation.swift @@ -7,13 +7,10 @@ // import Foundation -import os.log import RSCore -class CloudKitReceiveStatusOperation: MainThreadOperation { +class CloudKitReceiveStatusOperation: MainThreadOperation, Logging { - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") - // MainThreadOperation public var isCanceled = false public var id: Int? @@ -33,15 +30,15 @@ class CloudKitReceiveStatusOperation: MainThreadOperation { return } - os_log(.debug, log: log, "Refreshing article statuses...") + logger.debug("Refreshing article statuses...") articlesZone.refreshArticles() { result in - os_log(.debug, log: self.log, "Done refreshing article statuses.") + self.logger.debug("Done refreshing article statuses.") switch result { case .success: self.operationDelegate?.operationDidComplete(self) case .failure(let error): - os_log(.error, log: self.log, "Receive status error: %@.", error.localizedDescription) + self.logger.debug("Receive status error: \(error.localizedDescription)") self.operationDelegate?.cancelOperation(self) } } diff --git a/Account/Sources/Account/CloudKit/CloudKitRemoteNotificationOperation.swift b/Account/Sources/Account/CloudKit/CloudKitRemoteNotificationOperation.swift index 008b84846..73833af34 100644 --- a/Account/Sources/Account/CloudKit/CloudKitRemoteNotificationOperation.swift +++ b/Account/Sources/Account/CloudKit/CloudKitRemoteNotificationOperation.swift @@ -7,14 +7,10 @@ // import Foundation - -import os.log import RSCore -class CloudKitRemoteNotificationOperation: MainThreadOperation { +class CloudKitRemoteNotificationOperation: MainThreadOperation, Logging { - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") - // MainThreadOperation public var isCanceled = false public var id: Int? @@ -37,12 +33,12 @@ class CloudKitRemoteNotificationOperation: MainThreadOperation { self.operationDelegate?.operationDidComplete(self) return } - - os_log(.debug, log: log, "Processing remote notification...") + + logger.debug("Processing remote notification...") accountZone.receiveRemoteNotification(userInfo: userInfo) { articlesZone.receiveRemoteNotification(userInfo: self.userInfo) { - os_log(.debug, log: self.log, "Done processing remote notification.") + self.logger.debug("Done processing remote notification.") self.operationDelegate?.operationDidComplete(self) } } diff --git a/Account/Sources/Account/CloudKit/CloudKitSendStatusOperation.swift b/Account/Sources/Account/CloudKit/CloudKitSendStatusOperation.swift index 6592ae7c1..021a73b72 100644 --- a/Account/Sources/Account/CloudKit/CloudKitSendStatusOperation.swift +++ b/Account/Sources/Account/CloudKit/CloudKitSendStatusOperation.swift @@ -8,14 +8,12 @@ import Foundation import Articles -import os.log import RSCore import RSWeb import SyncDatabase -class CloudKitSendStatusOperation: MainThreadOperation { +class CloudKitSendStatusOperation: MainThreadOperation, Logging { - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") private let blockSize = 150 // MainThreadOperation @@ -40,7 +38,7 @@ class CloudKitSendStatusOperation: MainThreadOperation { } func run() { - os_log(.debug, log: log, "Sending article statuses...") + logger.debug("Sending article statuses...") if showProgress { @@ -51,7 +49,7 @@ class CloudKitSendStatusOperation: MainThreadOperation { self.refreshProgress?.addToNumberOfTasksAndRemaining(ticks) self.selectForProcessing() case .failure(let databaseError): - os_log(.error, log: self.log, "Send status count pending error: %@.", databaseError.localizedDescription) + self.logger.error("Send status count pending error: \(databaseError.localizedDescription)") self.operationDelegate?.cancelOperation(self) } } @@ -77,7 +75,7 @@ private extension CloudKitSendStatusOperation { if self.showProgress { self.refreshProgress?.completeTask() } - os_log(.debug, log: self.log, "Done sending article statuses.") + self.logger.debug("Done sending article statuses.") self.operationDelegate?.operationDidComplete(self) } @@ -95,7 +93,7 @@ private extension CloudKitSendStatusOperation { } case .failure(let databaseError): - os_log(.error, log: self.log, "Send status error: %@.", databaseError.localizedDescription) + self.logger.error("Send status error: \(databaseError.localizedDescription)") self.operationDelegate?.cancelOperation(self) } } @@ -125,7 +123,7 @@ private extension CloudKitSendStatusOperation { if self.showProgress && self.refreshProgress?.numberRemaining ?? 0 > 1 { self.refreshProgress?.completeTask() } - os_log(.debug, log: self.log, "Done sending article status block...") + self.logger.debug("Done sending article status block...") completion(stop) } @@ -147,7 +145,7 @@ private extension CloudKitSendStatusOperation { case .failure(let error): self.database.resetSelectedForProcessing(syncStatuses.map({ $0.articleID })) { _ in self.processAccountError(account, error) - os_log(.error, log: self.log, "Send article status modify articles error: %@.", error.localizedDescription) + self.logger.error("Send article status modify articles error: \(error.localizedDescription)") completion(true) } } @@ -161,7 +159,7 @@ private extension CloudKitSendStatusOperation { processWithArticles(articles) case .failure(let databaseError): self.database.resetSelectedForProcessing(syncStatuses.map({ $0.articleID })) { _ in - os_log(.error, log: self.log, "Send article status fetch articles error: %@.", databaseError.localizedDescription) + self.logger.error("Send article status fetch articles error: \(databaseError.localizedDescription)") completion(true) } } diff --git a/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProvider.swift b/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProvider.swift index 651825259..5622d6793 100644 --- a/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProvider.swift +++ b/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProvider.swift @@ -7,7 +7,6 @@ // import Foundation -import os.log import OAuthSwift import Secrets import RSCore @@ -40,8 +39,6 @@ public enum RedditFeedType: Int { public final class RedditFeedProvider: FeedProvider, RedditFeedProviderTokenRefreshOperationDelegate { - var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "RedditFeedProvider") - private static let homeURL = "https://www.reddit.com" private static let server = "www.reddit.com" private static let apiBase = "https://oauth.reddit.com" diff --git a/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProviderTokenRefreshOperation.swift b/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProviderTokenRefreshOperation.swift index 6a90e9984..691f9b890 100644 --- a/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProviderTokenRefreshOperation.swift +++ b/Account/Sources/Account/FeedProvider/Reddit/RedditFeedProviderTokenRefreshOperation.swift @@ -19,9 +19,7 @@ protocol RedditFeedProviderTokenRefreshOperationDelegate: AnyObject { var oauthSwift: OAuth2Swift? { get } } -class RedditFeedProviderTokenRefreshOperation: MainThreadOperation { - - var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "RedditFeedProvider") +class RedditFeedProviderTokenRefreshOperation: MainThreadOperation, Logging { public var isCanceled = false public var id: Int? @@ -49,7 +47,7 @@ class RedditFeedProviderTokenRefreshOperation: MainThreadOperation { return } - os_log(.debug, log: self.log, "Access token expired, attempting to renew...") + logger.debug("Access token expired, attempting to renew...") delegate.oauthSwift?.renewAccessToken(withRefreshToken: delegate.oauthRefreshToken) { [weak self] result in guard let self = self else { return } @@ -61,7 +59,7 @@ class RedditFeedProviderTokenRefreshOperation: MainThreadOperation { do { try RedditFeedProvider.storeCredentials(username: username, oauthToken: delegate.oauthToken, oauthRefreshToken: delegate.oauthRefreshToken) delegate.oauthTokenLastRefresh = Date() - os_log(.debug, log: self.log, "Access token renewed.") + self.logger.debug("Access token renewed.") } catch { self.error = error self.operationDelegate?.operationDidComplete(self) diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 418c1c81a..43fea522f 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -12,7 +12,6 @@ import RSDatabase import RSParser import RSWeb import SyncDatabase -import os.log import Secrets public enum FeedbinAccountDelegateError: String, Error { @@ -20,12 +19,11 @@ public enum FeedbinAccountDelegateError: String, Error { case unknown = "An unknown error occurred." } -final class FeedbinAccountDelegate: AccountDelegate { +final class FeedbinAccountDelegate: AccountDelegate, Logging { private let database: SyncDatabase private let caller: FeedbinAPICaller - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Feedbin") let behaviors: AccountBehaviors = [.disallowFeedCopyInRootFolder] let server: String? = "api.feedbin.com" @@ -132,7 +130,7 @@ final class FeedbinAccountDelegate: AccountDelegate { func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - os_log(.debug, log: log, "Sending article statuses...") + logger.debug("Sending article statuses") database.selectForProcessing { result in @@ -178,7 +176,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } group.notify(queue: DispatchQueue.main) { - os_log(.debug, log: self.log, "Done sending article statuses.") + self.logger.debug("Done sending article statuses.") if errorOccurred { completion(.failure(FeedbinAccountDelegateError.unknown)) } else { @@ -198,7 +196,7 @@ final class FeedbinAccountDelegate: AccountDelegate { func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - os_log(.debug, log: log, "Refreshing article statuses...") + logger.debug("Refreshing article statuses...") let group = DispatchGroup() var errorOccurred = false @@ -212,7 +210,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): errorOccurred = true - os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription) + self.logger.error("Retrieving unread entries failed: \(error.localizedDescription, privacy: .public)") group.leave() } @@ -227,14 +225,14 @@ final class FeedbinAccountDelegate: AccountDelegate { } case .failure(let error): errorOccurred = true - os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription) + self.logger.error("Retrieving starred entries failed: \(error.localizedDescription, privacy: .public)") group.leave() } } group.notify(queue: DispatchQueue.main) { - os_log(.debug, log: self.log, "Done refreshing article statuses.") + self.logger.debug("Done refreshing article statuses.") if errorOccurred { completion(.failure(FeedbinAccountDelegateError.unknown)) } else { @@ -260,7 +258,7 @@ final class FeedbinAccountDelegate: AccountDelegate { return } - os_log(.debug, log: log, "Begin importing OPML...") + logger.debug("Begin importing OPML...") isOPMLImportInProgress = true refreshProgress.addToNumberOfTasksAndRemaining(1) @@ -268,7 +266,7 @@ final class FeedbinAccountDelegate: AccountDelegate { switch result { case .success(let importResult): if importResult.complete { - os_log(.debug, log: self.log, "Import OPML done.") + self.logger.debug("Import OPML done.") self.refreshProgress.completeTask() self.isOPMLImportInProgress = false DispatchQueue.main.async { @@ -278,7 +276,7 @@ final class FeedbinAccountDelegate: AccountDelegate { self.checkImportResult(opmlImportResultID: importResult.importResultID, completion: completion) } case .failure(let error): - os_log(.debug, log: self.log, "Import OPML failed.") + self.logger.error("Import OPML failed: \(error.localizedDescription, privacy: .public)") self.refreshProgress.completeTask() self.isOPMLImportInProgress = false DispatchQueue.main.async { @@ -352,7 +350,7 @@ final class FeedbinAccountDelegate: AccountDelegate { self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") } case .failure(let error): - os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) + self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") } } } @@ -371,7 +369,7 @@ final class FeedbinAccountDelegate: AccountDelegate { account.clearWebFeedMetadata(feed) } case .failure(let error): - os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) + self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") } } @@ -541,7 +539,7 @@ final class FeedbinAccountDelegate: AccountDelegate { case .success: break case .failure(let error): - os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription) + self.logger.error("Restore folder feed error: \(error.localizedDescription, privacy: .public)") } } @@ -624,13 +622,13 @@ private extension FeedbinAccountDelegate { Timer.scheduledTimer(withTimeInterval: 15, repeats: true) { timer in - os_log(.debug, log: self.log, "Checking status of OPML import...") + self.logger.debug("Checking status of OPML import...") self.caller.retrieveOPMLImportResult(importID: opmlImportResultID) { result in switch result { case .success(let importResult): if let result = importResult, result.complete { - os_log(.debug, log: self.log, "Checking status of OPML import successfully completed.") + self.logger.debug("Checking status of OPML import successfully completed.") timer.invalidate() self.refreshProgress.completeTask() self.isOPMLImportInProgress = false @@ -639,7 +637,7 @@ private extension FeedbinAccountDelegate { } } case .failure(let error): - os_log(.debug, log: self.log, "Import OPML check failed.") + self.logger.debug("Import OPML check failed.") timer.invalidate() self.refreshProgress.completeTask() self.isOPMLImportInProgress = false @@ -772,7 +770,7 @@ private extension FeedbinAccountDelegate { guard let tags = tags else { return } assert(Thread.isMainThread) - os_log(.debug, log: log, "Syncing folders with %ld tags.", tags.count) + logger.debug("Syncing folders with \(tags.count, privacy: .public) tags.") let tagNames = tags.map { $0.name } @@ -811,7 +809,7 @@ private extension FeedbinAccountDelegate { guard let subscriptions = subscriptions else { return } assert(Thread.isMainThread) - os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count) + logger.debug("Syncing feeds with \(subscriptions.count, privacy: .public) subscriptions.") let subFeedIds = subscriptions.map { String($0.feedID) } @@ -865,7 +863,7 @@ private extension FeedbinAccountDelegate { guard let taggings = taggings else { return } assert(Thread.isMainThread) - os_log(.debug, log: log, "Syncing taggings with %ld taggings.", taggings.count) + logger.debug("Syncing taggings with \(taggings.count) taggings.") // Set up some structures to make syncing easier let folderDict = nameToFolderDictionary(with: account.folders) @@ -961,7 +959,7 @@ private extension FeedbinAccountDelegate { group.leave() case .failure(let error): errorOccurred = true - os_log(.error, log: self.log, "Article status sync call failed: %@.", error.localizedDescription) + self.logger.error("Article status sync call failed: \(error.localizedDescription, privacy: .public)") self.database.resetSelectedForProcessing(articleIDGroup.map { String($0) } ) group.leave() } @@ -1122,7 +1120,7 @@ private extension FeedbinAccountDelegate { func refreshArticles(_ account: Account, completion: @escaping VoidResultCompletionBlock) { - os_log(.debug, log: log, "Refreshing articles...") + logger.debug("Refreshing articles...") caller.retrieveEntries() { result in @@ -1143,7 +1141,7 @@ private extension FeedbinAccountDelegate { } self.refreshArticles(account, page: page, updateFetchDate: updateFetchDate) { result in - os_log(.debug, log: self.log, "Done refreshing articles.") + self.logger.debug("Done refreshing articles.") switch result { case .success: completion(.success(())) @@ -1160,7 +1158,7 @@ private extension FeedbinAccountDelegate { } func refreshMissingArticles(_ account: Account, completion: @escaping ((Result) -> Void)) { - os_log(.debug, log: log, "Refreshing missing articles...") + logger.debug("Refreshing missing articles...") account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in @@ -1187,7 +1185,7 @@ private extension FeedbinAccountDelegate { case .failure(let error): errorOccurred = true - os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription) + self.logger.error("Refreshing missing articles failed: \(error.localizedDescription, privacy: .public)") group.leave() } } @@ -1195,7 +1193,7 @@ private extension FeedbinAccountDelegate { group.notify(queue: DispatchQueue.main) { self.refreshProgress.completeTask() - os_log(.debug, log: self.log, "Done refreshing missing articles.") + self.logger.debug("Done refreshing missing articles.") if errorOccurred { completion(.failure(FeedbinAccountDelegateError.unknown)) } else { @@ -1312,7 +1310,7 @@ private extension FeedbinAccountDelegate { case .success(let pendingArticleIDs): process(pendingArticleIDs) case .failure(let error): - os_log(.error, log: self.log, "Sync Article Read Status failed: %@.", error.localizedDescription) + self.logger.error("Sync Articles Read Status failed: \(error.localizedDescription, privacy: .public)") } } @@ -1364,7 +1362,7 @@ private extension FeedbinAccountDelegate { case .success(let pendingArticleIDs): process(pendingArticleIDs) case .failure(let error): - os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription) + self.logger.error("Sync Article Starred Status failed: \(error.localizedDescription, privacy: .public)") } } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 456c8b738..9ea2d1f25 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -7,7 +7,6 @@ // import Foundation -import os.log import RSCore import RSParser import Articles @@ -19,9 +18,7 @@ public enum LocalAccountDelegateError: String, Error { case invalidParameter = "An invalid parameter was used." } -final class LocalAccountDelegate: AccountDelegate { - - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "LocalAccount") +final class LocalAccountDelegate: AccountDelegate, Logging { weak var account: Account? @@ -68,7 +65,7 @@ final class LocalAccountDelegate: AccountDelegate { group.leave() } case .failure(let error): - os_log(.error, log: self.log, "Feed Provider refresh error: %@.", error.localizedDescription) + self.logger.error("Feed Provided refresh error: \(error.localizedDescription)") feedProviderError = error self.refreshProgress.completeTask() group.leave() diff --git a/Account/Sources/Account/OPMLFile.swift b/Account/Sources/Account/OPMLFile.swift index 134358c35..c461c6f33 100644 --- a/Account/Sources/Account/OPMLFile.swift +++ b/Account/Sources/Account/OPMLFile.swift @@ -7,13 +7,10 @@ // import Foundation -import os.log import RSCore import RSParser -final class OPMLFile { - - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile") +final class OPMLFile: Logging { private let fileURL: URL private let account: Account @@ -50,8 +47,8 @@ final class OPMLFile { do { try opmlDocumentString.write(to: fileURL, atomically: true, encoding: .utf8) - } catch let error as NSError { - os_log(.error, log: log, "OPML save to disk failed: %@.", error.localizedDescription) + } catch let error { + logger.error("OPML save to disk failed: \(error.localizedDescription)") } } @@ -76,7 +73,7 @@ private extension OPMLFile { do { fileData = try Data(contentsOf: fileURL) } catch { - os_log(.error, log: log, "OPML read from disk failed: %@.", error.localizedDescription) + logger.error("OPML read from disk failed: \(error.localizedDescription)") } return fileData @@ -89,7 +86,7 @@ private extension OPMLFile { do { opmlDocument = try RSOPMLParser.parseOPML(with: parserData) } catch { - os_log(.error, log: log, "OPML Import failed: %@.", error.localizedDescription) + logger.error("OPML Import failed: \(error.localizedDescription)") return nil } diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 58422a96f..2182fb396 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -11,7 +11,6 @@ import RSCore import RSParser import RSWeb import SyncDatabase -import os.log import Secrets public enum ReaderAPIAccountDelegateError: LocalizedError { @@ -34,14 +33,13 @@ public enum ReaderAPIAccountDelegateError: LocalizedError { } } -final class ReaderAPIAccountDelegate: AccountDelegate { +final class ReaderAPIAccountDelegate: AccountDelegate, Logging { private let variant: ReaderAPIVariant private let database: SyncDatabase private let caller: ReaderAPICaller - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ReaderAPI") var behaviors: AccountBehaviors { var behaviors: AccountBehaviors = [.disallowOPMLImports, .disallowFeedInMultipleFolders] @@ -196,7 +194,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - os_log(.debug, log: log, "Sending article statuses...") + logger.debug("Sending article statuses...") database.selectForProcessing { result in @@ -229,7 +227,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } group.notify(queue: DispatchQueue.main) { - os_log(.debug, log: self.log, "Done sending article statuses.") + self.logger.debug("Done sending article statuses.") completion(.success(())) } } @@ -244,7 +242,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { - os_log(.debug, log: log, "Refreshing article statuses...") + logger.debug("Refreshing article statuses...") let group = DispatchGroup() var errorOccurred = false @@ -258,7 +256,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } case .failure(let error): errorOccurred = true - os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription) + self.logger.info("Retrieving unread entries failed: \(error.localizedDescription)") group.leave() } @@ -273,14 +271,14 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } case .failure(let error): errorOccurred = true - os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription) + self.logger.info("Retrieving starred entries failed: \(error.localizedDescription)") group.leave() } } group.notify(queue: DispatchQueue.main) { - os_log(.debug, log: self.log, "Done refreshing article statuses.") + self.logger.debug("Done refreshing article statuses.") if errorOccurred { completion(.failure(ReaderAPIAccountDelegateError.unknown)) } else { @@ -342,7 +340,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID) } case .failure(let error): - os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) + self.logger.error("Remove feed error: \(error.localizedDescription)") } } } @@ -361,7 +359,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { account.clearWebFeedMetadata(feed) } case .failure(let error): - os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) + self.logger.error("Remove feed error: \(error.localizedDescription)") } } @@ -593,7 +591,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { case .success: break case .failure(let error): - os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription) + self.logger.error("Restore folder feed error: \(error.localizedDescription)") } } @@ -711,7 +709,7 @@ private extension ReaderAPIAccountDelegate { guard !folderTags.isEmpty else { return } - os_log(.debug, log: log, "Syncing folders with %ld tags.", folderTags.count) + logger.debug("Syncing folders with \(folderTags.count) tags.") let readerFolderExternalIDs = folderTags.compactMap { $0.tagID } @@ -751,7 +749,7 @@ private extension ReaderAPIAccountDelegate { guard let subscriptions = subscriptions else { return } assert(Thread.isMainThread) - os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count) + logger.debug("Syncing feeds with \(subscriptions.count) subscriptions") let subFeedIds = subscriptions.map { $0.feedID } @@ -793,7 +791,7 @@ private extension ReaderAPIAccountDelegate { func syncFeedFolderRelationship(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) { guard let subscriptions = subscriptions else { return } assert(Thread.isMainThread) - os_log(.debug, log: log, "Syncing taggings with %ld subscriptions.", subscriptions.count) + logger.debug("Syncing taggins with \(subscriptions.count) subscriptions.") // Set up some structures to make syncing easier let folderDict = externalIDToFolderDictionary(with: account.folders) @@ -887,7 +885,7 @@ private extension ReaderAPIAccountDelegate { self.database.deleteSelectedForProcessing(articleIDGroup.map { $0 } ) group.leave() case .failure(let error): - os_log(.error, log: self.log, "Article status sync call failed: %@.", error.localizedDescription) + self.logger.error("Article status sync call failed: \(error.localizedDescription)") self.database.resetSelectedForProcessing(articleIDGroup.map { $0 } ) group.leave() } @@ -987,7 +985,7 @@ private extension ReaderAPIAccountDelegate { return } - os_log(.debug, log: self.log, "Refreshing missing articles...") + self.logger.debug("Refreshing missing articles...") let group = DispatchGroup() let articleIDs = Array(fetchedArticleIDs) @@ -1007,7 +1005,7 @@ private extension ReaderAPIAccountDelegate { } case .failure(let error): - os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription) + self.logger.error("Refresh missing articles failed: \(error.localizedDescription)") group.leave() } } @@ -1015,7 +1013,7 @@ private extension ReaderAPIAccountDelegate { group.notify(queue: DispatchQueue.main) { self.refreshProgress.completeTask() - os_log(.debug, log: self.log, "Done refreshing missing articles.") + self.logger.debug("Done refreshing missing articles.") completion() } } @@ -1121,7 +1119,7 @@ private extension ReaderAPIAccountDelegate { case .success(let pendingArticleIDs): process(pendingArticleIDs) case .failure(let error): - os_log(.error, log: self.log, "Sync Article Read Status failed: %@.", error.localizedDescription) + self.logger.error("Sync Article Read Status failed: \(error.localizedDescription)") } } @@ -1170,7 +1168,7 @@ private extension ReaderAPIAccountDelegate { case .success(let pendingArticleIDs): process(pendingArticleIDs) case .failure(let error): - os_log(.error, log: self.log, "Sync Article Starred Status failed: %@.", error.localizedDescription) + self.logger.error("Sync Article Starred Status failed: \(error.localizedDescription)") } } diff --git a/Account/Sources/Account/WebFeedMetadataFile.swift b/Account/Sources/Account/WebFeedMetadataFile.swift index be04530d7..6d6983286 100644 --- a/Account/Sources/Account/WebFeedMetadataFile.swift +++ b/Account/Sources/Account/WebFeedMetadataFile.swift @@ -7,12 +7,9 @@ // import Foundation -import os.log import RSCore -final class WebFeedMetadataFile { - - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "webFeedMetadataFile") +final class WebFeedMetadataFile: Logging { private let fileURL: URL private let account: Account @@ -52,8 +49,8 @@ final class WebFeedMetadataFile { do { let data = try encoder.encode(feedMetadata) try data.write(to: fileURL) - } catch let error as NSError { - os_log(.error, log: log, "Save to disk failed: %@.", error.localizedDescription) + } catch let error { + logger.error("Save to disk failed: \(error.localizedDescription)") } } diff --git a/Articles/Package.swift b/Articles/Package.swift index d5353dc79..d79b1815d 100644 --- a/Articles/Package.swift +++ b/Articles/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "Articles", - platforms: [.macOS(SupportedPlatform.MacOSVersion.v10_15), .iOS(SupportedPlatform.IOSVersion.v13)], + platforms: [.macOS(SupportedPlatform.MacOSVersion.v11), .iOS(SupportedPlatform.IOSVersion.v14)], products: [ .library( name: "Articles", diff --git a/ArticlesDatabase/Package.swift b/ArticlesDatabase/Package.swift index e47d036b5..cd61a3001 100644 --- a/ArticlesDatabase/Package.swift +++ b/ArticlesDatabase/Package.swift @@ -21,7 +21,7 @@ dependencies.append(contentsOf: [ let package = Package( name: "ArticlesDatabase", - platforms: [.macOS(SupportedPlatform.MacOSVersion.v10_15), .iOS(SupportedPlatform.IOSVersion.v13)], + platforms: [.macOS(SupportedPlatform.MacOSVersion.v11), .iOS(SupportedPlatform.IOSVersion.v14)], products: [ .library( name: "ArticlesDatabase", diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 44c877446..749bcf7b5 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -15,7 +15,6 @@ import Account import RSCore import RSCoreResources import Secrets -import OSLog import CrashReporter // If we're not going to import Sparkle, provide dummy protocols to make it easy @@ -30,15 +29,13 @@ import Sparkle var appDelegate: AppDelegate! @NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate +class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate, Logging { private struct WindowRestorationIdentifiers { static let mainWindow = "mainWindow" } - var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") - var userNotificationManager: UserNotificationManager! var faviconDownloader: FaviconDownloader! var imageDownloader: ImageDownloader! @@ -194,14 +191,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, try self.softwareUpdater.start() } catch { - NSLog("Failed to start software updater with error: \(error)") + logger.error("Failed to start software updater with error: \(error, privacy: .public)") } #endif AppDefaults.shared.registerDefaults() let isFirstRun = AppDefaults.shared.isFirstRun if isFirstRun { - os_log(.debug, log: log, "Is first run.") + logger.debug("Is first run") } let localAccount = AccountManager.shared.defaultAccount @@ -856,6 +853,7 @@ internal extension AppDelegate { confirmImportSuccess(themeName: theme.name) } catch { NSApplication.shared.presentError(error) + logger.error("Error importing theme: \(error.localizedDescription, privacy: .public)") } } @@ -884,6 +882,7 @@ internal extension AppDelegate { } } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error, "path": filename]) + logger.error("Error importing theme: \(error.localizedDescription, privacy: .public)") } } @@ -1012,12 +1011,12 @@ private extension AppDelegate { let account = AccountManager.shared.existingAccount(with: accountID) guard account != nil else { - os_log(.debug, log: log, "No account found from notification.") + logger.debug("No account found from notification.") return } let article = try? account!.fetchArticles(.articleIDs([articleID])) guard article != nil else { - os_log(.debug, log: log, "No article found from search using %@", articleID) + logger.debug("No article found from search using: \(articleID, privacy: .public)") return } account!.markArticles(article!, statusKey: .read, flag: true) { _ in } @@ -1031,12 +1030,12 @@ private extension AppDelegate { } let account = AccountManager.shared.existingAccount(with: accountID) guard account != nil else { - os_log(.debug, log: log, "No account found from notification.") + logger.debug("No account found from notification.") return } let article = try? account!.fetchArticles(.articleIDs([articleID])) guard article != nil else { - os_log(.debug, log: log, "No article found from search using %@", articleID) + logger.debug("No article found from search using: \(articleID, privacy: .public)") return } account!.markArticles(article!, statusKey: .starred, flag: true) { _ in } diff --git a/Mac/ErrorHandler.swift b/Mac/ErrorHandler.swift index 018da1a63..4ec6f59be 100644 --- a/Mac/ErrorHandler.swift +++ b/Mac/ErrorHandler.swift @@ -8,18 +8,18 @@ import AppKit import Account -import os.log +import RSCore -struct ErrorHandler { +struct ErrorHandler: Logging { - private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Account") + public static func present(_ error: Error) { NSApplication.shared.presentError(error) } public static func log(_ error: Error) { - os_log(.error, log: self.log, "%@", error.localizedDescription) + ErrorHandler.logger.error("\(error.localizedDescription, privacy: .public)") } } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 4d9f55b56..cb9c7e9d3 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -10,7 +10,6 @@ import Foundation import RSCore import Articles import Account -import os.log protocol TimelineDelegate: AnyObject { func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?) diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 3a4112ca4..6ac802851 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -8,10 +8,11 @@ import AppKit import Account +import RSCore import RSWeb import Secrets -class AccountsFeedbinWindowController: NSWindowController { +class AccountsFeedbinWindowController: NSWindowController, Logging { @IBOutlet weak var signInTextField: NSTextField! @IBOutlet weak var noAccountTextField: NSTextField! @@ -115,6 +116,7 @@ class AccountsFeedbinWindowController: NSWindowController { self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") } case .failure: diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index 9c70fceb5..0af42818e 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -9,9 +9,10 @@ import AppKit import Account import RSWeb +import RSCore import Secrets -class AccountsNewsBlurWindowController: NSWindowController { +class AccountsNewsBlurWindowController: NSWindowController, Logging { @IBOutlet weak var signInTextField: NSTextField! @IBOutlet weak var noAccountTextField: NSTextField! @@ -113,6 +114,7 @@ class AccountsNewsBlurWindowController: NSWindowController { self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") } case .failure: diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift index 0c4d3c569..b127003db 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift @@ -9,9 +9,10 @@ import AppKit import Account import RSWeb +import RSCore import Secrets -class AccountsReaderAPIWindowController: NSWindowController { +class AccountsReaderAPIWindowController: NSWindowController, Logging { @IBOutlet weak var titleImageView: NSImageView! @IBOutlet weak var titleLabel: NSTextField! @@ -170,6 +171,7 @@ class AccountsReaderAPIWindowController: NSWindowController { self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") } case .failure: diff --git a/Mac/Scriptability/AppDelegate+Scriptability.swift b/Mac/Scriptability/AppDelegate+Scriptability.swift index 132fe26be..cfd8310f5 100644 --- a/Mac/Scriptability/AppDelegate+Scriptability.swift +++ b/Mac/Scriptability/AppDelegate+Scriptability.swift @@ -55,7 +55,7 @@ extension AppDelegate : AppDelegateAppleEvents { if let themeURL = URL(string: themeURLString) { let request = URLRequest(url: themeURL) - let task = URLSession.shared.downloadTask(with: request) { location, response, error in + let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in guard let location = location else { return } @@ -64,6 +64,7 @@ extension AppDelegate : AppDelegateAppleEvents { try ArticleThemeDownloader.shared.handleFile(at: location) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error]) + self?.logger.error("Failed to import theme: \(error.localizedDescription, privacy: .public)") } } task.resume() diff --git a/Mac/ShareExtension/ShareViewController.swift b/Mac/ShareExtension/ShareViewController.swift index 2d7558063..bffe4d20d 100644 --- a/Mac/ShareExtension/ShareViewController.swift +++ b/Mac/ShareExtension/ShareViewController.swift @@ -7,7 +7,6 @@ // import Cocoa -import os.log class ShareViewController: NSViewController { @@ -16,7 +15,6 @@ class ShareViewController: NSViewController { private var url: URL? private var extensionContainers: ExtensionContainers? - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ShareViewController") override var nibName: NSNib.Name? { return NSNib.Name("ShareViewController") diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index d6abb9ffb..f0cd1d128 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -847,6 +847,8 @@ D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */; }; D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; }; + DF2A8F33289BFBD9002455AD /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = DF2A8F32289BFBD9002455AD /* RSCore */; }; + DF2A8F34289BFBDA002455AD /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DF2A8F32289BFBD9002455AD /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DFD6AACF27ADE86E00463FAD /* NewsFax.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = DFD6AACD27ADE86E00463FAD /* NewsFax.nnwtheme */; }; DFFB8FC2279B75E300AC21D7 /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4A24D343A500E90810 /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DFFC199827A0D0D7004B7AEF /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFFC199727A0D0D7004B7AEF /* NotificationsViewController.swift */; }; @@ -930,6 +932,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + DF2A8F34289BFBDA002455AD /* RSCore in Embed Frameworks */, 17EF6A2225C4E5B4002C9F81 /* RSWeb in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -1599,6 +1602,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DF2A8F33289BFBD9002455AD /* RSCore in Frameworks */, 17EF6A2125C4E5B4002C9F81 /* RSWeb in Frameworks */, 176813F72564BB2C00D98635 /* SwiftUI.framework in Frameworks */, 176813F52564BB2C00D98635 /* WidgetKit.framework in Frameworks */, @@ -2583,6 +2587,7 @@ 849A97861ED9ECEF007D329B /* Article Styles */, 84DAEE201F86CAE00058304B /* Importers */, 8444C9011FED81880051386C /* Exporters */, + DF2A8F28289A3EA8002455AD /* Logging */, 51FE0FF9234552490056195D /* UserNotifications */, 84F2D5341FC22FCB00998D64 /* SmartFeeds */, 51B5C85A23F22A7A00032075 /* ShareExtension */, @@ -2862,6 +2867,13 @@ path = Scriptability; sourceTree = ""; }; + DF2A8F28289A3EA8002455AD /* Logging */ = { + isa = PBXGroup; + children = ( + ); + path = Logging; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2882,6 +2894,7 @@ name = "NetNewsWire iOS Widget Extension"; packageProductDependencies = ( 17EF6A2025C4E5B4002C9F81 /* RSWeb */, + DF2A8F32289BFBD9002455AD /* RSCore */, ); productName = "NetNewsWire WidgetExtension"; productReference = 176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */; @@ -5181,6 +5194,11 @@ package = 653813412680E2DA007A082C /* XCRemoteSwiftPackageReference "RSCore" */; productName = RSCore; }; + DF2A8F32289BFBD9002455AD /* RSCore */ = { + isa = XCSwiftPackageProductDependency; + package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */; + productName = RSCore; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 849C64581ED37A5D003D8FC0 /* Project object */; diff --git a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 71fbf0ee7..0f71953cd 100644 --- a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/microsoft/plcrashreporter.git", "state": { "branch": null, - "revision": "6b27393cad517c067dceea85fadf050e70c4ceaa", - "version": "1.10.1" + "revision": "81cdec2b3827feb03286cb297f4c501a8eb98df1", + "version": "1.10.2" } }, { @@ -60,8 +60,8 @@ "repositoryURL": "https://github.com/Ranchero-Software/RSCore.git", "state": { "branch": null, - "revision": "4cd6b67ed213c0db9c02c9d8ff968b45fd694970", - "version": "1.0.8" + "revision": "814adfd956e5bd099b3b81b6680d0a4f6cd56130", + "version": "1.0.9" } }, { diff --git a/Secrets/Package.swift b/Secrets/Package.swift index fdd6bc08a..f3286fe67 100644 --- a/Secrets/Package.swift +++ b/Secrets/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "Secrets", - platforms: [.macOS(SupportedPlatform.MacOSVersion.v10_15), .iOS(SupportedPlatform.IOSVersion.v13)], + platforms: [.macOS(SupportedPlatform.MacOSVersion.v11), .iOS(SupportedPlatform.IOSVersion.v14)], products: [ .library( name: "Secrets", diff --git a/Shared/Article Extractor/ArticleExtractor.swift b/Shared/Article Extractor/ArticleExtractor.swift index 6b26f8f8d..1fd4d151b 100644 --- a/Shared/Article Extractor/ArticleExtractor.swift +++ b/Shared/Article Extractor/ArticleExtractor.swift @@ -9,6 +9,7 @@ import Foundation import Account import Secrets +import RSCore public enum ArticleExtractorState { case ready @@ -23,7 +24,7 @@ protocol ArticleExtractorDelegate { func articleExtractionDidComplete(extractedArticle: ExtractedArticle) } -class ArticleExtractor { +class ArticleExtractor: Logging { private var dataTask: URLSessionDataTask? = nil @@ -91,6 +92,7 @@ class ArticleExtractor { } } } catch { + self.logger.error("Failed to extract article: \(error.localizedDescription, privacy: .public)") self.state = .failedToParse DispatchQueue.main.async { self.delegate?.articleExtractionDidFail(with: error) diff --git a/Shared/ArticleStyles/ArticleThemeDownloader.swift b/Shared/ArticleStyles/ArticleThemeDownloader.swift index d6714e169..703db2ef5 100644 --- a/Shared/ArticleStyles/ArticleThemeDownloader.swift +++ b/Shared/ArticleStyles/ArticleThemeDownloader.swift @@ -8,8 +8,9 @@ import Foundation import Zip +import RSCore -public class ArticleThemeDownloader { +public class ArticleThemeDownloader: Logging { public enum ArticleThemeDownloaderError: LocalizedError { case noThemeFile @@ -63,6 +64,7 @@ public class ArticleThemeDownloader { } return URL(fileURLWithPath: unzipDirectory.appendingPathComponent(themeFilePath!).path) } catch { + logger.error("Failed to unzip theme: \(error.localizedDescription, privacy: .public)") try? FileManager.default.removeItem(at: location) throw error } @@ -101,7 +103,7 @@ public class ArticleThemeDownloader { try FileManager.default.removeItem(atPath: downloadDirectory().appendingPathComponent(path).path) } } catch { - print(error) + logger.error("Failed to clean up theme download: \(error.localizedDescription, privacy: .public)") } } } diff --git a/Shared/ArticleStyles/ArticleThemesManager.swift b/Shared/ArticleStyles/ArticleThemesManager.swift index ff0365e9f..ab66608a9 100644 --- a/Shared/ArticleStyles/ArticleThemesManager.swift +++ b/Shared/ArticleStyles/ArticleThemesManager.swift @@ -14,7 +14,7 @@ public extension Notification.Name { static let CurrentArticleThemeDidChangeNotification = Notification.Name("CurrentArticleThemeDidChangeNotification") } -final class ArticleThemesManager: NSObject, NSFilePresenter { +final class ArticleThemesManager: NSObject, NSFilePresenter, Logging { static var shared: ArticleThemesManager! public let folderPath: String @@ -58,6 +58,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter { do { try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil) } catch { + logger.error("Could not create folder for themes: \(error.localizedDescription, privacy: .public)") assertionFailure("Could not create folder for Themes.") abort() } @@ -113,6 +114,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter { return try ArticleTheme(path: path, isAppTheme: isAppTheme) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error]) + logger.error("Failed to import theme: \(error.localizedDescription, privacy: .public)") return nil } diff --git a/Shared/Extensions/CacheCleaner.swift b/Shared/Extensions/CacheCleaner.swift index fa00603e7..2364a59fb 100644 --- a/Shared/Extensions/CacheCleaner.swift +++ b/Shared/Extensions/CacheCleaner.swift @@ -7,12 +7,10 @@ // import Foundation -import os.log import RSWeb +import RSCore -struct CacheCleaner { - - static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CacheCleaner") +struct CacheCleaner: Logging { static func purgeIfNecessary() { @@ -35,10 +33,10 @@ struct CacheCleaner { for tempItem in [faviconsFolderURL, imagesFolderURL, feedURLToIconURL, homePageToIconURL, homePagesWithNoIconURL] { do { - os_log(.info, log: self.log, "Removing cache file: %@", tempItem.absoluteString) + CacheCleaner.logger.info("Removing cache file: \(tempItem.absoluteString, privacy: .public)") try FileManager.default.removeItem(at: tempItem) } catch { - os_log(.error, log: self.log, "Could not delete cache file: %@", error.localizedDescription) + CacheCleaner.logger.error("Could not delete cache file: \(error.localizedDescription, privacy: .public)") } } diff --git a/Shared/Favicons/FaviconDownloader.swift b/Shared/Favicons/FaviconDownloader.swift index 02c48f6f0..da9822650 100644 --- a/Shared/Favicons/FaviconDownloader.swift +++ b/Shared/Favicons/FaviconDownloader.swift @@ -18,7 +18,7 @@ extension Notification.Name { static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL } -final class FaviconDownloader { +final class FaviconDownloader: Logging { private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0) @@ -297,6 +297,7 @@ private extension FaviconDownloader { let data = try encoder.encode(homePageToFaviconURLCache) try data.write(to: url) } catch { + logger.error("Failed to Save Home Page To Favicon URL Cache: \(error.localizedDescription, privacy: .public)") assertionFailure(error.localizedDescription) } } @@ -311,6 +312,7 @@ private extension FaviconDownloader { let data = try encoder.encode(Array(homePageURLsWithNoFaviconURLCache)) try data.write(to: url) } catch { + logger.error("Failed to Save URLs With No Favicon URL Cache: \(error.localizedDescription, privacy: .public)") assertionFailure(error.localizedDescription) } } diff --git a/Shared/Favicons/SingleFaviconDownloader.swift b/Shared/Favicons/SingleFaviconDownloader.swift index 1b82802f6..d65806e13 100644 --- a/Shared/Favicons/SingleFaviconDownloader.swift +++ b/Shared/Favicons/SingleFaviconDownloader.swift @@ -7,7 +7,6 @@ // import Foundation -import os.log import RSCore import RSWeb @@ -19,7 +18,7 @@ extension Notification.Name { static let DidLoadFavicon = Notification.Name("DidLoadFaviconNotification") } -final class SingleFaviconDownloader { +final class SingleFaviconDownloader: Logging { enum DiskStatus { case unknown, notOnDisk, onDisk @@ -29,8 +28,6 @@ final class SingleFaviconDownloader { var iconImage: IconImage? let homePageURL: String? - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SingleFaviconDownloader") - private var lastDownloadAttemptDate: Date private var diskStatus = DiskStatus.unknown private var diskCache: BinaryDiskCache @@ -128,7 +125,9 @@ private extension SingleFaviconDownloader { self.diskStatus = .onDisk } } - catch {} + catch { + self.logger.error("Unable to save to disk: \(error.localizedDescription, privacy: .public)") + } } } @@ -139,16 +138,16 @@ private extension SingleFaviconDownloader { return } - downloadUsingCache(url) { (data, response, error) in + downloadUsingCache(url) { [weak self] (data, response, error) in if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil { - self.saveToDisk(data) + self?.saveToDisk(data) RSImage.image(with: data, imageResultBlock: completion) return } if let error = error { - os_log(.info, log: self.log, "Error downloading image at %@: %@.", url.absoluteString, error.localizedDescription) + self?.logger.error("Error downloading image at: \(url.absoluteString, privacy: .sensitive): \(error.localizedDescription, privacy: .public)") } completion(nil) diff --git a/Shared/Images/ImageDownloader.swift b/Shared/Images/ImageDownloader.swift index 9a549d3ef..242951eba 100644 --- a/Shared/Images/ImageDownloader.swift +++ b/Shared/Images/ImageDownloader.swift @@ -7,7 +7,6 @@ // import Foundation -import os.log import RSCore import RSWeb @@ -16,9 +15,7 @@ extension Notification.Name { static let ImageDidBecomeAvailable = Notification.Name("ImageDidBecomeAvailableNotification") // UserInfoKey.url } -final class ImageDownloader { - - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ImageDownloader") +final class ImageDownloader: Logging { private let folder: String private var diskCache: BinaryDiskCache @@ -103,19 +100,19 @@ private extension ImageDownloader { return } - downloadUsingCache(imageURL) { (data, response, error) in + downloadUsingCache(imageURL) { [weak self] (data, response, error) in if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil { - self.saveToDisk(url, data) + self?.saveToDisk(url, data) completion(data) return } if let response = response as? HTTPURLResponse, response.statusCode >= HTTPResponseCode.badRequest && response.statusCode <= HTTPResponseCode.notAcceptable { - self.badURLs.insert(url) + self?.badURLs.insert(url) } if let error = error { - os_log(.info, log: self.log, "Error downloading image at %@: %@.", url, error.localizedDescription) + self?.logger.error("Error downloading image at: \(url, privacy: .sensitive): \(error.localizedDescription, privacy: .public)") } completion(nil) diff --git a/Shared/ShareExtension/ExtensionContainersFile.swift b/Shared/ShareExtension/ExtensionContainersFile.swift index 094a5360d..6e85f989b 100644 --- a/Shared/ShareExtension/ExtensionContainersFile.swift +++ b/Shared/ShareExtension/ExtensionContainersFile.swift @@ -7,15 +7,12 @@ // import Foundation -import os.log import RSCore import RSParser import Account -final class ExtensionContainersFile { +final class ExtensionContainersFile: Logging { - private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionContainersFile") - private static var filePath: String = { let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) @@ -55,7 +52,7 @@ final class ExtensionContainersFile { }) if let error = errorPointer?.pointee { - os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription) + logger.error("Read from coordination failed: \(error.localizedDescription, privacy: .public)") } return extensionContainers @@ -88,19 +85,19 @@ private extension ExtensionContainersFile { let fileCoordinator = NSFileCoordinator() let fileURL = URL(fileURLWithPath: ExtensionContainersFile.filePath) - fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { writeURL in + fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { [weak self] writeURL in do { let extensionAccounts = AccountManager.shared.sortedActiveAccounts.map { ExtensionAccount(account: $0) } let extensionContainers = ExtensionContainers(accounts: extensionAccounts) let data = try encoder.encode(extensionContainers) try data.write(to: writeURL) } catch let error as NSError { - os_log(.error, log: Self.log, "Save to disk failed: %@.", error.localizedDescription) + self?.logger.error("Save to disk failed: \(error.localizedDescription, privacy: .public)") } }) if let error = errorPointer?.pointee { - os_log(.error, log: Self.log, "Save to disk coordination failed: %@.", error.localizedDescription) + logger.error("Save to disk coordination failed: \(error.localizedDescription, privacy: .public)") } } diff --git a/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift b/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift index 6ac0dd2e4..737d756c6 100644 --- a/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift +++ b/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift @@ -7,13 +7,11 @@ // import Foundation -import os.log import Account +import RSCore -final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter { +final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging { - private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionFeedAddRequestFile") - private static var filePath: String = { let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) @@ -82,12 +80,12 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter { try data.write(to: url) } catch let error as NSError { - os_log(.error, log: Self.log, "Save to disk failed: %@.", error.localizedDescription) + logger.error("Save to disk failed: \(error.localizedDescription, privacy: .public)") } }) if let error = errorPointer?.pointee { - os_log(.error, log: Self.log, "Save to disk coordination failed: %@.", error.localizedDescription) + logger.error("Save to disk coordination failed: \(error.localizedDescription, privacy: .public)") } } @@ -107,7 +105,7 @@ private extension ExtensionFeedAddRequestFile { var requests: [ExtensionFeedAddRequest]? = nil - fileCoordinator.coordinate(writingItemAt: fileURL, options: [.forMerging], error: errorPointer, byAccessor: { url in + fileCoordinator.coordinate(writingItemAt: fileURL, options: [.forMerging], error: errorPointer, byAccessor: { [weak self] url in do { if let fileData = try? Data(contentsOf: url), @@ -119,12 +117,12 @@ private extension ExtensionFeedAddRequestFile { try data.write(to: url) } catch let error as NSError { - os_log(.error, log: Self.log, "Save to disk failed: %@.", error.localizedDescription) + self?.logger.error("Save to disk failed: \(error.localizedDescription, privacy: .public)") } }) if let error = errorPointer?.pointee { - os_log(.error, log: Self.log, "Save to disk coordination failed: %@.", error.localizedDescription) + logger.error("Save to disk coordination failed: \(error.localizedDescription, privacy: .public)") } requests?.forEach { processRequest($0) } diff --git a/Shared/Widget/WidgetDataDecoder.swift b/Shared/Widget/WidgetDataDecoder.swift index e2d03f53b..de26dc370 100644 --- a/Shared/Widget/WidgetDataDecoder.swift +++ b/Shared/Widget/WidgetDataDecoder.swift @@ -7,10 +7,11 @@ // import Foundation +import RSCore -struct WidgetDataDecoder { +struct WidgetDataDecoder: Logging { - static func decodeWidgetData() throws -> WidgetData { + func decodeWidgetData() throws -> WidgetData { let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) let dataURL = containerURL?.appendingPathComponent("widget-data.json") @@ -22,13 +23,14 @@ struct WidgetDataDecoder { } } - static func sampleData() -> WidgetData { + func sampleData() -> WidgetData { let pathToSample = Bundle.main.url(forResource: "widget-sample", withExtension: "json") do { let data = try Data(contentsOf: pathToSample!) let decoded = try JSONDecoder().decode(WidgetData.self, from: data) return decoded } catch { + logger.error("Error accessing sample widget data: \(error.localizedDescription, privacy: .public)") return WidgetData(currentUnreadCount: 0, currentTodayCount: 0, currentStarredCount: 0, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date()) } } diff --git a/Shared/Widget/WidgetDataEncoder.swift b/Shared/Widget/WidgetDataEncoder.swift index f0a5b976f..b23525f90 100644 --- a/Shared/Widget/WidgetDataEncoder.swift +++ b/Shared/Widget/WidgetDataEncoder.swift @@ -8,16 +8,15 @@ import Foundation import WidgetKit -import os.log import UIKit import RSCore import Articles import Account -public final class WidgetDataEncoder { +public final class WidgetDataEncoder: Logging { + - private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") private let fetchLimit = 7 private var backgroundTaskID: UIBackgroundTaskIdentifier! @@ -36,7 +35,7 @@ public final class WidgetDataEncoder { @available(iOS 14, *) func encodeWidgetData() throws { flushSharedContainer() - os_log(.debug, log: log, "Starting encoding widget data.") + logger.debug("Started encoding widget data.") do { let unreadArticles = Array(try AccountManager.shared.fetchArticles(.unread(fetchLimit))).sortedByDate(.orderedDescending) @@ -95,14 +94,14 @@ public final class WidgetDataEncoder { } let encodedData = try? JSONEncoder().encode(latestData) - os_log(.debug, log: self.log, "Finished encoding widget data.") + self.logger.debug("Finished encoding widget data.") if self.fileExists() { try? FileManager.default.removeItem(at: self.dataURL!) - os_log(.debug, log: self.log, "Removed widget data from container.") + self.logger.debug("Removed widget data from container.") } if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) { - os_log(.debug, log: self.log, "Wrote widget data to container.") + self.logger.debug("Wrote widget data to container.") WidgetCenter.shared.reloadAllTimelines() UIApplication.shared.endBackgroundTask(self.backgroundTaskID!) self.backgroundTaskID = .invalid diff --git a/SyncDatabase/Package.swift b/SyncDatabase/Package.swift index 19f86b7ce..02fea65c1 100644 --- a/SyncDatabase/Package.swift +++ b/SyncDatabase/Package.swift @@ -18,7 +18,7 @@ dependencies.append(contentsOf: [ let package = Package( name: "SyncDatabase", - platforms: [.macOS(SupportedPlatform.MacOSVersion.v10_15), .iOS(SupportedPlatform.IOSVersion.v13)], + platforms: [.macOS(.v11), .iOS(.v14)], products: [ .library( name: "SyncDatabase", diff --git a/Widget/TimelineProvider.swift b/Widget/TimelineProvider.swift index 8ca18dc5d..87ff6b71b 100644 --- a/Widget/TimelineProvider.swift +++ b/Widget/TimelineProvider.swift @@ -8,35 +8,41 @@ import WidgetKit import SwiftUI +import RSCore -struct Provider: TimelineProvider { +struct Provider: TimelineProvider, Logging { + + let decoder = WidgetDataDecoder() func placeholder(in context: Context) -> WidgetTimelineEntry { do { - let data = try WidgetDataDecoder.decodeWidgetData() + let data = try decoder.decodeWidgetData() return WidgetTimelineEntry(date: Date(), widgetData: data) } catch { - return WidgetTimelineEntry(date: Date(), widgetData: WidgetDataDecoder.sampleData()) + logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)") + return WidgetTimelineEntry(date: Date(), widgetData: decoder.sampleData()) } } func getSnapshot(in context: Context, completion: @escaping (WidgetTimelineEntry) -> Void) { if context.isPreview { do { - let data = try WidgetDataDecoder.decodeWidgetData() + let data = try decoder.decodeWidgetData() completion(WidgetTimelineEntry(date: Date(), widgetData: data)) } catch { + logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)") completion(WidgetTimelineEntry(date: Date(), - widgetData: WidgetDataDecoder.sampleData())) + widgetData: decoder.sampleData())) } } else { do { - let widgetData = try WidgetDataDecoder.decodeWidgetData() + let widgetData = try decoder.decodeWidgetData() let entry = WidgetTimelineEntry(date: Date(), widgetData: widgetData) completion(entry) } catch { + logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)") let entry = WidgetTimelineEntry(date: Date(), - widgetData: WidgetDataDecoder.sampleData()) + widgetData: decoder.sampleData()) completion(entry) } } @@ -48,9 +54,10 @@ struct Provider: TimelineProvider { var entry: WidgetTimelineEntry do { - let widgetData = try WidgetDataDecoder.decodeWidgetData() + let widgetData = try decoder.decodeWidgetData() entry = WidgetTimelineEntry(date: date, widgetData: widgetData) } catch { + logger.error("Failed to decode widget data: \(error.localizedDescription, privacy: .public)") entry = WidgetTimelineEntry(date: date, widgetData: WidgetData(currentUnreadCount: 0, currentTodayCount: 0, currentStarredCount: 0, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date())) } diff --git a/Widget/Widget Views/SmartFeedSummaryWidget.swift b/Widget/Widget Views/SmartFeedSummaryWidget.swift index efa4fb756..b1baf3611 100644 --- a/Widget/Widget Views/SmartFeedSummaryWidget.swift +++ b/Widget/Widget Views/SmartFeedSummaryWidget.swift @@ -102,6 +102,6 @@ struct SmartFeedSummaryWidgetView: View { struct SmartFeedSummaryWidgetView_Previews: PreviewProvider { static var previews: some View { - SmartFeedSummaryWidgetView(entry: Provider.Entry.init(date: Date(), widgetData: WidgetDataDecoder.sampleData())) + SmartFeedSummaryWidgetView(entry: Provider.Entry.init(date: Date(), widgetData: WidgetDataDecoder().sampleData())) } } diff --git a/iOS/Account/FeedbinAccountViewController.swift b/iOS/Account/FeedbinAccountViewController.swift index c6e66844b..964ff7e93 100644 --- a/iOS/Account/FeedbinAccountViewController.swift +++ b/iOS/Account/FeedbinAccountViewController.swift @@ -11,8 +11,9 @@ import Account import Secrets import RSWeb import SafariServices +import RSCore -class FeedbinAccountViewController: UITableViewController { +class FeedbinAccountViewController: UITableViewController, Logging { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var cancelBarButtonItem: UIBarButtonItem! @@ -116,7 +117,9 @@ class FeedbinAccountViewController: UITableViewController { do { try self.account?.removeCredentials(type: .basic) - } catch {} + } catch { + self.logger.error("Error removing credentials: \(error.localizedDescription, privacy: .public).") + } try self.account?.storeCredentials(credentials) self.account?.refreshAll() { result in @@ -132,6 +135,7 @@ class FeedbinAccountViewController: UITableViewController { self.delegate?.dismiss() } catch { self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")) + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public).") } } else { self.showError(NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error")) diff --git a/iOS/Account/NewsBlurAccountViewController.swift b/iOS/Account/NewsBlurAccountViewController.swift index e7ebf6e72..9f46361d0 100644 --- a/iOS/Account/NewsBlurAccountViewController.swift +++ b/iOS/Account/NewsBlurAccountViewController.swift @@ -10,9 +10,10 @@ import UIKit import Account import Secrets import RSWeb +import RSCore import SafariServices -class NewsBlurAccountViewController: UITableViewController { +class NewsBlurAccountViewController: UITableViewController, Logging { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var cancelBarButtonItem: UIBarButtonItem! @@ -118,7 +119,9 @@ class NewsBlurAccountViewController: UITableViewController { do { try self.account?.removeCredentials(type: .newsBlurBasic) try self.account?.removeCredentials(type: .newsBlurSessionId) - } catch {} + } catch { + self.logger.error("Error removing credentials: \(error.localizedDescription, privacy: .public).") + } try self.account?.storeCredentials(basicCredentials) try self.account?.storeCredentials(sessionCredentials) @@ -135,6 +138,7 @@ class NewsBlurAccountViewController: UITableViewController { self.delegate?.dismiss() } catch { self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")) + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public).") } } else { self.showError(NSLocalizedString("Invalid username/password combination.", comment: "Credentials Error")) diff --git a/iOS/Account/ReaderAPIAccountViewController.swift b/iOS/Account/ReaderAPIAccountViewController.swift index 79d603b7e..1f93ad95a 100644 --- a/iOS/Account/ReaderAPIAccountViewController.swift +++ b/iOS/Account/ReaderAPIAccountViewController.swift @@ -11,8 +11,9 @@ import Account import Secrets import RSWeb import SafariServices +import RSCore -class ReaderAPIAccountViewController: UITableViewController { +class ReaderAPIAccountViewController: UITableViewController, Logging { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var cancelBarButtonItem: UIBarButtonItem! @@ -187,6 +188,7 @@ class ReaderAPIAccountViewController: UITableViewController { self.delegate?.dismiss() } catch { self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")) + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public).") } } else { self.showError(NSLocalizedString("Invalid username/password combination.", comment: "Credentials Error")) diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 298faba77..20e89fe8c 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -11,14 +11,13 @@ import RSCore import RSWeb import Account import BackgroundTasks -import os.log import Secrets import WidgetKit var appDelegate: AppDelegate! @UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider { +class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider, Logging { private var bgTaskDispatchQueue = DispatchQueue.init(label: "BGTaskScheduler") @@ -36,8 +35,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } } - var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") - var userNotificationManager: UserNotificationManager! var faviconDownloader: FaviconDownloader! var imageDownloader: ImageDownloader! @@ -83,7 +80,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let isFirstRun = AppDefaults.shared.isFirstRun if isFirstRun { - os_log("Is first run.", log: log, type: .info) + logger.info("Is first run.") } if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() { @@ -166,7 +163,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func resumeDatabaseProcessingIfNecessary() { if AccountManager.shared.isSuspended { AccountManager.shared.resumeAll() - os_log("Application processing resumed.", log: self.log, type: .info) + logger.info("Application processing resumed.") } } @@ -276,7 +273,7 @@ private extension AppDelegate { self.waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in guard let self = self else { return } self.completeProcessing(true) - os_log("Accounts wait for progress terminated for running too long.", log: self.log, type: .info) + self.logger.info("Accounts wait for progress terminated for running too long.") } DispatchQueue.main.async { [weak self] in @@ -288,18 +285,18 @@ private extension AppDelegate { func waitToComplete(completion: @escaping (Bool) -> Void) { guard UIApplication.shared.applicationState == .background else { - os_log("App came back to foreground, no longer waiting.", log: self.log, type: .info) + logger.info("App came back to foreground, no longer waiting.") completion(false) return } if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning { - os_log("Waiting for sync to finish...", log: self.log, type: .info) + logger.info("Waiting for sync to finish...") DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in self?.waitToComplete(completion: completion) } } else { - os_log("Refresh progress complete.", log: self.log, type: .info) + logger.info("Refresh progress complete.") completion(true) } } @@ -324,9 +321,9 @@ private extension AppDelegate { self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid } - self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { + self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in completeProcessing() - os_log("Accounts sync processing terminated for running too long.", log: self.log, type: .info) + self?.logger.info("Accounts sync processing terminated for running too long.") } DispatchQueue.main.async { @@ -350,7 +347,7 @@ private extension AppDelegate { } } - os_log("Application processing suspended.", log: self.log, type: .info) + logger.info("Application processing suspended.") } } @@ -374,11 +371,11 @@ private extension AppDelegate { // We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the // task scheduler can hang indefinitely. - bgTaskDispatchQueue.async { + bgTaskDispatchQueue.async { [weak self] in do { try BGTaskScheduler.shared.submit(request) } catch { - os_log(.error, log: self.log, "Could not schedule app refresh: %@", error.localizedDescription) + self?.logger.error("Could not schedule app refresh: \(error.localizedDescription, privacy: .public)") } } } @@ -390,7 +387,7 @@ private extension AppDelegate { scheduleBackgroundFeedRefresh() // schedule next refresh - os_log("Woken to perform account refresh.", log: self.log, type: .info) + logger.info("Woken to perform account refresh.") DispatchQueue.main.async { if AccountManager.shared.isSuspended { @@ -400,7 +397,7 @@ private extension AppDelegate { if !AccountManager.shared.isSuspended { try? WidgetDataEncoder.shared.encodeWidgetData() self.suspendApplication() - os_log("Account refresh operation completed.", log: self.log, type: .info) + self.logger.info("Account refresh operation completed.") task.setTaskCompleted(success: true) } } @@ -408,7 +405,7 @@ private extension AppDelegate { // set expiration handler task.expirationHandler = { [weak task] in - os_log("Accounts refresh processing terminated for running too long.", log: self.log, type: .info) + self.logger.info("Accounts refresh processing terminated for running too long.") DispatchQueue.main.async { self.suspendApplication() task?.setTaskCompleted(success: false) @@ -431,12 +428,12 @@ private extension AppDelegate { resumeDatabaseProcessingIfNecessary() let account = AccountManager.shared.existingAccount(with: accountID) guard account != nil else { - os_log(.debug, log: self.log, "No account found from notification.") + logger.debug("No account found from notification.") return } let article = try? account!.fetchArticles(.articleIDs([articleID])) guard article != nil else { - os_log(.debug, log: self.log, "No article found from search using %@", articleID) + logger.debug("No account found from search using \(articleID, privacy: .public)") return } account!.markArticles(article!, statusKey: .read, flag: true) { _ in } @@ -459,12 +456,12 @@ private extension AppDelegate { resumeDatabaseProcessingIfNecessary() let account = AccountManager.shared.existingAccount(with: accountID) guard account != nil else { - os_log(.debug, log: self.log, "No account found from notification.") + logger.debug("No account found from notification.") return } let article = try? account!.fetchArticles(.articleIDs([articleID])) guard article != nil else { - os_log(.debug, log: self.log, "No article found from search using %@", articleID) + logger.debug("No article found from search using \(articleID, privacy: .public)") return } account!.markArticles(article!, statusKey: .starred, flag: true) { _ in } diff --git a/iOS/ErrorHandler.swift b/iOS/ErrorHandler.swift index 64957ad3b..cb5dd9fe3 100644 --- a/iOS/ErrorHandler.swift +++ b/iOS/ErrorHandler.swift @@ -8,24 +8,21 @@ import UIKit import RSCore -import os.log -struct ErrorHandler { - - private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") +struct ErrorHandler: Logging { public static func present(_ viewController: UIViewController) -> (Error) -> () { return { [weak viewController] error in if UIApplication.shared.applicationState == .active { viewController?.presentError(error) } else { - ErrorHandler.log(error) + log(error) } } } public static func log(_ error: Error) { - os_log(.error, log: self.log, "%@", error.localizedDescription) + ErrorHandler.logger.error("\(error.localizedDescription, privacy: .public)") } } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index da737f550..8eca04869 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -50,7 +50,7 @@ struct FeedNode: Hashable { } } -class SceneCoordinator: NSObject, UndoableCommandRunner { +class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { var undoableCommands = [UndoableCommand]() var undoManager: UndoManager? { @@ -1272,6 +1272,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { try ArticleThemeImporter.importTheme(controller: rootSplitViewController, filename: filename) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error]) + logger.error("Failed to import theme with error: \(error.localizedDescription, privacy: .public)") } } diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index 9d086813c..456b34577 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -10,8 +10,9 @@ import UIKit import UserNotifications import Account import Zip +import RSCore -class SceneDelegate: UIResponder, UIWindowSceneDelegate { +class SceneDelegate: UIResponder, UIWindowSceneDelegate, Logging { var window: UIWindow? var coordinator: SceneCoordinator! @@ -184,7 +185,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { DispatchQueue.main.async { NotificationCenter.default.post(name: .didBeginDownloadingTheme, object: nil) } - let task = URLSession.shared.downloadTask(with: request) { location, response, error in + let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in guard let location = location else { return } @@ -192,6 +193,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { try ArticleThemeDownloader.shared.handleFile(at: location) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error]) + self?.logger.error("Failed to import theme with error: \(error.localizedDescription, privacy: .public)") } } task.resume() diff --git a/iOS/Settings/ArticleThemeImporter.swift b/iOS/Settings/ArticleThemeImporter.swift index bfc8ff23a..1f71e6033 100644 --- a/iOS/Settings/ArticleThemeImporter.swift +++ b/iOS/Settings/ArticleThemeImporter.swift @@ -7,8 +7,9 @@ // import UIKit +import RSCore -struct ArticleThemeImporter { +struct ArticleThemeImporter: Logging { static func importTheme(controller: UIViewController, filename: String) throws { let theme = try ArticleTheme(path: filename, isAppTheme: false) @@ -39,6 +40,7 @@ struct ArticleThemeImporter { confirmImportSuccess(controller: controller, themeName: theme.name) } catch { controller.presentError(error) + ArticleThemeImporter.logger.error("Error importing theme: \(error.localizedDescription, privacy: .public)") } } diff --git a/iOS/Settings/ArticleThemesTableViewController.swift b/iOS/Settings/ArticleThemesTableViewController.swift index d9a3d7f99..66d118caf 100644 --- a/iOS/Settings/ArticleThemesTableViewController.swift +++ b/iOS/Settings/ArticleThemesTableViewController.swift @@ -8,10 +8,10 @@ import Foundation import UniformTypeIdentifiers - +import RSCore import UIKit -class ArticleThemesTableViewController: UITableViewController { +class ArticleThemesTableViewController: UITableViewController, Logging { override func viewDidLoad() { let importBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(importTheme(_:))); @@ -118,6 +118,7 @@ extension ArticleThemesTableViewController: UIDocumentPickerDelegate { try ArticleThemeImporter.importTheme(controller: self, filename: url.standardizedFileURL.path) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error]) + logger.error("Did fail to import theme: \(error.localizedDescription, privacy: .public)") } } diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 6bd34618e..a1ab1f4af 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -13,8 +13,9 @@ import SafariServices import SwiftUI import UniformTypeIdentifiers import UserNotifications +import RSCore -class SettingsViewController: UITableViewController { +class SettingsViewController: UITableViewController, Logging { private weak var opmlAccount: Account? @@ -509,6 +510,7 @@ private extension SettingsViewController { try opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8) } catch { self.presentError(title: "OPML Export Error", message: error.localizedDescription) + logger.error("OPML Export Error: \(error.localizedDescription, privacy: .public)") } let docPicker = UIDocumentPickerViewController(forExporting: [tempFile], asCopy: true)