LoggingTest.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  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. @testable import Logging
  15. import XCTest
  16. #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
  17. import Darwin
  18. #else
  19. import Glibc
  20. #endif
  21. class LoggingTest: XCTestCase {
  22. func testAutoclosure() throws {
  23. // bootstrap with our test logging impl
  24. let logging = TestLogging()
  25. LoggingSystem.bootstrapInternal(logging.make)
  26. var logger = Logger(label: "test")
  27. logger.logLevel = .info
  28. logger.log(level: .debug, {
  29. XCTFail("debug should not be called")
  30. return "debug"
  31. }())
  32. logger.trace({
  33. XCTFail("trace should not be called")
  34. return "trace"
  35. }())
  36. logger.debug({
  37. XCTFail("debug should not be called")
  38. return "debug"
  39. }())
  40. logger.info({
  41. "info"
  42. }())
  43. logger.warning({
  44. "warning"
  45. }())
  46. logger.error({
  47. "error"
  48. }())
  49. XCTAssertEqual(3, logging.history.entries.count, "expected number of entries to match")
  50. logging.history.assertNotExist(level: .debug, message: "trace")
  51. logging.history.assertNotExist(level: .debug, message: "debug")
  52. logging.history.assertExist(level: .info, message: "info")
  53. logging.history.assertExist(level: .warning, message: "warning")
  54. logging.history.assertExist(level: .error, message: "error")
  55. }
  56. func testMultiplex() throws {
  57. // bootstrap with our test logging impl
  58. let logging1 = TestLogging()
  59. let logging2 = TestLogging()
  60. LoggingSystem.bootstrapInternal { MultiplexLogHandler([logging1.make(label: $0), logging2.make(label: $0)]) }
  61. var logger = Logger(label: "test")
  62. logger.logLevel = .warning
  63. logger.info("hello world?")
  64. logger[metadataKey: "foo"] = "bar"
  65. logger.warning("hello world!")
  66. logging1.history.assertNotExist(level: .info, message: "hello world?")
  67. logging2.history.assertNotExist(level: .info, message: "hello world?")
  68. logging1.history.assertExist(level: .warning, message: "hello world!", metadata: ["foo": "bar"])
  69. logging2.history.assertExist(level: .warning, message: "hello world!", metadata: ["foo": "bar"])
  70. }
  71. enum TestError: Error {
  72. case boom
  73. }
  74. func testDictionaryMetadata() {
  75. let testLogging = TestLogging()
  76. LoggingSystem.bootstrapInternal(testLogging.make)
  77. var logger = Logger(label: "\(#function)")
  78. logger[metadataKey: "foo"] = ["bar": "buz"]
  79. logger[metadataKey: "empty-dict"] = [:]
  80. logger[metadataKey: "nested-dict"] = ["l1key": ["l2key": ["l3key": "l3value"]]]
  81. logger.info("hello world!")
  82. testLogging.history.assertExist(level: .info,
  83. message: "hello world!",
  84. metadata: ["foo": ["bar": "buz"],
  85. "empty-dict": [:],
  86. "nested-dict": ["l1key": ["l2key": ["l3key": "l3value"]]]])
  87. }
  88. func testListMetadata() {
  89. let testLogging = TestLogging()
  90. LoggingSystem.bootstrapInternal(testLogging.make)
  91. var logger = Logger(label: "\(#function)")
  92. logger[metadataKey: "foo"] = ["bar", "buz"]
  93. logger[metadataKey: "empty-list"] = []
  94. logger[metadataKey: "nested-list"] = ["l1str", ["l2str1", "l2str2"]]
  95. logger.info("hello world!")
  96. testLogging.history.assertExist(level: .info,
  97. message: "hello world!",
  98. metadata: ["foo": ["bar", "buz"],
  99. "empty-list": [],
  100. "nested-list": ["l1str", ["l2str1", "l2str2"]]])
  101. }
  102. // Example of custom "box" which may be used to implement "render at most once" semantics
  103. // Not thread-safe, thus should not be shared across threads.
  104. internal class LazyMetadataBox: CustomStringConvertible {
  105. private var makeValue: (() -> String)?
  106. private var _value: String?
  107. public init(_ makeValue: @escaping () -> String) {
  108. self.makeValue = makeValue
  109. }
  110. /// This allows caching a value in case it is accessed via an by name subscript,
  111. // rather than as part of rendering all metadata that a LoggingContext was carrying
  112. public var value: String {
  113. if let f = self.makeValue {
  114. self._value = f()
  115. self.makeValue = nil
  116. }
  117. assert(self._value != nil, "_value MUST NOT be nil once `lazyValue` has run.")
  118. return self._value!
  119. }
  120. public var description: String {
  121. return "\(self.value)"
  122. }
  123. }
  124. func testStringConvertibleMetadata() {
  125. let testLogging = TestLogging()
  126. LoggingSystem.bootstrapInternal(testLogging.make)
  127. var logger = Logger(label: "\(#function)")
  128. logger[metadataKey: "foo"] = .stringConvertible("raw-string")
  129. let lazyBox = LazyMetadataBox { "rendered-at-first-use" }
  130. logger[metadataKey: "lazy"] = .stringConvertible(lazyBox)
  131. logger.info("hello world!")
  132. testLogging.history.assertExist(level: .info,
  133. message: "hello world!",
  134. metadata: ["foo": .stringConvertible("raw-string"),
  135. "lazy": .stringConvertible(LazyMetadataBox { "rendered-at-first-use" })])
  136. }
  137. private func dontEvaluateThisString(file: StaticString = #file, line: UInt = #line) -> Logger.Message {
  138. XCTFail("should not have been evaluated", file: file, line: line)
  139. return "should not have been evaluated"
  140. }
  141. func testAutoClosuresAreNotForcedUnlessNeeded() {
  142. let testLogging = TestLogging()
  143. LoggingSystem.bootstrapInternal(testLogging.make)
  144. var logger = Logger(label: "\(#function)")
  145. logger.logLevel = .error
  146. logger.debug(self.dontEvaluateThisString(), metadata: ["foo": "\(self.dontEvaluateThisString())"])
  147. logger.debug(self.dontEvaluateThisString())
  148. logger.info(self.dontEvaluateThisString())
  149. logger.warning(self.dontEvaluateThisString())
  150. logger.log(level: .warning, self.dontEvaluateThisString())
  151. }
  152. func testLocalMetadata() {
  153. let testLogging = TestLogging()
  154. LoggingSystem.bootstrapInternal(testLogging.make)
  155. var logger = Logger(label: "\(#function)")
  156. logger.info("hello world!", metadata: ["foo": "bar"])
  157. logger[metadataKey: "bar"] = "baz"
  158. logger[metadataKey: "baz"] = "qux"
  159. logger.warning("hello world!")
  160. logger.error("hello world!", metadata: ["baz": "quc"])
  161. testLogging.history.assertExist(level: .info, message: "hello world!", metadata: ["foo": "bar"])
  162. testLogging.history.assertExist(level: .warning, message: "hello world!", metadata: ["bar": "baz", "baz": "qux"])
  163. testLogging.history.assertExist(level: .error, message: "hello world!", metadata: ["bar": "baz", "baz": "quc"])
  164. }
  165. func testCustomFactory() {
  166. struct CustomHandler: LogHandler {
  167. func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, file: String, function: String, line: UInt) {}
  168. subscript(metadataKey _: String) -> Logger.Metadata.Value? {
  169. get { return nil }
  170. set {}
  171. }
  172. var metadata: Logger.Metadata {
  173. get { return Logger.Metadata() }
  174. set {}
  175. }
  176. var logLevel: Logger.Level {
  177. get { return .info }
  178. set {}
  179. }
  180. }
  181. let logger1 = Logger(label: "foo")
  182. XCTAssertFalse(logger1.handler is CustomHandler, "expected non-custom log handler")
  183. let logger2 = Logger(label: "foo", factory: { _ in CustomHandler() })
  184. XCTAssertTrue(logger2.handler is CustomHandler, "expected custom log handler")
  185. }
  186. func testAllLogLevelsExceptCriticalCanBeBlocked() {
  187. let testLogging = TestLogging()
  188. LoggingSystem.bootstrapInternal(testLogging.make)
  189. var logger = Logger(label: "\(#function)")
  190. logger.logLevel = .critical
  191. logger.trace("no")
  192. logger.debug("no")
  193. logger.info("no")
  194. logger.notice("no")
  195. logger.warning("no")
  196. logger.error("no")
  197. logger.critical("yes: critical")
  198. testLogging.history.assertNotExist(level: .trace, message: "no")
  199. testLogging.history.assertNotExist(level: .debug, message: "no")
  200. testLogging.history.assertNotExist(level: .info, message: "no")
  201. testLogging.history.assertNotExist(level: .notice, message: "no")
  202. testLogging.history.assertNotExist(level: .warning, message: "no")
  203. testLogging.history.assertNotExist(level: .error, message: "no")
  204. testLogging.history.assertExist(level: .critical, message: "yes: critical")
  205. }
  206. func testAllLogLevelsWork() {
  207. let testLogging = TestLogging()
  208. LoggingSystem.bootstrapInternal(testLogging.make)
  209. var logger = Logger(label: "\(#function)")
  210. logger.logLevel = .trace
  211. logger.trace("yes: trace")
  212. logger.debug("yes: debug")
  213. logger.info("yes: info")
  214. logger.notice("yes: notice")
  215. logger.warning("yes: warning")
  216. logger.error("yes: error")
  217. logger.critical("yes: critical")
  218. testLogging.history.assertExist(level: .trace, message: "yes: trace")
  219. testLogging.history.assertExist(level: .debug, message: "yes: debug")
  220. testLogging.history.assertExist(level: .info, message: "yes: info")
  221. testLogging.history.assertExist(level: .notice, message: "yes: notice")
  222. testLogging.history.assertExist(level: .warning, message: "yes: warning")
  223. testLogging.history.assertExist(level: .error, message: "yes: error")
  224. testLogging.history.assertExist(level: .critical, message: "yes: critical")
  225. }
  226. func testLogMessageWithStringInterpolation() {
  227. let testLogging = TestLogging()
  228. LoggingSystem.bootstrapInternal(testLogging.make)
  229. var logger = Logger(label: "\(#function)")
  230. logger.logLevel = .debug
  231. let someInt = Int.random(in: 23 ..< 42)
  232. logger.debug("My favourite number is \(someInt) and not \(someInt - 1)")
  233. testLogging.history.assertExist(level: .debug,
  234. message: "My favourite number is \(someInt) and not \(someInt - 1)" as String)
  235. }
  236. func testLoggingAString() {
  237. let testLogging = TestLogging()
  238. LoggingSystem.bootstrapInternal(testLogging.make)
  239. var logger = Logger(label: "\(#function)")
  240. logger.logLevel = .debug
  241. let anActualString: String = "hello world!"
  242. // We can't stick an actual String in here because we expect a Logger.Message. If we want to log an existing
  243. // `String`, we can use string interpolation. The error you'll get trying to use the String directly is:
  244. //
  245. // error: Cannot convert value of type 'String' to expected argument type 'Logger.Message'
  246. logger.debug("\(anActualString)")
  247. testLogging.history.assertExist(level: .debug, message: "hello world!")
  248. }
  249. func testMultiplexerIsValue() {
  250. let multi = MultiplexLogHandler([StreamLogHandler.standardOutput(label: "x"), StreamLogHandler.standardOutput(label: "y")])
  251. LoggingSystem.bootstrapInternal { _ in
  252. print("new multi")
  253. return multi
  254. }
  255. let logger1: Logger = {
  256. var logger = Logger(label: "foo")
  257. logger.logLevel = .debug
  258. logger[metadataKey: "only-on"] = "first"
  259. return logger
  260. }()
  261. XCTAssertEqual(.debug, logger1.logLevel)
  262. var logger2 = logger1
  263. logger2.logLevel = .error
  264. logger2[metadataKey: "only-on"] = "second"
  265. XCTAssertEqual(.error, logger2.logLevel)
  266. XCTAssertEqual(.debug, logger1.logLevel)
  267. XCTAssertEqual("first", logger1[metadataKey: "only-on"])
  268. XCTAssertEqual("second", logger2[metadataKey: "only-on"])
  269. logger1.error("hey")
  270. }
  271. func testLoggerWithGlobalOverride() {
  272. struct LogHandlerWithGlobalLogLevelOverride: LogHandler {
  273. // the static properties hold the globally overridden log level (if overridden)
  274. private static let overrideLock = Lock()
  275. private static var overrideLogLevel: Logger.Level?
  276. private let recorder: Recorder
  277. // this holds the log level if not overridden
  278. private var _logLevel: Logger.Level = .info
  279. // metadata storage
  280. var metadata: Logger.Metadata = [:]
  281. init(recorder: Recorder) {
  282. self.recorder = recorder
  283. }
  284. var logLevel: Logger.Level {
  285. // when we get asked for the log level, we check if it was globally overridden or not
  286. get {
  287. return LogHandlerWithGlobalLogLevelOverride.overrideLock.withLock {
  288. LogHandlerWithGlobalLogLevelOverride.overrideLogLevel
  289. } ?? self._logLevel
  290. }
  291. // we set the log level whenever we're asked (note: this might not have an effect if globally
  292. // overridden)
  293. set {
  294. self._logLevel = newValue
  295. }
  296. }
  297. func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?,
  298. file: String, function: String, line: UInt) {
  299. self.recorder.record(level: level, metadata: metadata, message: message)
  300. }
  301. subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
  302. get {
  303. return self.metadata[metadataKey]
  304. }
  305. set(newValue) {
  306. self.metadata[metadataKey] = newValue
  307. }
  308. }
  309. // this is the function to globally override the log level, it is not part of the `LogHandler` protocol
  310. static func overrideGlobalLogLevel(_ logLevel: Logger.Level) {
  311. LogHandlerWithGlobalLogLevelOverride.overrideLock.withLock {
  312. LogHandlerWithGlobalLogLevelOverride.overrideLogLevel = logLevel
  313. }
  314. }
  315. }
  316. let logRecorder = Recorder()
  317. LoggingSystem.bootstrapInternal { _ in
  318. LogHandlerWithGlobalLogLevelOverride(recorder: logRecorder)
  319. }
  320. var logger1 = Logger(label: "logger-\(#file):\(#line)")
  321. var logger2 = logger1
  322. logger1.logLevel = .warning
  323. logger1[metadataKey: "only-on"] = "first"
  324. logger2.logLevel = .error
  325. logger2[metadataKey: "only-on"] = "second"
  326. XCTAssertEqual(.error, logger2.logLevel)
  327. XCTAssertEqual(.warning, logger1.logLevel)
  328. XCTAssertEqual("first", logger1[metadataKey: "only-on"])
  329. XCTAssertEqual("second", logger2[metadataKey: "only-on"])
  330. logger1.notice("logger1, before")
  331. logger2.notice("logger2, before")
  332. LogHandlerWithGlobalLogLevelOverride.overrideGlobalLogLevel(.debug)
  333. logger1.notice("logger1, after")
  334. logger2.notice("logger2, after")
  335. logRecorder.assertNotExist(level: .notice, message: "logger1, before")
  336. logRecorder.assertNotExist(level: .notice, message: "logger2, before")
  337. logRecorder.assertExist(level: .notice, message: "logger1, after")
  338. logRecorder.assertExist(level: .notice, message: "logger2, after")
  339. }
  340. func testLogLevelCases() {
  341. let levels = Logger.Level.allCases
  342. XCTAssertEqual(7, levels.count)
  343. }
  344. func testLogLevelOrdering() {
  345. XCTAssertLessThan(Logger.Level.trace, Logger.Level.debug)
  346. XCTAssertLessThan(Logger.Level.trace, Logger.Level.info)
  347. XCTAssertLessThan(Logger.Level.trace, Logger.Level.notice)
  348. XCTAssertLessThan(Logger.Level.trace, Logger.Level.warning)
  349. XCTAssertLessThan(Logger.Level.trace, Logger.Level.error)
  350. XCTAssertLessThan(Logger.Level.trace, Logger.Level.critical)
  351. XCTAssertLessThan(Logger.Level.debug, Logger.Level.info)
  352. XCTAssertLessThan(Logger.Level.debug, Logger.Level.notice)
  353. XCTAssertLessThan(Logger.Level.debug, Logger.Level.warning)
  354. XCTAssertLessThan(Logger.Level.debug, Logger.Level.error)
  355. XCTAssertLessThan(Logger.Level.debug, Logger.Level.critical)
  356. XCTAssertLessThan(Logger.Level.info, Logger.Level.notice)
  357. XCTAssertLessThan(Logger.Level.info, Logger.Level.warning)
  358. XCTAssertLessThan(Logger.Level.info, Logger.Level.error)
  359. XCTAssertLessThan(Logger.Level.info, Logger.Level.critical)
  360. XCTAssertLessThan(Logger.Level.notice, Logger.Level.warning)
  361. XCTAssertLessThan(Logger.Level.notice, Logger.Level.error)
  362. XCTAssertLessThan(Logger.Level.notice, Logger.Level.critical)
  363. XCTAssertLessThan(Logger.Level.warning, Logger.Level.error)
  364. XCTAssertLessThan(Logger.Level.warning, Logger.Level.critical)
  365. XCTAssertLessThan(Logger.Level.error, Logger.Level.critical)
  366. }
  367. final class InterceptStream: TextOutputStream {
  368. var interceptedText: String?
  369. var strings = [String]()
  370. func write(_ string: String) {
  371. // This is a test implementation, a real implementation would include locking
  372. self.strings.append(string)
  373. self.interceptedText = (self.interceptedText ?? "") + string
  374. }
  375. }
  376. func testStreamLogHandlerWritesToAStream() {
  377. let interceptStream = InterceptStream()
  378. LoggingSystem.bootstrapInternal { _ in
  379. StreamLogHandler(label: "test", stream: interceptStream)
  380. }
  381. let log = Logger(label: "test")
  382. let testString = "my message is better than yours"
  383. log.critical("\(testString)")
  384. let messageSucceeded = interceptStream.interceptedText?.trimmingCharacters(in: .whitespacesAndNewlines).hasSuffix(testString)
  385. XCTAssertTrue(messageSucceeded ?? false)
  386. XCTAssertEqual(interceptStream.strings.count, 1)
  387. }
  388. func testStreamLogHandlerOutputFormat() {
  389. let interceptStream = InterceptStream()
  390. let label = "testLabel"
  391. LoggingSystem.bootstrapInternal { _ in
  392. StreamLogHandler(label: label, stream: interceptStream)
  393. }
  394. let log = Logger(label: label)
  395. let testString = "my message is better than yours"
  396. log.critical("\(testString)")
  397. let pattern = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\+|-)\\d{4}\\s\(Logger.Level.critical)\\s\(label)\\s:\\s\(testString)$"
  398. let messageSucceeded = interceptStream.interceptedText?.trimmingCharacters(in: .whitespacesAndNewlines).range(of: pattern, options: .regularExpression) != nil
  399. XCTAssertTrue(messageSucceeded)
  400. XCTAssertEqual(interceptStream.strings.count, 1)
  401. }
  402. func testStreamLogHandlerOutputFormatWithMetaData() {
  403. let interceptStream = InterceptStream()
  404. let label = "testLabel"
  405. LoggingSystem.bootstrapInternal { _ in
  406. StreamLogHandler(label: label, stream: interceptStream)
  407. }
  408. let log = Logger(label: label)
  409. let testString = "my message is better than yours"
  410. log.critical("\(testString)", metadata: ["test": "test"])
  411. let pattern = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\+|-)\\d{4}\\s\(Logger.Level.critical)\\s\(label)\\s:\\stest=test\\s\(testString)$"
  412. let messageSucceeded = interceptStream.interceptedText?.trimmingCharacters(in: .whitespacesAndNewlines).range(of: pattern, options: .regularExpression) != nil
  413. XCTAssertTrue(messageSucceeded)
  414. XCTAssertEqual(interceptStream.strings.count, 1)
  415. }
  416. func testStdioOutputStreamFlush() {
  417. // flush on every statement
  418. self.withWriteReadFDsAndReadBuffer { writeFD, readFD, readBuffer in
  419. let logStream = StdioOutputStream(file: writeFD, flushMode: .always)
  420. LoggingSystem.bootstrapInternal { StreamLogHandler(label: $0, stream: logStream) }
  421. Logger(label: "test").critical("test")
  422. let size = read(readFD, readBuffer, 256)
  423. XCTAssertGreaterThan(size, -1, "expected flush")
  424. logStream.flush()
  425. let size2 = read(readFD, readBuffer, 256)
  426. XCTAssertEqual(size2, -1, "expected no flush")
  427. }
  428. // default flushing
  429. self.withWriteReadFDsAndReadBuffer { writeFD, readFD, readBuffer in
  430. let logStream = StdioOutputStream(file: writeFD, flushMode: .undefined)
  431. LoggingSystem.bootstrapInternal { StreamLogHandler(label: $0, stream: logStream) }
  432. Logger(label: "test").critical("test")
  433. let size = read(readFD, readBuffer, 256)
  434. XCTAssertEqual(size, -1, "expected no flush")
  435. logStream.flush()
  436. let size2 = read(readFD, readBuffer, 256)
  437. XCTAssertGreaterThan(size2, -1, "expected flush")
  438. }
  439. }
  440. func withWriteReadFDsAndReadBuffer(_ body: (UnsafeMutablePointer<FILE>, CInt, UnsafeMutablePointer<Int8>) -> Void) {
  441. var fds: [Int32] = [-1, -1]
  442. fds.withUnsafeMutableBufferPointer { ptr in
  443. let err = pipe(ptr.baseAddress!)
  444. XCTAssertEqual(err, 0, "pipe faild \(err)")
  445. }
  446. let writeFD = fdopen(fds[1], "w")
  447. let writeBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: 256)
  448. defer {
  449. writeBuffer.deinitialize(count: 256)
  450. writeBuffer.deallocate()
  451. }
  452. var err = setvbuf(writeFD, writeBuffer, _IOFBF, 256)
  453. XCTAssertEqual(err, 0, "setvbuf faild \(err)")
  454. let readFD = fds[0]
  455. err = fcntl(readFD, F_SETFL, fcntl(readFD, F_GETFL) | O_NONBLOCK)
  456. XCTAssertEqual(err, 0, "fcntl faild \(err)")
  457. let readBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: 256)
  458. defer {
  459. readBuffer.deinitialize(count: 256)
  460. readBuffer.deallocate()
  461. }
  462. // the actual test
  463. body(writeFD!, readFD, readBuffer)
  464. fds.forEach { close($0) }
  465. }
  466. func testOverloadingError() {
  467. struct Dummy: Error, LocalizedError {
  468. var errorDescription: String? {
  469. return "errorDescription"
  470. }
  471. }
  472. // bootstrap with our test logging impl
  473. let logging = TestLogging()
  474. LoggingSystem.bootstrapInternal(logging.make)
  475. var logger = Logger(label: "test")
  476. logger.logLevel = .error
  477. logger.error(Dummy())
  478. logging.history.assertExist(level: .error, message: "errorDescription")
  479. }
  480. }
  481. extension Logger {
  482. public func error(_ error: Error,
  483. metadata: @autoclosure () -> Logger.Metadata? = nil,
  484. file: String = #file, function: String = #function, line: UInt = #line) {
  485. self.error("\(error.localizedDescription)", metadata: metadata(), file: file, function: function, line: line)
  486. }
  487. }