Logger.swift 10 KB

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