Logger.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. import os.log
  2. import os.signpost
  3. import UIKit
  4. var LoggerTestMode = false
  5. private let baseReporter = TrioApp.resolver.resolve(GroupedIssueReporter.self)!
  6. private let router = TrioApp.resolver.resolve(Router.self)!
  7. let loggerLock = NSRecursiveLock()
  8. func debug(
  9. _ category: Logger.Category,
  10. _ message: @autoclosure () -> String,
  11. printToConsole: Bool = true,
  12. file: String = #file,
  13. function: String = #function,
  14. line: UInt = #line
  15. ) {
  16. let msg = message()
  17. DispatchWorkItem(qos: .background, flags: .enforceQoS) {
  18. loggerLock.perform {
  19. category.logger.debug(msg, printToConsole: printToConsole, file: file, function: function, line: line)
  20. }
  21. }.perform()
  22. }
  23. func info(
  24. _ category: Logger.Category,
  25. _ message: String,
  26. type: MessageType = .info,
  27. file: String = #file,
  28. function: String = #function,
  29. line: UInt = #line
  30. ) {
  31. DispatchWorkItem(qos: .background, flags: .enforceQoS) {
  32. loggerLock.perform {
  33. category.logger.info(message, type: type, file: file, function: function, line: line)
  34. }
  35. }.perform()
  36. }
  37. func info(
  38. _ category: Logger.Category,
  39. _ message: String,
  40. notificationText: String,
  41. type: MessageType = .info,
  42. file: String = #file,
  43. function: String = #function,
  44. line: UInt = #line
  45. ) {
  46. DispatchWorkItem(qos: .background, flags: .enforceQoS) {
  47. loggerLock.perform {
  48. category.logger.info(
  49. message,
  50. notificationText: notificationText,
  51. type: type,
  52. file: file,
  53. function: function,
  54. line: line
  55. )
  56. }
  57. }.perform()
  58. }
  59. func warning(
  60. _ category: Logger.Category,
  61. _ message: String,
  62. description: String? = nil,
  63. error maybeError: Swift.Error? = nil,
  64. file: String = #file,
  65. function: String = #function,
  66. line: UInt = #line
  67. ) {
  68. DispatchWorkItem(qos: .background, flags: .enforceQoS) {
  69. loggerLock.perform {
  70. category.logger.warning(
  71. message,
  72. description: description,
  73. error: maybeError,
  74. file: file,
  75. function: function,
  76. line: line
  77. )
  78. }
  79. }.perform()
  80. }
  81. func error(
  82. _ category: Logger.Category,
  83. _ message: String,
  84. description: String? = nil,
  85. error maybeError: Swift.Error? = nil,
  86. file: String = #file,
  87. function: String = #function,
  88. line: UInt = #line
  89. ) -> Never {
  90. loggerLock.perform {
  91. category.logger.errorWithoutFatalError(
  92. message,
  93. description: description,
  94. error: maybeError,
  95. file: file,
  96. function: function,
  97. line: line
  98. )
  99. fatalError(
  100. "\(message) @ \(String(describing: description)) @ \(String(describing: maybeError)) @ \(file) @ \(function) @ \(line)"
  101. )
  102. }
  103. }
  104. func check(
  105. _ condition: @autoclosure () -> Bool,
  106. _ message: @autoclosure () -> String,
  107. description: @autoclosure () -> String? = nil,
  108. file: String = #file,
  109. function: String = #function,
  110. line: UInt = #line
  111. ) {
  112. guard !condition() else { return }
  113. let msg = message()
  114. let descr = description()
  115. loggerLock.perform {
  116. warning(.default, msg, description: descr, file: file.file, function: function, line: line)
  117. }
  118. }
  119. final class Logger {
  120. static let `default` = Logger(category: .default, reporter: baseReporter)
  121. static let service = Logger(category: .service, reporter: baseReporter)
  122. static let businessLogic = Logger(category: .businessLogic, reporter: baseReporter)
  123. static let openAPS = Logger(category: .openAPS, reporter: baseReporter)
  124. static let deviceManager = Logger(category: .deviceManager, reporter: baseReporter)
  125. static let apsManager = Logger(category: .apsManager, reporter: baseReporter)
  126. static let nightscout = Logger(category: .nightscout, reporter: baseReporter)
  127. static let remoteControl = Logger(category: .remoteControl, reporter: baseReporter)
  128. static let bolusState = Logger(category: .bolusState, reporter: baseReporter)
  129. static let watchManager = Logger(category: .watchManager, reporter: baseReporter)
  130. static let coreData = Logger(category: .coreData, reporter: baseReporter)
  131. static let storage = Logger(category: .storage, reporter: baseReporter)
  132. enum Category: String {
  133. case `default`
  134. case service
  135. case businessLogic
  136. case openAPS
  137. case deviceManager
  138. case apsManager
  139. case nightscout
  140. case remoteControl
  141. case bolusState
  142. case watchManager
  143. case coreData
  144. case storage
  145. var name: String {
  146. rawValue.capitalizingFirstLetter()
  147. }
  148. var logger: Logger {
  149. switch self {
  150. case .default: return .default
  151. case .service: return .service
  152. case .businessLogic: return .businessLogic
  153. case .openAPS: return .openAPS
  154. case .deviceManager: return .deviceManager
  155. case .apsManager: return .apsManager
  156. case .nightscout: return .nightscout
  157. case .remoteControl: return .remoteControl
  158. case .bolusState: return .bolusState
  159. case .watchManager: return .watchManager
  160. case .coreData: return .coreData
  161. case .storage: return .storage
  162. }
  163. }
  164. fileprivate var log: OSLog {
  165. let subsystem = Bundle.main.bundleIdentifier!
  166. switch self {
  167. case .default: return OSLog.default
  168. case .apsManager,
  169. .bolusState,
  170. .businessLogic,
  171. .coreData,
  172. .deviceManager,
  173. .nightscout,
  174. .openAPS,
  175. .remoteControl,
  176. .service,
  177. .storage,
  178. .watchManager:
  179. return OSLog(subsystem: subsystem, category: name)
  180. }
  181. }
  182. }
  183. fileprivate enum Error: Swift.Error {
  184. case error(String)
  185. case errorWithInnerError(String, Swift.Error)
  186. case errorWithDescription(String, String)
  187. case errorWithDescriptionAndInnerError(String, String, Swift.Error)
  188. private func domain() -> String {
  189. switch self {
  190. case let .error(domain),
  191. let .errorWithDescription(domain, _),
  192. let .errorWithDescriptionAndInnerError(domain, _, _),
  193. let .errorWithInnerError(domain, _):
  194. return domain
  195. }
  196. }
  197. private func innerError() -> Swift.Error? {
  198. switch self {
  199. case let .errorWithDescriptionAndInnerError(_, _, error),
  200. let .errorWithInnerError(_, error):
  201. return error
  202. default: return nil
  203. }
  204. }
  205. func asNSError() -> NSError {
  206. var info: [String: Any] = ["Description": String(describing: self)]
  207. if let error = innerError() {
  208. info["Error"] = String(describing: error)
  209. }
  210. return NSError(domain: domain(), code: -1, userInfo: info)
  211. }
  212. }
  213. private let category: Category
  214. private let reporter: IssueReporter
  215. let log: OSLog
  216. private init(category: Category, reporter: IssueReporter) {
  217. self.category = category
  218. self.reporter = reporter
  219. log = category.log
  220. }
  221. static func setup() {
  222. loggerLock.perform {
  223. baseReporter.setup()
  224. }
  225. }
  226. func debug(
  227. _ message: @autoclosure () -> String,
  228. printToConsole: Bool = true,
  229. file: String = #file,
  230. function: String = #function,
  231. line: UInt = #line
  232. ) {
  233. let message = "DEV: \(message())"
  234. if printToConsole {
  235. os_log("%@ - %@ - %d %{public}@", log: log, type: .debug, file.file, function, line, message)
  236. }
  237. reporter.log(category.name, message, file: file, function: function, line: line)
  238. }
  239. func info(
  240. _ message: String,
  241. type: MessageType = .info,
  242. file: String = #file,
  243. function: String = #function,
  244. line: UInt = #line
  245. ) {
  246. info(message, notificationText: message, type: type, file: file, function: function, line: line)
  247. }
  248. func info(
  249. _ message: String,
  250. notificationText: String,
  251. type: MessageType = .info,
  252. file: String = #file,
  253. function: String = #function,
  254. line: UInt = #line
  255. ) {
  256. let printedMessage = "INFO: \(message)"
  257. os_log("%@ - %@ - %d %{public}@", log: log, type: .info, file.file, function, line, printedMessage)
  258. reporter.log(category.name, printedMessage, file: file, function: function, line: line)
  259. showAlert(notificationText, type: type)
  260. }
  261. func warning(
  262. _ message: String,
  263. description: String? = nil,
  264. error maybeError: Swift.Error? = nil,
  265. file: String = #file,
  266. function: String = #function,
  267. line: UInt = #line
  268. ) {
  269. let loggerError = maybeError.loggerError(message: message, withDescription: description)
  270. let message = "WARN: \(String(describing: loggerError))"
  271. os_log("%@ - %@ - %d %{public}@", log: log, type: .default, file.file, function, line, message)
  272. reporter.log(category.name, message, file: file, function: function, line: line)
  273. if !LoggerTestMode, maybeError?.shouldReportNonFatalIssue ?? true {
  274. reporter.reportNonFatalIssue(withError: loggerError.asNSError())
  275. }
  276. }
  277. func error(
  278. _ message: String,
  279. description: String? = nil,
  280. error maybeError: Swift.Error? = nil,
  281. file: String = #file,
  282. function: String = #function,
  283. line: UInt = #line
  284. ) -> Never {
  285. errorWithoutFatalError(message, description: description, error: maybeError, file: file, function: function, line: line)
  286. fatalError(
  287. "\(message) @ \(String(describing: description)) @ \(String(describing: maybeError)) @ \(file) @ \(function) @ \(line)"
  288. )
  289. }
  290. private func showAlert(_ message: String, type: MessageType = .info) {
  291. DispatchQueue.main.async {
  292. let messageCont = MessageContent(content: message, type: type)
  293. router.alertMessage.send(messageCont)
  294. }
  295. }
  296. fileprivate func errorWithoutFatalError(
  297. _ message: String,
  298. description: String? = nil,
  299. error maybeError: Swift.Error? = nil,
  300. file: String = #file,
  301. function: String = #function,
  302. line: UInt = #line
  303. ) {
  304. let loggerError = maybeError.loggerError(message: message, withDescription: description)
  305. let message = "ERR: \(String(describing: loggerError))"
  306. os_log("%@ - %@ - %d %{public}@", log: log, type: .error, file.file, function, line, message)
  307. reporter.log(category.name, message, file: file, function: function, line: line)
  308. reporter.reportNonFatalIssue(withError: loggerError.asNSError())
  309. }
  310. }
  311. private extension Optional where Wrapped == Swift.Error {
  312. func loggerError(message: String, withDescription description: String?) -> Logger.Error {
  313. switch (description, self) {
  314. case (nil, nil):
  315. return .error(message)
  316. case let (descr?, nil):
  317. return .errorWithDescription(message, descr)
  318. case let (nil, error?):
  319. return .errorWithInnerError(message, error)
  320. case let (descr?, error?):
  321. return .errorWithDescriptionAndInnerError(message, descr, error)
  322. }
  323. }
  324. }
  325. private extension String {
  326. var file: String { components(separatedBy: "/").last ?? "" }
  327. }