CocoaLumberjack.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2024, Deusty, LLC
  4. // All rights reserved.
  5. //
  6. // Redistribution and use of this software in source and binary forms,
  7. // with or without modification, are permitted provided that the following conditions are met:
  8. //
  9. // * Redistributions of source code must retain the above copyright notice,
  10. // this list of conditions and the following disclaimer.
  11. //
  12. // * Neither the name of Deusty nor the names of its contributors may be used
  13. // to endorse or promote products derived from this software without specific
  14. // prior written permission of Deusty, LLC.
  15. // swiftlint:disable file_length
  16. @_exported import CocoaLumberjack
  17. #if SWIFT_PACKAGE
  18. import CocoaLumberjackSwiftSupport
  19. #endif
  20. extension DDLogFlag {
  21. public static func from(_ logLevel: DDLogLevel) -> DDLogFlag {
  22. DDLogFlag(rawValue: logLevel.rawValue)
  23. }
  24. public init(_ logLevel: DDLogLevel) {
  25. self = DDLogFlag(rawValue: logLevel.rawValue)
  26. }
  27. /// Returns the log level, or the lowest equivalent.
  28. public func toLogLevel() -> DDLogLevel {
  29. if let ourValid = DDLogLevel(rawValue: rawValue) {
  30. return ourValid
  31. } else {
  32. if contains(.verbose) {
  33. return .verbose
  34. } else if contains(.debug) {
  35. return .debug
  36. } else if contains(.info) {
  37. return .info
  38. } else if contains(.warning) {
  39. return .warning
  40. } else if contains(.error) {
  41. return .error
  42. } else {
  43. return .off
  44. }
  45. }
  46. }
  47. }
  48. /// The log level that can dynamically limit log messages (vs. the static DDDefaultLogLevel). This log level will only be checked, if the message passes the `DDDefaultLogLevel`.
  49. public var dynamicLogLevel = DDLogLevel.all
  50. /// Resets the ``dynamicLogLevel`` to ``DDLogLevel/all``.
  51. /// - SeeAlso: ``dynamicLogLevel``
  52. @inlinable
  53. public func resetDynamicLogLevel() {
  54. dynamicLogLevel = .all
  55. }
  56. @available(*, deprecated, message: "Please use dynamicLogLevel", renamed: "dynamicLogLevel")
  57. public var defaultDebugLevel: DDLogLevel {
  58. get {
  59. dynamicLogLevel
  60. }
  61. set {
  62. dynamicLogLevel = newValue
  63. }
  64. }
  65. @available(*, deprecated, message: "Please use resetDynamicLogLevel", renamed: "resetDynamicLogLevel")
  66. public func resetDefaultDebugLevel() {
  67. resetDynamicLogLevel()
  68. }
  69. /// If `true`, all logs (except errors) are logged asynchronously by default.
  70. public var asyncLoggingEnabled = true
  71. @frozen
  72. public struct DDLogMessageFormat: ExpressibleByStringInterpolation {
  73. public typealias StringLiteralType = String
  74. @usableFromInline
  75. struct Storage {
  76. #if swift(>=5.6)
  77. @usableFromInline
  78. typealias Args = Array<any CVarArg>
  79. #else
  80. @usableFromInline
  81. typealias Args = Array<CVarArg>
  82. #endif
  83. @usableFromInline
  84. let requiresArgumentParsing: Bool
  85. @usableFromInline
  86. var format: String
  87. @usableFromInline
  88. var args: Args {
  89. willSet {
  90. // We only assert here to let the compiler optimize it away.
  91. // The setter will be used repeatedly during string interpolation, thus should stay fast.
  92. assert(requiresArgumentParsing || newValue.isEmpty, "Non-empty arguments always require argument parsing!")
  93. }
  94. }
  95. @usableFromInline
  96. init(requiresArgumentParsing: Bool, format: String, args: Args) {
  97. precondition(requiresArgumentParsing || args.isEmpty, "Non-empty arguments always require argument parsing!")
  98. self.requiresArgumentParsing = requiresArgumentParsing
  99. self.format = format
  100. self.args = args
  101. }
  102. @available(*, deprecated, message: "Use initializer specifying the need for argument parsing: init(requiresArgumentParsing:format:args:)")
  103. @usableFromInline
  104. init(format: String, args: Args) {
  105. self.init(requiresArgumentParsing: !args.isEmpty, format: format, args: args)
  106. }
  107. @usableFromInline
  108. mutating func addString(_ string: String) {
  109. format.append(string.replacingOccurrences(of: "%", with: "%%"))
  110. }
  111. @inlinable
  112. mutating func addValue(_ arg: Args.Element, withSpecifier specifier: String) {
  113. format.append(specifier)
  114. args.append(arg)
  115. }
  116. }
  117. @frozen
  118. public struct StringInterpolation: StringInterpolationProtocol {
  119. @usableFromInline
  120. var storage: Storage
  121. @inlinable
  122. public init(literalCapacity: Int, interpolationCount: Int) {
  123. var format = String()
  124. format.reserveCapacity(literalCapacity)
  125. var args = Storage.Args()
  126. args.reserveCapacity(interpolationCount)
  127. storage = .init(requiresArgumentParsing: true, format: format, args: args)
  128. }
  129. @inlinable
  130. public mutating func appendLiteral(_ literal: StringLiteralType) {
  131. storage.addString(literal)
  132. }
  133. @inlinable
  134. public mutating func appendInterpolation<S: StringProtocol>(_ string: S) {
  135. storage.addValue(String(string), withSpecifier: "%@")
  136. }
  137. @inlinable
  138. public mutating func appendInterpolation(_ int: Int8) {
  139. storage.addValue(int, withSpecifier: "%c")
  140. }
  141. @inlinable
  142. public mutating func appendInterpolation(_ int: UInt8) {
  143. storage.addValue(int, withSpecifier: "%c")
  144. }
  145. @inlinable
  146. public mutating func appendInterpolation(_ int: Int16) {
  147. storage.addValue(int, withSpecifier: "%i")
  148. }
  149. @inlinable
  150. public mutating func appendInterpolation(_ int: UInt16) {
  151. storage.addValue(int, withSpecifier: "%u")
  152. }
  153. @inlinable
  154. public mutating func appendInterpolation(_ int: Int32) {
  155. storage.addValue(int, withSpecifier: "%li")
  156. }
  157. @inlinable
  158. public mutating func appendInterpolation(_ int: UInt32) {
  159. storage.addValue(int, withSpecifier: "%lu")
  160. }
  161. @inlinable
  162. public mutating func appendInterpolation(_ int: Int64) {
  163. storage.addValue(int, withSpecifier: "%lli")
  164. }
  165. @inlinable
  166. public mutating func appendInterpolation(_ int: UInt64) {
  167. storage.addValue(int, withSpecifier: "%llu")
  168. }
  169. @inlinable
  170. public mutating func appendInterpolation(_ int: Int) {
  171. #if arch(arm64) || arch(x86_64)
  172. storage.addValue(int, withSpecifier: "%lli")
  173. #else
  174. storage.addValue(int, withSpecifier: "%li")
  175. #endif
  176. }
  177. @inlinable
  178. public mutating func appendInterpolation(_ int: UInt) {
  179. #if arch(arm64) || arch(x86_64)
  180. storage.addValue(int, withSpecifier: "%llu")
  181. #else
  182. storage.addValue(int, withSpecifier: "%lu")
  183. #endif
  184. }
  185. @inlinable
  186. public mutating func appendInterpolation(_ flt: Float) {
  187. storage.addValue(flt, withSpecifier: "%f")
  188. }
  189. @inlinable
  190. public mutating func appendInterpolation(_ dbl: Double) {
  191. storage.addValue(dbl, withSpecifier: "%lf")
  192. }
  193. @inlinable
  194. public mutating func appendInterpolation(_ bool: Bool) {
  195. storage.addValue(bool, withSpecifier: "%i") // bools are printed as ints
  196. }
  197. @inlinable
  198. public mutating func appendInterpolation<Convertible: ReferenceConvertible>(_ convertible: Convertible) {
  199. if convertible is CVarArg {
  200. print("""
  201. [WARNING]: CocoaLumberjackSwift is creating a \(DDLogMessageFormat.self) with an interpolation conforming to `CVarArg` \
  202. using the overload for `ReferenceConvertible` interpolations!
  203. Please report this as a bug, including the following snippet:
  204. ```
  205. Convertible: \(Convertible.self), ReferenceType: \(Convertible.ReferenceType.self), type(of: convertible): \(type(of: convertible))
  206. ```
  207. """)
  208. }
  209. // This should be safe, sine the compiler should convert it to the reference.
  210. storage.addValue(convertible as? CVarArg ?? convertible as! Convertible.ReferenceType, withSpecifier: "%@")
  211. }
  212. @inlinable
  213. public mutating func appendInterpolation<Obj: NSObject>(_ object: Obj) {
  214. storage.addValue(object, withSpecifier: "%@")
  215. }
  216. @_disfavoredOverload
  217. public mutating func appendInterpolation(_ any: Any) {
  218. appendInterpolation(String(describing: any))
  219. }
  220. }
  221. @usableFromInline
  222. let storage: Storage
  223. @inlinable
  224. var format: String { storage.format }
  225. @inlinable
  226. var args: Storage.Args { storage.args }
  227. @inlinable
  228. var formatted: String {
  229. guard storage.requiresArgumentParsing else { return storage.format }
  230. return String(format: storage.format, arguments: storage.args)
  231. }
  232. @inlinable
  233. public init(stringLiteral value: StringLiteralType) {
  234. storage = .init(requiresArgumentParsing: false, format: value, args: [])
  235. }
  236. @inlinable
  237. public init(stringInterpolation: StringInterpolation) {
  238. storage = stringInterpolation.storage
  239. }
  240. @inlinable
  241. internal init(_formattedMessage: String) {
  242. storage = .init(requiresArgumentParsing: false, format: _formattedMessage, args: [])
  243. }
  244. }
  245. extension DDLogMessage {
  246. @inlinable
  247. public convenience init(_ format: DDLogMessageFormat,
  248. level: DDLogLevel,
  249. flag: DDLogFlag,
  250. context: Int = 0,
  251. file: StaticString = #file,
  252. function: StaticString = #function,
  253. line: UInt = #line,
  254. tag: Any? = nil,
  255. timestamp: Date? = nil) {
  256. self.init(format: format.format,
  257. formatted: format.formatted,
  258. level: level,
  259. flag: flag,
  260. context: context,
  261. file: String(describing: file),
  262. function: String(describing: function),
  263. line: line,
  264. tag: tag,
  265. options: [.dontCopyMessage],
  266. timestamp: timestamp)
  267. }
  268. }
  269. @inlinable
  270. public func _DDLogMessage(_ messageFormat: @autoclosure () -> DDLogMessageFormat,
  271. level: DDLogLevel,
  272. flag: DDLogFlag,
  273. context: Int,
  274. file: StaticString,
  275. function: StaticString,
  276. line: UInt,
  277. tag: Any?,
  278. asynchronous: Bool,
  279. ddlog: DDLog) {
  280. // The `dynamicLogLevel` will always be checked here (instead of being passed in).
  281. // We cannot "mix" it with the `DDDefaultLogLevel`, because otherwise the compiler won't strip strings that are not logged.
  282. if level.rawValue & flag.rawValue != 0 && dynamicLogLevel.rawValue & flag.rawValue != 0 {
  283. let logMessage = DDLogMessage(messageFormat(),
  284. level: level,
  285. flag: flag,
  286. context: context,
  287. file: file,
  288. function: function,
  289. line: line,
  290. tag: tag)
  291. ddlog.log(asynchronous: asynchronous, message: logMessage)
  292. }
  293. }
  294. @inlinable
  295. public func DDLogDebug(_ message: @autoclosure () -> DDLogMessageFormat,
  296. level: DDLogLevel = DDDefaultLogLevel,
  297. context: Int = 0,
  298. file: StaticString = #file,
  299. function: StaticString = #function,
  300. line: UInt = #line,
  301. tag: Any? = nil,
  302. asynchronous: Bool = asyncLoggingEnabled,
  303. ddlog: DDLog = .sharedInstance) {
  304. _DDLogMessage(message(),
  305. level: level,
  306. flag: .debug,
  307. context: context,
  308. file: file,
  309. function: function,
  310. line: line,
  311. tag: tag,
  312. asynchronous: asynchronous,
  313. ddlog: ddlog)
  314. }
  315. @inlinable
  316. public func DDLogInfo(_ message: @autoclosure () -> DDLogMessageFormat,
  317. level: DDLogLevel = DDDefaultLogLevel,
  318. context: Int = 0,
  319. file: StaticString = #file,
  320. function: StaticString = #function,
  321. line: UInt = #line,
  322. tag: Any? = nil,
  323. asynchronous: Bool = asyncLoggingEnabled,
  324. ddlog: DDLog = .sharedInstance) {
  325. _DDLogMessage(message(),
  326. level: level,
  327. flag: .info,
  328. context: context,
  329. file: file,
  330. function: function,
  331. line: line,
  332. tag: tag,
  333. asynchronous: asynchronous,
  334. ddlog: ddlog)
  335. }
  336. @inlinable
  337. public func DDLogWarn(_ message: @autoclosure () -> DDLogMessageFormat,
  338. level: DDLogLevel = DDDefaultLogLevel,
  339. context: Int = 0,
  340. file: StaticString = #file,
  341. function: StaticString = #function,
  342. line: UInt = #line,
  343. tag: Any? = nil,
  344. asynchronous: Bool = asyncLoggingEnabled,
  345. ddlog: DDLog = .sharedInstance) {
  346. _DDLogMessage(message(),
  347. level: level,
  348. flag: .warning,
  349. context: context,
  350. file: file,
  351. function: function,
  352. line: line,
  353. tag: tag,
  354. asynchronous: asynchronous,
  355. ddlog: ddlog)
  356. }
  357. @inlinable
  358. public func DDLogVerbose(_ message: @autoclosure () -> DDLogMessageFormat,
  359. level: DDLogLevel = DDDefaultLogLevel,
  360. context: Int = 0,
  361. file: StaticString = #file,
  362. function: StaticString = #function,
  363. line: UInt = #line,
  364. tag: Any? = nil,
  365. asynchronous: Bool = asyncLoggingEnabled,
  366. ddlog: DDLog = .sharedInstance) {
  367. _DDLogMessage(message(),
  368. level: level,
  369. flag: .verbose,
  370. context: context,
  371. file: file,
  372. function: function,
  373. line: line,
  374. tag: tag,
  375. asynchronous: asynchronous,
  376. ddlog: ddlog)
  377. }
  378. @inlinable
  379. public func DDLogError(_ message: @autoclosure () -> DDLogMessageFormat,
  380. level: DDLogLevel = DDDefaultLogLevel,
  381. context: Int = 0,
  382. file: StaticString = #file,
  383. function: StaticString = #function,
  384. line: UInt = #line,
  385. tag: Any? = nil,
  386. asynchronous: Bool = false,
  387. ddlog: DDLog = .sharedInstance) {
  388. _DDLogMessage(message(),
  389. level: level,
  390. flag: .error,
  391. context: context,
  392. file: file,
  393. function: function,
  394. line: line,
  395. tag: tag,
  396. asynchronous: asynchronous,
  397. ddlog: ddlog)
  398. }
  399. @available(*, deprecated, message: "Use an interpolated DDLogMessageFormat instead")
  400. @inlinable
  401. @_disfavoredOverload
  402. public func _DDLogMessage(_ message: @autoclosure () -> Any,
  403. level: DDLogLevel,
  404. flag: DDLogFlag,
  405. context: Int,
  406. file: StaticString,
  407. function: StaticString,
  408. line: UInt,
  409. tag: Any?,
  410. asynchronous: Bool,
  411. ddlog: DDLog) {
  412. // This will lead to `messageFormat` and `message` being equal on DDLogMessage,
  413. // which is what the legacy initializer of DDLogMessage does as well.
  414. _DDLogMessage(.init(_formattedMessage: String(describing: message())),
  415. level: level,
  416. flag: flag,
  417. context: context,
  418. file: file,
  419. function: function,
  420. line: line,
  421. tag: tag,
  422. asynchronous: asynchronous,
  423. ddlog: ddlog)
  424. }
  425. @available(*, deprecated, message: "Use an interpolated DDLogMessageFormat instead")
  426. @inlinable
  427. @_disfavoredOverload
  428. public func DDLogDebug(_ message: @autoclosure () -> Any,
  429. level: DDLogLevel = DDDefaultLogLevel,
  430. context: Int = 0,
  431. file: StaticString = #file,
  432. function: StaticString = #function,
  433. line: UInt = #line,
  434. tag: Any? = nil,
  435. asynchronous async: Bool = asyncLoggingEnabled,
  436. ddlog: DDLog = .sharedInstance) {
  437. _DDLogMessage(message(),
  438. level: level,
  439. flag: .debug,
  440. context: context,
  441. file: file,
  442. function: function,
  443. line: line,
  444. tag: tag,
  445. asynchronous: async,
  446. ddlog: ddlog)
  447. }
  448. @available(*, deprecated, message: "Use an interpolated DDLogMessageFormat instead")
  449. @inlinable
  450. @_disfavoredOverload
  451. public func DDLogInfo(_ message: @autoclosure () -> Any,
  452. level: DDLogLevel = DDDefaultLogLevel,
  453. context: Int = 0,
  454. file: StaticString = #file,
  455. function: StaticString = #function,
  456. line: UInt = #line,
  457. tag: Any? = nil,
  458. asynchronous async: Bool = asyncLoggingEnabled,
  459. ddlog: DDLog = .sharedInstance) {
  460. _DDLogMessage(message(),
  461. level: level,
  462. flag: .info,
  463. context: context,
  464. file: file,
  465. function: function,
  466. line: line,
  467. tag: tag,
  468. asynchronous: async,
  469. ddlog: ddlog)
  470. }
  471. @available(*, deprecated, message: "Use an interpolated DDLogMessageFormat instead")
  472. @inlinable
  473. @_disfavoredOverload
  474. public func DDLogWarn(_ message: @autoclosure () -> Any,
  475. level: DDLogLevel = DDDefaultLogLevel,
  476. context: Int = 0,
  477. file: StaticString = #file,
  478. function: StaticString = #function,
  479. line: UInt = #line,
  480. tag: Any? = nil,
  481. asynchronous async: Bool = asyncLoggingEnabled,
  482. ddlog: DDLog = .sharedInstance) {
  483. _DDLogMessage(message(),
  484. level: level,
  485. flag: .warning,
  486. context: context,
  487. file: file,
  488. function: function,
  489. line: line,
  490. tag: tag,
  491. asynchronous: async,
  492. ddlog: ddlog)
  493. }
  494. @available(*, deprecated, message: "Use an interpolated DDLogMessageFormat instead")
  495. @inlinable
  496. @_disfavoredOverload
  497. public func DDLogVerbose(_ message: @autoclosure () -> Any,
  498. level: DDLogLevel = DDDefaultLogLevel,
  499. context: Int = 0,
  500. file: StaticString = #file,
  501. function: StaticString = #function,
  502. line: UInt = #line,
  503. tag: Any? = nil,
  504. asynchronous async: Bool = asyncLoggingEnabled,
  505. ddlog: DDLog = .sharedInstance) {
  506. _DDLogMessage(message(),
  507. level: level,
  508. flag: .verbose,
  509. context: context,
  510. file: file,
  511. function: function,
  512. line: line,
  513. tag: tag,
  514. asynchronous: async,
  515. ddlog: ddlog)
  516. }
  517. @available(*, deprecated, message: "Use an interpolated DDLogMessageFormat instead")
  518. @inlinable
  519. @_disfavoredOverload
  520. public func DDLogError(_ message: @autoclosure () -> Any,
  521. level: DDLogLevel = DDDefaultLogLevel,
  522. context: Int = 0,
  523. file: StaticString = #file,
  524. function: StaticString = #function,
  525. line: UInt = #line,
  526. tag: Any? = nil,
  527. asynchronous async: Bool = false,
  528. ddlog: DDLog = .sharedInstance) {
  529. _DDLogMessage(message(),
  530. level: level,
  531. flag: .error,
  532. context: context,
  533. file: file,
  534. function: function,
  535. line: line,
  536. tag: tag,
  537. asynchronous: async,
  538. ddlog: ddlog)
  539. }
  540. /// Returns a String of the current filename, without full path or extension.
  541. /// Analogous to the C preprocessor macro `THIS_FILE`.
  542. public func currentFileName(_ fileName: StaticString = #file) -> String {
  543. var str = String(describing: fileName)
  544. if let idx = str.range(of: "/", options: .backwards)?.upperBound {
  545. str = String(str[idx...])
  546. }
  547. if let idx = str.range(of: ".", options: .backwards)?.lowerBound {
  548. str = String(str[..<idx])
  549. }
  550. return str
  551. }
  552. // swiftlint:disable identifier_name
  553. // swiftlint doesn't like func names that begin with a capital letter - deprecated
  554. @available(*, deprecated, message: "Please use currentFileName", renamed: "currentFileName")
  555. public func CurrentFileName(_ fileName: StaticString = #file) -> String {
  556. currentFileName(fileName)
  557. }
  558. // swiftlint:enable identifier_name
  559. // swiftlint:enable file_length