CocoaLumberjack.swift 22 KB

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