Logging.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Logging API open source project
  4. //
  5. // Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
  6. // Licensed under Apache License v2.0
  7. //
  8. // See LICENSE.txt for license information
  9. // See CONTRIBUTORS.txt for the list of Swift Logging API project authors
  10. //
  11. // SPDX-License-Identifier: Apache-2.0
  12. //
  13. //===----------------------------------------------------------------------===//
  14. #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
  15. import Darwin
  16. #else
  17. import Glibc
  18. #endif
  19. /// A `Logger` is the central type in `SwiftLog`. Its central function is to emit log messages using one of the methods
  20. /// corresponding to a log level.
  21. ///
  22. /// The most basic usage of a `Logger` is
  23. ///
  24. /// logger.info("Hello World!")
  25. ///
  26. public struct Logger {
  27. @usableFromInline
  28. var handler: LogHandler
  29. public let label: String
  30. internal init(label: String, _ handler: LogHandler) {
  31. self.label = label
  32. self.handler = handler
  33. }
  34. }
  35. extension Logger {
  36. /// Log a message passing the log level as a parameter.
  37. ///
  38. /// If the `logLevel` passed to this method is more severe than the `Logger`'s `logLevel`, it will be logged,
  39. /// otherwise nothing will happen.
  40. ///
  41. /// - parameters:
  42. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  43. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  44. /// - metadata: One-off metadata to attach to this log message
  45. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  46. /// defaults to `#file`).
  47. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  48. /// it defaults to `#function`).
  49. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  50. /// defaults to `#line`).
  51. @inlinable
  52. public func log(level: Logger.Level,
  53. _ message: @autoclosure () -> Logger.Message,
  54. metadata: @autoclosure () -> Logger.Metadata? = nil,
  55. file: String = #file, function: String = #function, line: UInt = #line) {
  56. if self.logLevel <= level {
  57. self.handler.log(level: level,
  58. message: message(),
  59. metadata: metadata(),
  60. file: file, function: function, line: line)
  61. }
  62. }
  63. /// Add, change, or remove a logging metadata item.
  64. ///
  65. /// - note: Logging metadata behaves as a value that means a change to the logging metadata will only affect the
  66. /// very `Logger` it was changed on.
  67. @inlinable
  68. public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
  69. get {
  70. return self.handler[metadataKey: metadataKey]
  71. }
  72. set {
  73. self.handler[metadataKey: metadataKey] = newValue
  74. }
  75. }
  76. /// Get or set the log level configured for this `Logger`.
  77. ///
  78. /// - note: `Logger`s treat `logLevel` as a value. This means that a change in `logLevel` will only affect this
  79. /// very `Logger`. It it acceptable for logging backends to have some form of global log level override
  80. /// that affects multiple or even all loggers. This means a change in `logLevel` to one `Logger` might in
  81. /// certain cases have no effect.
  82. @inlinable
  83. public var logLevel: Logger.Level {
  84. get {
  85. return self.handler.logLevel
  86. }
  87. set {
  88. self.handler.logLevel = newValue
  89. }
  90. }
  91. }
  92. extension Logger {
  93. /// Log a message passing with the `Logger.trace` log level.
  94. ///
  95. /// If `.trace` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
  96. /// otherwise nothing will happen.
  97. ///
  98. /// - parameters:
  99. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  100. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  101. /// - metadata: One-off metadata to attach to this log message
  102. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  103. /// defaults to `#file`).
  104. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  105. /// it defaults to `#function`).
  106. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  107. /// defaults to `#line`).
  108. @inlinable
  109. public func trace(_ message: @autoclosure () -> Logger.Message,
  110. metadata: @autoclosure () -> Logger.Metadata? = nil,
  111. file: String = #file, function: String = #function, line: UInt = #line) {
  112. self.log(level: .trace, message(), metadata: metadata(), file: file, function: function, line: line)
  113. }
  114. /// Log a message passing with the `Logger.info` log level.
  115. ///
  116. /// If `.debug` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
  117. /// otherwise nothing will happen.
  118. ///
  119. /// - parameters:
  120. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  121. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  122. /// - metadata: One-off metadata to attach to this log message
  123. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  124. /// defaults to `#file`).
  125. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  126. /// it defaults to `#function`).
  127. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  128. /// defaults to `#line`).
  129. @inlinable
  130. public func debug(_ message: @autoclosure () -> Logger.Message,
  131. metadata: @autoclosure () -> Logger.Metadata? = nil,
  132. file: String = #file, function: String = #function, line: UInt = #line) {
  133. self.log(level: .debug, message(), metadata: metadata(), file: file, function: function, line: line)
  134. }
  135. /// Log a message passing with the `Logger.Level.info` log level.
  136. ///
  137. /// If `.info` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
  138. /// otherwise nothing will happen.
  139. ///
  140. /// - parameters:
  141. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  142. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  143. /// - metadata: One-off metadata to attach to this log message
  144. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  145. /// defaults to `#file`).
  146. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  147. /// it defaults to `#function`).
  148. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  149. /// defaults to `#line`).
  150. @inlinable
  151. public func info(_ message: @autoclosure () -> Logger.Message,
  152. metadata: @autoclosure () -> Logger.Metadata? = nil,
  153. file: String = #file, function: String = #function, line: UInt = #line) {
  154. self.log(level: .info, message(), metadata: metadata(), file: file, function: function, line: line)
  155. }
  156. /// Log a message passing with the `Logger.Level.notice` log level.
  157. ///
  158. /// If `.notice` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
  159. /// otherwise nothing will happen.
  160. ///
  161. /// - parameters:
  162. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  163. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  164. /// - metadata: One-off metadata to attach to this log message
  165. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  166. /// defaults to `#file`).
  167. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  168. /// it defaults to `#function`).
  169. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  170. /// defaults to `#line`).
  171. @inlinable
  172. public func notice(_ message: @autoclosure () -> Logger.Message,
  173. metadata: @autoclosure () -> Logger.Metadata? = nil,
  174. file: String = #file, function: String = #function, line: UInt = #line) {
  175. self.log(level: .notice, message(), metadata: metadata(), file: file, function: function, line: line)
  176. }
  177. /// Log a message passing with the `Logger.Level.warning` log level.
  178. ///
  179. /// If `.warning` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
  180. /// otherwise nothing will happen.
  181. ///
  182. /// - parameters:
  183. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  184. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  185. /// - metadata: One-off metadata to attach to this log message
  186. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  187. /// defaults to `#file`).
  188. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  189. /// it defaults to `#function`).
  190. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  191. /// defaults to `#line`).
  192. @inlinable
  193. public func warning(_ message: @autoclosure () -> Logger.Message,
  194. metadata: @autoclosure () -> Logger.Metadata? = nil,
  195. file: String = #file, function: String = #function, line: UInt = #line) {
  196. self.log(level: .warning, message(), metadata: metadata(), file: file, function: function, line: line)
  197. }
  198. /// Log a message passing with the `Logger.Level.error` log level.
  199. ///
  200. /// If `.error` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
  201. /// otherwise nothing will happen.
  202. ///
  203. /// - parameters:
  204. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  205. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  206. /// - metadata: One-off metadata to attach to this log message
  207. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  208. /// defaults to `#file`).
  209. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  210. /// it defaults to `#function`).
  211. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  212. /// defaults to `#line`).
  213. @inlinable
  214. public func error(_ message: @autoclosure () -> Logger.Message,
  215. metadata: @autoclosure () -> Logger.Metadata? = nil,
  216. file: String = #file, function: String = #function, line: UInt = #line) {
  217. self.log(level: .error, message(), metadata: metadata(), file: file, function: function, line: line)
  218. }
  219. /// Log a message passing with the `Logger.Level.critical` log level.
  220. ///
  221. /// `.critical` messages will always be logged.
  222. ///
  223. /// - parameters:
  224. /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
  225. /// - message: The message to be logged. `message` can be used with any string interpolation literal.
  226. /// - metadata: One-off metadata to attach to this log message
  227. /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
  228. /// defaults to `#file`).
  229. /// - function: The function this log message originates from (there's usually no need to pass it explicitly as
  230. /// it defaults to `#function`).
  231. /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
  232. /// defaults to `#line`).
  233. @inlinable
  234. public func critical(_ message: @autoclosure () -> Logger.Message,
  235. metadata: @autoclosure () -> Logger.Metadata? = nil,
  236. file: String = #file, function: String = #function, line: UInt = #line) {
  237. self.log(level: .critical, message(), metadata: metadata(), file: file, function: function, line: line)
  238. }
  239. }
  240. /// The `LoggingSystem` is a global facility where the default logging backend implementation (`LogHandler`) can be
  241. /// configured. `LoggingSystem` is set up just once in a given program to set up the desired logging backend
  242. /// implementation.
  243. public enum LoggingSystem {
  244. fileprivate static let lock = ReadWriteLock()
  245. fileprivate static var factory: (String) -> LogHandler = StdoutLogHandler.init
  246. fileprivate static var initialized = false
  247. /// `bootstrap` is a one-time configuration function which globally selects the desired logging backend
  248. /// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will
  249. /// lead to undefined behaviour, most likely a crash.
  250. ///
  251. /// - parameters:
  252. /// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
  253. public static func bootstrap(_ factory: @escaping (String) -> LogHandler) {
  254. self.lock.withWriterLock {
  255. precondition(!self.initialized, "logging system can only be initialized once per process.")
  256. self.factory = factory
  257. self.initialized = true
  258. }
  259. }
  260. // for our testing we want to allow multiple bootstraping
  261. internal static func bootstrapInternal(_ factory: @escaping (String) -> LogHandler) {
  262. self.lock.withWriterLock {
  263. self.factory = factory
  264. }
  265. }
  266. }
  267. extension Logger {
  268. /// `Metadata` is a typealias for `[String: Logger.MetadataValue]` the type of the metadata storage.
  269. public typealias Metadata = [String: MetadataValue]
  270. /// A logging metadata value. `Logger.MetadataValue` is string, array, and dictionary literal convertible.
  271. public enum MetadataValue {
  272. /// A metadata value which is a `String`.
  273. case string(String)
  274. /// A metadata value which is some `CustomStringConvertible`.
  275. case stringConvertible(CustomStringConvertible)
  276. /// A metadata value which is a dictionary from `String` to `Logger.MetadataValue`.
  277. case dictionary(Metadata)
  278. /// A metadata value which is an array of `Logger.MetadataValue`s.
  279. case array([Metadata.Value])
  280. }
  281. /// The log level.
  282. ///
  283. /// Raw values of log levels correspond to their severity, and are ordered by lowest numeric value (0) being
  284. /// the most severe. The raw values match the syslog values.
  285. public enum Level: CaseIterable {
  286. /// Appropriate for messages that contain information only when debugging a program.
  287. case trace
  288. /// Appropriate for messages that contain information normally of use only when
  289. /// debugging a program.
  290. case debug
  291. /// Appropriate for informational messages.
  292. case info
  293. /// Appropriate for conditions that are not error conditions, but that may require
  294. /// special handling.
  295. case notice
  296. /// Appropriate for messages that are not error conditions, but more severe than
  297. /// `.notice`.
  298. case warning
  299. /// Appropriate for error conditions.
  300. case error
  301. /// Appropriate for critical error conditions that usually require immediate
  302. /// attention.
  303. ///
  304. /// When a `critical` message is logged, the logging backend (`LogHandler`) is free to perform
  305. /// more heavy-weight operations to capture system state (such as capturing stack traces) to facilitate
  306. /// debugging.
  307. case critical
  308. }
  309. /// Construct a `Logger` given a `label` identifying the creator of the `Logger`.
  310. ///
  311. /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
  312. /// a datatype.
  313. ///
  314. /// - parameters:
  315. /// - label: An identifier for the creator of a `Logger`.
  316. public init(label: String) {
  317. self = LoggingSystem.lock.withReaderLock { Logger(label: label, LoggingSystem.factory(label)) }
  318. }
  319. /// Construct a `Logger` given a `label` identifying the creator of the `Logger` or a non-standard `LogHandler`.
  320. ///
  321. /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
  322. /// a datatype.
  323. ///
  324. /// This initializer provides an escape hatch in case the global default logging backend implementation (set up
  325. /// using `LoggingSystem.bootstrap` is not appropriate for this particular logger.
  326. ///
  327. /// - parameters:
  328. /// - label: An identifier for the creator of a `Logger`.
  329. /// - factory: A closure creating non-standard `LogHandler`s.
  330. public init(label: String, factory: (String) -> LogHandler) {
  331. self = Logger(label: label, factory(label))
  332. }
  333. }
  334. extension Logger.Level {
  335. internal var naturalIntegralValue: Int {
  336. switch self {
  337. case .trace:
  338. return 0
  339. case .debug:
  340. return 1
  341. case .info:
  342. return 2
  343. case .notice:
  344. return 3
  345. case .warning:
  346. return 4
  347. case .error:
  348. return 5
  349. case .critical:
  350. return 6
  351. }
  352. }
  353. }
  354. extension Logger.Level: Comparable {
  355. public static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
  356. return lhs.naturalIntegralValue < rhs.naturalIntegralValue
  357. }
  358. }
  359. // Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
  360. // https://bugs.swift.org/browse/SR-9687
  361. // Then we could write it as follows and it would work under Swift 5 and not only 4 as it does currently:
  362. // extension Logger.Metadata.Value: Equatable {
  363. extension Logger.MetadataValue: Equatable {
  364. public static func == (lhs: Logger.Metadata.Value, rhs: Logger.Metadata.Value) -> Bool {
  365. switch (lhs, rhs) {
  366. case (.string(let lhs), .string(let rhs)):
  367. return lhs == rhs
  368. case (.stringConvertible(let lhs), .stringConvertible(let rhs)):
  369. return lhs.description == rhs.description
  370. case (.array(let lhs), .array(let rhs)):
  371. return lhs == rhs
  372. case (.dictionary(let lhs), .dictionary(let rhs)):
  373. return lhs == rhs
  374. default:
  375. return false
  376. }
  377. }
  378. }
  379. extension Logger {
  380. /// `Logger.Message` represents a log message's text. It is usually created using string literals.
  381. ///
  382. /// Example creating a `Logger.Message`:
  383. ///
  384. /// let world: String = "world"
  385. /// let myLogMessage: Logger.Message = "Hello \(world)"
  386. ///
  387. /// Most commonly, `Logger.Message`s appear simply as the parameter to a logging method such as:
  388. ///
  389. /// logger.info("Hello \(world)")
  390. ///
  391. public struct Message: ExpressibleByStringLiteral,
  392. Equatable,
  393. CustomStringConvertible,
  394. ExpressibleByStringInterpolation {
  395. public typealias StringLiteralType = String
  396. private var value: String
  397. public init(stringLiteral value: String) {
  398. self.value = value
  399. }
  400. public var description: String {
  401. return self.value
  402. }
  403. }
  404. }
  405. /// A pseudo-`LogHandler` that can be used to send messages to multiple other `LogHandler`s.
  406. ///
  407. /// The first `LogHandler` passed to the initialisation function of `MultiplexLogHandler` control the `logLevel` as
  408. /// well as the `metadata` for this `LogHandler`. Any subsequent `LogHandler`s used to initialise a
  409. /// `MultiplexLogHandler` are merely to emit the log message to another place.
  410. public struct MultiplexLogHandler: LogHandler {
  411. private var handlers: [LogHandler]
  412. public init(_ handlers: [LogHandler]) {
  413. assert(handlers.count > 0)
  414. self.handlers = handlers
  415. }
  416. public var logLevel: Logger.Level {
  417. get {
  418. return self.handlers[0].logLevel
  419. }
  420. set {
  421. self.mutatingForEachHandler {
  422. $0.logLevel = newValue
  423. }
  424. }
  425. }
  426. public func log(level: Logger.Level,
  427. message: Logger.Message,
  428. metadata: Logger.Metadata?,
  429. file: String, function: String, line: UInt) {
  430. self.handlers.forEach { handler in
  431. handler.log(level: level, message: message, metadata: metadata, file: file, function: function, line: line)
  432. }
  433. }
  434. public var metadata: Logger.Metadata {
  435. get {
  436. return self.handlers[0].metadata
  437. }
  438. set {
  439. self.mutatingForEachHandler { $0.metadata = newValue }
  440. }
  441. }
  442. public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
  443. get {
  444. return self.handlers[0].metadata[metadataKey]
  445. }
  446. set {
  447. self.mutatingForEachHandler { $0[metadataKey: metadataKey] = newValue }
  448. }
  449. }
  450. private mutating func mutatingForEachHandler(_ mutator: (inout LogHandler) -> Void) {
  451. for index in self.handlers.indices {
  452. mutator(&self.handlers[index])
  453. }
  454. }
  455. }
  456. /// Ships with the logging module, really boring just prints something using the `print` function
  457. internal struct StdoutLogHandler: LogHandler {
  458. private let lock = Lock()
  459. public init(label: String) {}
  460. private var _logLevel: Logger.Level = .info
  461. public var logLevel: Logger.Level {
  462. get {
  463. return self.lock.withLock { self._logLevel }
  464. }
  465. set {
  466. self.lock.withLock {
  467. self._logLevel = newValue
  468. }
  469. }
  470. }
  471. private var prettyMetadata: String?
  472. private var _metadata = Logger.Metadata() {
  473. didSet {
  474. self.prettyMetadata = self.prettify(self._metadata)
  475. }
  476. }
  477. public func log(level: Logger.Level,
  478. message: Logger.Message,
  479. metadata: Logger.Metadata?,
  480. file: String, function: String, line: UInt) {
  481. let prettyMetadata = metadata?.isEmpty ?? true
  482. ? self.prettyMetadata
  483. : self.prettify(self.metadata.merging(metadata!, uniquingKeysWith: { _, new in new }))
  484. print("\(self.timestamp()) \(level):\(prettyMetadata.map { " \($0)" } ?? "") \(message)")
  485. }
  486. public var metadata: Logger.Metadata {
  487. get {
  488. return self.lock.withLock { self._metadata }
  489. }
  490. set {
  491. self.lock.withLock { self._metadata = newValue }
  492. }
  493. }
  494. public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
  495. get {
  496. return self.lock.withLock { self._metadata[metadataKey] }
  497. }
  498. set {
  499. self.lock.withLock {
  500. self._metadata[metadataKey] = newValue
  501. }
  502. }
  503. }
  504. private func prettify(_ metadata: Logger.Metadata) -> String? {
  505. return !metadata.isEmpty ? metadata.map { "\($0)=\($1)" }.joined(separator: " ") : nil
  506. }
  507. private func timestamp() -> String {
  508. var buffer = [Int8](repeating: 0, count: 255)
  509. var timestamp = time(nil)
  510. let localTime = localtime(&timestamp)
  511. strftime(&buffer, buffer.count, "%Y-%m-%dT%H:%M:%S%z", localTime)
  512. return buffer.withUnsafeBufferPointer {
  513. $0.withMemoryRebound(to: CChar.self) {
  514. String(cString: $0.baseAddress!)
  515. }
  516. }
  517. }
  518. }
  519. // Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
  520. // https://bugs.swift.org/browse/SR-9686
  521. extension Logger.MetadataValue: ExpressibleByStringLiteral {
  522. public typealias StringLiteralType = String
  523. public init(stringLiteral value: String) {
  524. self = .string(value)
  525. }
  526. }
  527. // Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
  528. // https://bugs.swift.org/browse/SR-9686
  529. extension Logger.MetadataValue: CustomStringConvertible {
  530. public var description: String {
  531. switch self {
  532. case .dictionary(let dict):
  533. return dict.mapValues { $0.description }.description
  534. case .array(let list):
  535. return list.map { $0.description }.description
  536. case .string(let str):
  537. return str
  538. case .stringConvertible(let repr):
  539. return repr.description
  540. }
  541. }
  542. }
  543. // Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
  544. // https://bugs.swift.org/browse/SR-9687
  545. extension Logger.MetadataValue: ExpressibleByStringInterpolation {}
  546. // Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
  547. // https://bugs.swift.org/browse/SR-9686
  548. extension Logger.MetadataValue: ExpressibleByDictionaryLiteral {
  549. public typealias Key = String
  550. public typealias Value = Logger.Metadata.Value
  551. public init(dictionaryLiteral elements: (String, Logger.Metadata.Value)...) {
  552. self = .dictionary(.init(uniqueKeysWithValues: elements))
  553. }
  554. }
  555. // Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
  556. // https://bugs.swift.org/browse/SR-9686
  557. extension Logger.MetadataValue: ExpressibleByArrayLiteral {
  558. public typealias ArrayLiteralElement = Logger.Metadata.Value
  559. public init(arrayLiteral elements: Logger.Metadata.Value...) {
  560. self = .array(elements)
  561. }
  562. }