import os.log import os.signpost import UIKit var LoggerTestMode = false private let baseReporter = FreeAPSApp.resolver.resolve(GroupedIssueReporter.self)! private let router = FreeAPSApp.resolver.resolve(Router.self)! let loggerLock = NSRecursiveLock() func debug( _ category: Logger.Category, _ message: @autoclosure () -> String, printToConsole: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { let msg = message() DispatchWorkItem(qos: .background, flags: .enforceQoS) { loggerLock.perform { category.logger.debug(msg, printToConsole: printToConsole, file: file, function: function, line: line) } }.perform() } func info( _ category: Logger.Category, _ message: String, type: MessageType = .info, file: String = #file, function: String = #function, line: UInt = #line ) { DispatchWorkItem(qos: .background, flags: .enforceQoS) { loggerLock.perform { category.logger.info(message, type: type, file: file, function: function, line: line) } }.perform() } func warning( _ category: Logger.Category, _ message: String, description: String? = nil, error maybeError: Swift.Error? = nil, file: String = #file, function: String = #function, line: UInt = #line ) { DispatchWorkItem(qos: .background, flags: .enforceQoS) { loggerLock.perform { category.logger.warning( message, description: description, error: maybeError, file: file, function: function, line: line ) } }.perform() } func error( _ category: Logger.Category, _ message: String, description: String? = nil, error maybeError: Swift.Error? = nil, file: String = #file, function: String = #function, line: UInt = #line ) -> Never { loggerLock.perform { category.logger.errorWithoutFatalError( message, description: description, error: maybeError, file: file, function: function, line: line ) fatalError( "\(message) @ \(String(describing: description)) @ \(String(describing: maybeError)) @ \(file) @ \(function) @ \(line)" ) } } func check( _ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String, description: @autoclosure () -> String? = nil, file: String = #file, function: String = #function, line: UInt = #line ) { guard !condition() else { return } let msg = message() let descr = description() loggerLock.perform { warning(.default, msg, description: descr, file: file.file, function: function, line: line) } } final class Logger { static let `default` = Logger(category: .default, reporter: baseReporter) static let service = Logger(category: .service, reporter: baseReporter) static let businessLogic = Logger(category: .businessLogic, reporter: baseReporter) static let openAPS = Logger(category: .openAPS, reporter: baseReporter) static let deviceManager = Logger(category: .deviceManager, reporter: baseReporter) static let apsManager = Logger(category: .apsManager, reporter: baseReporter) static let nightscout = Logger(category: .nightscout, reporter: baseReporter) enum Category: String { case `default` case service case businessLogic case openAPS case deviceManager case apsManager case nightscout var name: String { rawValue.capitalizingFirstLetter() } var logger: Logger { switch self { case .default: return .default case .service: return .service case .businessLogic: return .businessLogic case .openAPS: return .openAPS case .deviceManager: return .deviceManager case .apsManager: return .apsManager case .nightscout: return .nightscout } } fileprivate var log: OSLog { let subsystem = Bundle.main.bundleIdentifier! switch self { case .default: return OSLog.default case .apsManager, .businessLogic, .deviceManager, .nightscout, .openAPS, .service: return OSLog(subsystem: subsystem, category: name) } } } fileprivate enum Error: Swift.Error { case error(String) case errorWithInnerError(String, Swift.Error) case errorWithDescription(String, String) case errorWithDescriptionAndInnerError(String, String, Swift.Error) private func domain() -> String { switch self { case let .error(domain), let .errorWithDescription(domain, _), let .errorWithDescriptionAndInnerError(domain, _, _), let .errorWithInnerError(domain, _): return domain } } private func innerError() -> Swift.Error? { switch self { case let .errorWithDescriptionAndInnerError(_, _, error), let .errorWithInnerError(_, error): return error default: return nil } } func asNSError() -> NSError { var info: [String: Any] = ["Description": String(describing: self)] if let error = innerError() { info["Error"] = String(describing: error) } return NSError(domain: domain(), code: -1, userInfo: info) } } private let category: Category private let reporter: IssueReporter let log: OSLog private init(category: Category, reporter: IssueReporter) { self.category = category self.reporter = reporter log = category.log } static func setup() { loggerLock.perform { baseReporter.setup() } } func debug( _ message: @autoclosure () -> String, printToConsole: Bool = true, file: String = #file, function: String = #function, line: UInt = #line ) { let message = "DEV: \(message())" if printToConsole { os_log("%@ - %@ - %d %{public}@", log: log, type: .debug, file.file, function, line, message) } reporter.log(category.name, message, file: file, function: function, line: line) } func info( _ message: String, type: MessageType = .info, file: String = #file, function: String = #function, line: UInt = #line ) { let printedMessage = "INFO: \(message)" os_log("%@ - %@ - %d %{public}@", log: log, type: .info, file.file, function, line, printedMessage) reporter.log(category.name, printedMessage, file: file, function: function, line: line) showAlert(message, type: type) } func warning( _ message: String, description: String? = nil, error maybeError: Swift.Error? = nil, file: String = #file, function: String = #function, line: UInt = #line ) { let loggerError = maybeError.loggerError(message: message, withDescription: description) let message = "WARN: \(String(describing: loggerError))" os_log("%@ - %@ - %d %{public}@", log: log, type: .default, file.file, function, line, message) reporter.log(category.name, message, file: file, function: function, line: line) if !LoggerTestMode, maybeError?.shouldReportNonFatalIssue ?? true { reporter.reportNonFatalIssue(withError: loggerError.asNSError()) } } func error( _ message: String, description: String? = nil, error maybeError: Swift.Error? = nil, file: String = #file, function: String = #function, line: UInt = #line ) -> Never { errorWithoutFatalError(message, description: description, error: maybeError, file: file, function: function, line: line) fatalError( "\(message) @ \(String(describing: description)) @ \(String(describing: maybeError)) @ \(file) @ \(function) @ \(line)" ) } private func showAlert(_ message: String, type: MessageType = .info) { DispatchQueue.main.async { let messageCont = MessageContent(content: message, type: type) router.alertMessage.send(messageCont) } } fileprivate func errorWithoutFatalError( _ message: String, description: String? = nil, error maybeError: Swift.Error? = nil, file: String = #file, function: String = #function, line: UInt = #line ) { let loggerError = maybeError.loggerError(message: message, withDescription: description) let message = "ERR: \(String(describing: loggerError))" os_log("%@ - %@ - %d %{public}@", log: log, type: .error, file.file, function, line, message) reporter.log(category.name, message, file: file, function: function, line: line) reporter.reportNonFatalIssue(withError: loggerError.asNSError()) } } private extension Optional where Wrapped == Swift.Error { func loggerError(message: String, withDescription description: String?) -> Logger.Error { switch (description, self) { case (nil, nil): return .error(message) case let (descr?, nil): return .errorWithDescription(message, descr) case let (nil, error?): return .errorWithInnerError(message, error) case let (descr?, error?): return .errorWithDescriptionAndInnerError(message, descr, error) } } } private extension String { var file: String { components(separatedBy: "/").last ?? "" } }