DDLogMessageFormat.swift 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2025, 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. import CocoaLumberjack
  16. @frozen
  17. public struct DDLogMessageFormat: ExpressibleByStringInterpolation {
  18. public typealias StringLiteralType = String
  19. @usableFromInline
  20. struct Storage {
  21. @usableFromInline
  22. typealias VArg = any CVarArg
  23. @usableFromInline
  24. let requiresArgumentParsing: Bool
  25. @usableFromInline
  26. var format: String
  27. @usableFromInline
  28. var args: Array<VArg> {
  29. willSet {
  30. // We only assert here to let the compiler optimize it away.
  31. // The setter will be used repeatedly during string interpolation, thus should stay fast.
  32. assert(requiresArgumentParsing || newValue.isEmpty, "Non-empty arguments always require argument parsing!")
  33. }
  34. }
  35. @usableFromInline
  36. init(requiresArgumentParsing: Bool, format: String, args: Array<VArg>) {
  37. precondition(requiresArgumentParsing || args.isEmpty, "Non-empty arguments always require argument parsing!")
  38. self.requiresArgumentParsing = requiresArgumentParsing
  39. self.format = format
  40. self.args = args
  41. }
  42. @available(*, deprecated, message: "Use initializer specifying the need for argument parsing: init(requiresArgumentParsing:format:args:)")
  43. @usableFromInline
  44. init(format: String, args: Array<VArg>) {
  45. self.init(requiresArgumentParsing: !args.isEmpty, format: format, args: args)
  46. }
  47. @usableFromInline
  48. mutating func addString(_ string: String) {
  49. format.append(string.replacingOccurrences(of: "%", with: "%%"))
  50. }
  51. @inlinable
  52. mutating func addValue(_ arg: VArg, withSpecifier specifier: String) {
  53. format.append(specifier)
  54. args.append(arg)
  55. }
  56. }
  57. @frozen
  58. public struct StringInterpolation: StringInterpolationProtocol {
  59. @usableFromInline
  60. var storage: Storage
  61. @inlinable
  62. public init(literalCapacity: Int, interpolationCount: Int) {
  63. var format = String()
  64. format.reserveCapacity(literalCapacity)
  65. var args = Array<Storage.VArg>()
  66. args.reserveCapacity(interpolationCount)
  67. storage = .init(requiresArgumentParsing: true, format: format, args: args)
  68. }
  69. @inlinable
  70. public mutating func appendLiteral(_ literal: StringLiteralType) {
  71. storage.addString(literal)
  72. }
  73. @inlinable
  74. public mutating func appendInterpolation<S: StringProtocol>(_ string: S) {
  75. storage.addValue(String(string), withSpecifier: "%@")
  76. }
  77. @inlinable
  78. public mutating func appendInterpolation(_ int: Int8) {
  79. storage.addValue(int, withSpecifier: "%c")
  80. }
  81. @inlinable
  82. public mutating func appendInterpolation(_ int: UInt8) {
  83. storage.addValue(int, withSpecifier: "%c")
  84. }
  85. @inlinable
  86. public mutating func appendInterpolation(_ int: Int16) {
  87. storage.addValue(int, withSpecifier: "%i")
  88. }
  89. @inlinable
  90. public mutating func appendInterpolation(_ int: UInt16) {
  91. storage.addValue(int, withSpecifier: "%u")
  92. }
  93. @inlinable
  94. public mutating func appendInterpolation(_ int: Int32) {
  95. storage.addValue(int, withSpecifier: "%li")
  96. }
  97. @inlinable
  98. public mutating func appendInterpolation(_ int: UInt32) {
  99. storage.addValue(int, withSpecifier: "%lu")
  100. }
  101. @inlinable
  102. public mutating func appendInterpolation(_ int: Int64) {
  103. storage.addValue(int, withSpecifier: "%lli")
  104. }
  105. @inlinable
  106. public mutating func appendInterpolation(_ int: UInt64) {
  107. storage.addValue(int, withSpecifier: "%llu")
  108. }
  109. @inlinable
  110. public mutating func appendInterpolation(_ int: Int) {
  111. #if arch(arm64) || arch(x86_64)
  112. storage.addValue(int, withSpecifier: "%lli")
  113. #else
  114. storage.addValue(int, withSpecifier: "%li")
  115. #endif
  116. }
  117. @inlinable
  118. public mutating func appendInterpolation(_ int: UInt) {
  119. #if arch(arm64) || arch(x86_64)
  120. storage.addValue(int, withSpecifier: "%llu")
  121. #else
  122. storage.addValue(int, withSpecifier: "%lu")
  123. #endif
  124. }
  125. @inlinable
  126. public mutating func appendInterpolation(_ flt: Float) {
  127. storage.addValue(flt, withSpecifier: "%f")
  128. }
  129. @inlinable
  130. public mutating func appendInterpolation(_ dbl: Double) {
  131. storage.addValue(dbl, withSpecifier: "%lf")
  132. }
  133. @inlinable
  134. public mutating func appendInterpolation(_ bool: Bool) {
  135. storage.addValue(bool, withSpecifier: "%i") // bools are printed as ints
  136. }
  137. // Move printed string out of inlinable code portion
  138. @usableFromInline
  139. func _warnReferenceConvertibleCVarArgInterpolation<Convertible: ReferenceConvertible>(_ convertible: Convertible) {
  140. print("""
  141. [WARNING]: CocoaLumberjackSwift is creating a \(DDLogMessageFormat.self) with an interpolation conforming to `CVarArg` \
  142. using the overload for `ReferenceConvertible` interpolations!
  143. Please report this as a bug, including the following snippet:
  144. ```
  145. Convertible: \(Convertible.self), ReferenceType: \(Convertible.ReferenceType.self), type(of: convertible): \(type(of: convertible))
  146. ```
  147. """)
  148. }
  149. @inlinable
  150. public mutating func appendInterpolation<Convertible: ReferenceConvertible>(_ convertible: Convertible) {
  151. if convertible is Storage.VArg {
  152. _warnReferenceConvertibleCVarArgInterpolation(convertible)
  153. }
  154. // This should be safe, sine the compiler should convert it to the reference.
  155. // swiftlint:disable:next force_cast
  156. storage.addValue(convertible as? Storage.VArg ?? convertible as! Convertible.ReferenceType, withSpecifier: "%@")
  157. }
  158. @inlinable
  159. public mutating func appendInterpolation<Obj: NSObject>(_ object: Obj) {
  160. storage.addValue(object, withSpecifier: "%@")
  161. }
  162. @_disfavoredOverload
  163. public mutating func appendInterpolation(_ any: Any) {
  164. appendInterpolation(String(describing: any))
  165. }
  166. }
  167. @usableFromInline
  168. let storage: Storage
  169. @inlinable
  170. var format: String { storage.format }
  171. @inlinable
  172. var args: Array<Storage.VArg> { storage.args }
  173. @inlinable
  174. var formatted: String {
  175. guard storage.requiresArgumentParsing else { return storage.format }
  176. return String(format: storage.format, arguments: storage.args)
  177. }
  178. @inlinable
  179. public init(stringLiteral value: StringLiteralType) {
  180. storage = .init(requiresArgumentParsing: false, format: value, args: [])
  181. }
  182. @inlinable
  183. public init(stringInterpolation: StringInterpolation) {
  184. storage = stringInterpolation.storage
  185. }
  186. @inlinable
  187. internal init(_formattedMessage: String) {
  188. storage = .init(requiresArgumentParsing: false, format: _formattedMessage, args: [])
  189. }
  190. }
  191. extension DDLogMessage {
  192. @inlinable
  193. public convenience init(_ format: DDLogMessageFormat,
  194. level: DDLogLevel,
  195. flag: DDLogFlag,
  196. context: Int = 0,
  197. file: StaticString = #file,
  198. function: StaticString = #function,
  199. line: UInt = #line,
  200. tag: Any? = nil,
  201. timestamp: Date? = nil) {
  202. self.init(format: format.format,
  203. formatted: format.formatted,
  204. level: level,
  205. flag: flag,
  206. context: context,
  207. file: String(describing: file),
  208. function: String(describing: function),
  209. line: line,
  210. tag: tag,
  211. options: [.dontCopyMessage],
  212. timestamp: timestamp)
  213. }
  214. }