From c3c64c13547de9170927b7ef3d96f4540aa34210 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 12 Aug 2021 11:30:50 -0600 Subject: [PATCH] Update logging --- JellyfinPlayer.xcodeproj/project.pbxproj | 8 +++ Shared/Errors/ErrorMessage.swift | 16 ++---- Shared/Errors/LogConstructor.swift | 20 +++++++ Shared/Errors/NetworkError.swift | 70 +++++++++++++----------- Shared/ViewModels/ViewModel.swift | 11 ++-- 5 files changed, 77 insertions(+), 48 deletions(-) create mode 100644 Shared/Errors/LogConstructor.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 814dab25..62c35ceb 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -197,6 +197,9 @@ 62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; }; AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; }; E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; }; + E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; + E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; + E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD09526C47118007C8DCF /* ErrorMessage.swift */; }; @@ -382,6 +385,7 @@ D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = ""; }; DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.debug.xcconfig"; sourceTree = ""; }; E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = ""; }; + E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = ""; }; E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -773,6 +777,7 @@ isa = PBXGroup; children = ( E1FCD08726C35A0D007C8DCF /* NetworkError.swift */, + E131691626C583BC0074BFEE /* LogConstructor.swift */, E1FCD09526C47118007C8DCF /* ErrorMessage.swift */, ); path = Errors; @@ -1140,6 +1145,7 @@ 53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */, 5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */, 531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */, + E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */, 535870A32669D89F00D05A09 /* Model.xcdatamodeld in Sources */, 09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */, ); @@ -1176,6 +1182,7 @@ 625CB5682678B6FB00530A6E /* SplashView.swift in Sources */, 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */, 62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, + E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */, 5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */, 532E68CF267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift in Sources */, 532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */, @@ -1232,6 +1239,7 @@ 628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */, 628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */, E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */, + E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */, 62EC353226766849000E9F2D /* SessionManager.swift in Sources */, 536D3D79267BD5D00004248C /* ViewModel.swift in Sources */, ); diff --git a/Shared/Errors/ErrorMessage.swift b/Shared/Errors/ErrorMessage.swift index b61e534b..a1d74ff6 100644 --- a/Shared/Errors/ErrorMessage.swift +++ b/Shared/Errors/ErrorMessage.swift @@ -15,21 +15,17 @@ struct ErrorMessage: Identifiable { let code: Int let title: String let displayMessage: String - let logMessage: String - let logLevel: LogLevel - let logTag: String + let logConstructor: LogConstructor var id: String { - return "\(code)\(title)\(logMessage)" + return "\(code)\(title)\(logConstructor.message)" } - /// If the given displayMessage is `nil`, it will be set to the given logMessage - init(code: Int, title: String, displayMessage: String?, logMessage: String, logLevel: LogLevel, logTag: String?) { + /// If the custom displayMessage is `nil`, it will be set to the given logConstructor's message + init(code: Int, title: String, displayMessage: String?, logConstructor: LogConstructor) { self.code = code self.title = title - self.displayMessage = displayMessage ?? logMessage - self.logMessage = logMessage - self.logLevel = logLevel - self.logTag = logTag ?? "" + self.displayMessage = displayMessage ?? logConstructor.message + self.logConstructor = logConstructor } } diff --git a/Shared/Errors/LogConstructor.swift b/Shared/Errors/LogConstructor.swift new file mode 100644 index 00000000..df603a9d --- /dev/null +++ b/Shared/Errors/LogConstructor.swift @@ -0,0 +1,20 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Foundation +import JellyfinAPI + +struct LogConstructor { + var message: String + let tag: String + let level: LogLevel + let function: String + let file: String + let line: UInt +} diff --git a/Shared/Errors/NetworkError.swift b/Shared/Errors/NetworkError.swift index d05148f2..e4badaa6 100644 --- a/Shared/Errors/NetworkError.swift +++ b/Shared/Errors/NetworkError.swift @@ -10,33 +10,40 @@ import Foundation import JellyfinAPI + +/** + The implementation of the network errors here are a temporary measure. + It is very repetitive, messy, and doesn't fulfill the entire specification of "error reporting". + The specific kind of errors here should be created and surfaced from within JellyfinAPI on API calls. + */ + enum NetworkError: Error { /// For the case that the ErrorResponse object has a code of -1 - case URLError(response: ErrorResponse, displayMessage: String?, logLevel: LogLevel, tag: String?) + case URLError(response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) /// For the case that the ErrorRespones object has a code of -2 - case HTTPURLError(response: ErrorResponse, displayMessage: String?, logLevel: LogLevel, tag: String?) + case HTTPURLError(response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) /// For the case that the ErrorResponse object has a positive code - case JellyfinError(response: ErrorResponse, displayMessage: String?, logLevel: LogLevel, tag: String?) + case JellyfinError(response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) var errorMessage: ErrorMessage { switch self { - case .URLError(response: let response, displayMessage: let displayMessage, let logLevel, let tag): - return NetworkError.parseURLError(from: response, displayMessage: displayMessage, logLevel: logLevel, logTag: tag) - case .HTTPURLError(response: let response, displayMessage: let displayMessage, let logLevel, let tag): - return NetworkError.parseHTTPURLError(from: response, displayMessage: displayMessage, logLevel: logLevel, logTag: tag) - case .JellyfinError(response: let response, displayMessage: let displayMessage, let logLevel, let tag): - return NetworkError.parseJellyfinError(from: response, displayMessage: displayMessage, logLevel: logLevel, logTag: tag) + case .URLError(let response, let displayMessage, let logConstructor): + return NetworkError.parseURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor) + case .HTTPURLError(let response, let displayMessage, let logConstructor): + return NetworkError.parseHTTPURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor) + case .JellyfinError(let response, let displayMessage, let logConstructor): + return NetworkError.parseJellyfinError(from: response, displayMessage: displayMessage, logConstructor: logConstructor) } } func logMessage() { - let errorMessage = self.errorMessage + let logConstructor = errorMessage.logConstructor let logFunction: (@autoclosure () -> String, String, String, String, UInt) -> Void - switch errorMessage.logLevel { + switch logConstructor.level { case .trace: logFunction = LogManager.shared.log.trace case .debug: @@ -53,13 +60,14 @@ enum NetworkError: Error { logFunction = LogManager.shared.log.debug } - logFunction(errorMessage.logMessage, "", "", "", 0) + logFunction(logConstructor.message, logConstructor.tag, logConstructor.function, logConstructor.file, logConstructor.line) } - private static func parseURLError(from response: ErrorResponse, displayMessage: String?, logLevel: LogLevel, logTag: String?) -> ErrorMessage { + private static func parseURLError(from response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) -> ErrorMessage { let errorMessage: ErrorMessage var logMessage = "An error has occurred." + var logConstructor = logConstructor switch response { case .error(_, _, _, let err): @@ -69,56 +77,54 @@ enum NetworkError: Error { switch err._code { case -1001: logMessage = "Network timed out." + logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, title: "Timed Out", displayMessage: displayMessage, - logMessage: logMessage, - logLevel: logLevel, - logTag: logTag) + logConstructor: logConstructor) case -1004: logMessage = "Cannot connect to host." + logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, title: "Error", displayMessage: displayMessage, - logMessage: logMessage, - logLevel: logLevel, - logTag: logTag) + logConstructor: logConstructor) default: + logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, title: "Error", displayMessage: displayMessage, - logMessage: logMessage, - logLevel: logLevel, - logTag: logTag) + logConstructor: logConstructor) } } return errorMessage } - private static func parseHTTPURLError(from response: ErrorResponse, displayMessage: String?, logLevel: LogLevel, logTag: String?) -> ErrorMessage { + private static func parseHTTPURLError(from response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) -> ErrorMessage { let errorMessage: ErrorMessage let logMessage = "An HTTP URL error has occurred" + var logConstructor = logConstructor // Not implemented as has not run into one of these errors as time of writing switch response { case .error(_, _, _, _): + logConstructor.message = logMessage errorMessage = ErrorMessage(code: 0, title: "Error", displayMessage: displayMessage, - logMessage: logMessage, - logLevel: logLevel, - logTag: logTag) + logConstructor: logConstructor) } return errorMessage } - private static func parseJellyfinError(from response: ErrorResponse, displayMessage: String?, logLevel: LogLevel, logTag: String?) -> ErrorMessage { + private static func parseJellyfinError(from response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) -> ErrorMessage { let errorMessage: ErrorMessage var logMessage = "An error has occurred." + var logConstructor = logConstructor switch response { case .error(let code, _, _, _): @@ -127,19 +133,17 @@ enum NetworkError: Error { switch code { case 401: logMessage = "User is unauthorized." + logConstructor.message = logMessage errorMessage = ErrorMessage(code: code, title: "Unauthorized", displayMessage: displayMessage, - logMessage: logMessage, - logLevel: logLevel, - logTag: logTag) + logConstructor: logConstructor) default: + logConstructor.message = logMessage errorMessage = ErrorMessage(code: code, title: "Error", displayMessage: displayMessage, - logMessage: logMessage, - logLevel: logLevel, - logTag: logTag) + logConstructor: logConstructor) } } diff --git a/Shared/ViewModels/ViewModel.swift b/Shared/ViewModels/ViewModel.swift index 3b2397bc..ad885955 100644 --- a/Shared/ViewModels/ViewModel.swift +++ b/Shared/ViewModels/ViewModel.swift @@ -24,7 +24,7 @@ class ViewModel: ObservableObject { loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables) } - func handleAPIRequestError(displayMessage: String? = nil, logLevel: LogLevel = .error, tag: String = "", completion: Subscribers.Completion) { + func handleAPIRequestError(displayMessage: String? = nil, logLevel: LogLevel = .error, tag: String = "", function: String = #function, file: String = #file, line: UInt = #line, completion: Subscribers.Completion) { switch completion { case .finished: break @@ -32,19 +32,20 @@ class ViewModel: ObservableObject { if let errorResponse = error as? ErrorResponse { let networkError: NetworkError + let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file, line: line) switch errorResponse { case .error(-1, _, _, _): - networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logLevel: logLevel, tag: tag) + networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) // Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented LogManager.shared.log.error("Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)") case .error(-2, _, _, _): - networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage, logLevel: logLevel, tag: tag) + networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) LogManager.shared.log.error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)") default: - networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logLevel: logLevel, tag: tag) + networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor) // Able to use user-facing friendly description here since just HTTP status codes - LogManager.shared.log.error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.logMessage)\n\(error.localizedDescription)") + LogManager.shared.log.error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.logConstructor.message)\n\(error.localizedDescription)") } self.errorMessage = networkError.errorMessage