TestLogger.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. import Foundation
  15. @testable import Logging
  16. import XCTest
  17. #if os(Windows)
  18. import WinSDK
  19. #endif
  20. internal struct TestLogging {
  21. private let _config = Config() // shared among loggers
  22. private let recorder = Recorder() // shared among loggers
  23. func make(label: String) -> LogHandler {
  24. return TestLogHandler(label: label, config: self.config, recorder: self.recorder)
  25. }
  26. var config: Config { return self._config }
  27. var history: History { return self.recorder }
  28. }
  29. internal struct TestLogHandler: LogHandler {
  30. private let recorder: Recorder
  31. private let config: Config
  32. private var logger: Logger // the actual logger
  33. let label: String
  34. init(label: String, config: Config, recorder: Recorder) {
  35. self.label = label
  36. self.config = config
  37. self.recorder = recorder
  38. self.logger = Logger(label: "test", StreamLogHandler.standardOutput(label: label))
  39. self.logger.logLevel = .debug
  40. }
  41. func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, source: String, file: String, function: String, line: UInt) {
  42. let metadata = (self._metadataSet ? self.metadata : MDC.global.metadata).merging(metadata ?? [:], uniquingKeysWith: { _, new in new })
  43. self.logger.log(level: level, message, metadata: metadata, source: source, file: file, function: function, line: line)
  44. self.recorder.record(level: level, metadata: metadata, message: message, source: source)
  45. }
  46. private var _logLevel: Logger.Level?
  47. var logLevel: Logger.Level {
  48. get {
  49. // get from config unless set
  50. return self._logLevel ?? self.config.get(key: self.label)
  51. }
  52. set {
  53. self._logLevel = newValue
  54. }
  55. }
  56. private var _metadataSet = false
  57. private var _metadata = Logger.Metadata() {
  58. didSet {
  59. self._metadataSet = true
  60. }
  61. }
  62. public var metadata: Logger.Metadata {
  63. get {
  64. return self._metadata
  65. }
  66. set {
  67. self._metadata = newValue
  68. }
  69. }
  70. // TODO: would be nice to delegate to local copy of logger but StdoutLogger is a reference type. why?
  71. subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
  72. get {
  73. return self._metadata[metadataKey]
  74. }
  75. set {
  76. self._metadata[metadataKey] = newValue
  77. }
  78. }
  79. }
  80. internal class Config {
  81. private static let ALL = "*"
  82. private let lock = NSLock()
  83. private var storage = [String: Logger.Level]()
  84. func get(key: String) -> Logger.Level {
  85. return self.get(key) ?? self.get(Config.ALL) ?? Logger.Level.debug
  86. }
  87. func get(_ key: String) -> Logger.Level? {
  88. guard let value = (self.lock.withLock { self.storage[key] }) else {
  89. return nil
  90. }
  91. return value
  92. }
  93. func set(key: String = Config.ALL, value: Logger.Level) {
  94. self.lock.withLock { self.storage[key] = value }
  95. }
  96. func clear() {
  97. self.lock.withLock { self.storage.removeAll() }
  98. }
  99. }
  100. internal class Recorder: History {
  101. private let lock = NSLock()
  102. private var _entries = [LogEntry]()
  103. func record(level: Logger.Level, metadata: Logger.Metadata?, message: Logger.Message, source: String) {
  104. return self.lock.withLock {
  105. self._entries.append(LogEntry(level: level, metadata: metadata, message: message.description, source: source))
  106. }
  107. }
  108. var entries: [LogEntry] {
  109. return self.lock.withLock { self._entries }
  110. }
  111. }
  112. internal protocol History {
  113. var entries: [LogEntry] { get }
  114. }
  115. internal extension History {
  116. func atLevel(level: Logger.Level) -> [LogEntry] {
  117. return self.entries.filter { entry in
  118. level == entry.level
  119. }
  120. }
  121. var trace: [LogEntry] {
  122. return self.atLevel(level: .debug)
  123. }
  124. var debug: [LogEntry] {
  125. return self.atLevel(level: .debug)
  126. }
  127. var info: [LogEntry] {
  128. return self.atLevel(level: .info)
  129. }
  130. var warning: [LogEntry] {
  131. return self.atLevel(level: .warning)
  132. }
  133. var error: [LogEntry] {
  134. return self.atLevel(level: .error)
  135. }
  136. }
  137. internal struct LogEntry {
  138. let level: Logger.Level
  139. let metadata: Logger.Metadata?
  140. let message: String
  141. let source: String
  142. }
  143. extension History {
  144. #if compiler(>=5.3)
  145. func assertExist(level: Logger.Level,
  146. message: String,
  147. metadata: Logger.Metadata? = nil,
  148. source: String? = nil,
  149. file: StaticString = #file,
  150. fileID: String = #fileID,
  151. line: UInt = #line) {
  152. let source = source ?? Logger.currentModule(fileID: "\(fileID)")
  153. let entry = self.find(level: level, message: message, metadata: metadata, source: source)
  154. XCTAssertNotNil(entry, "entry not found: \(level), \(source), \(String(describing: metadata)), \(message)",
  155. file: file, line: line)
  156. }
  157. func assertNotExist(level: Logger.Level,
  158. message: String,
  159. metadata: Logger.Metadata? = nil,
  160. source: String? = nil,
  161. file: StaticString = #file,
  162. fileID: String = #file,
  163. line: UInt = #line) {
  164. let source = source ?? Logger.currentModule(fileID: "\(fileID)")
  165. let entry = self.find(level: level, message: message, metadata: metadata, source: source)
  166. XCTAssertNil(entry, "entry was found: \(level), \(source), \(String(describing: metadata)), \(message)",
  167. file: file, line: line)
  168. }
  169. #else
  170. func assertExist(level: Logger.Level,
  171. message: String,
  172. metadata: Logger.Metadata? = nil,
  173. source: String? = nil,
  174. file: StaticString = #file,
  175. line: UInt = #line) {
  176. let source = source ?? Logger.currentModule(filePath: "\(file)")
  177. let entry = self.find(level: level, message: message, metadata: metadata, source: source)
  178. XCTAssertNotNil(entry, "entry not found: \(level), \(source), \(String(describing: metadata)), \(message)",
  179. file: file, line: line)
  180. }
  181. func assertNotExist(level: Logger.Level,
  182. message: String,
  183. metadata: Logger.Metadata? = nil,
  184. source: String? = nil,
  185. file: StaticString = #file,
  186. line: UInt = #line) {
  187. let source = source ?? Logger.currentModule(filePath: "\(file)")
  188. let entry = self.find(level: level, message: message, metadata: metadata, source: source)
  189. XCTAssertNil(entry, "entry was found: \(level), \(source), \(String(describing: metadata)), \(message)",
  190. file: file, line: line)
  191. }
  192. #endif
  193. func find(level: Logger.Level, message: String, metadata: Logger.Metadata? = nil, source: String) -> LogEntry? {
  194. return self.entries.first { entry in
  195. entry.level == level &&
  196. entry.message == message &&
  197. entry.metadata ?? [:] == metadata ?? [:] &&
  198. entry.source == source
  199. }
  200. }
  201. }
  202. public class MDC {
  203. private let lock = NSLock()
  204. private var storage = [Int: Logger.Metadata]()
  205. public static var global = MDC()
  206. private init() {}
  207. public subscript(metadataKey: String) -> Logger.Metadata.Value? {
  208. get {
  209. return self.lock.withLock {
  210. self.storage[self.threadId]?[metadataKey]
  211. }
  212. }
  213. set {
  214. self.lock.withLock {
  215. if self.storage[self.threadId] == nil {
  216. self.storage[self.threadId] = Logger.Metadata()
  217. }
  218. self.storage[self.threadId]![metadataKey] = newValue
  219. }
  220. }
  221. }
  222. public var metadata: Logger.Metadata {
  223. return self.lock.withLock {
  224. self.storage[self.threadId] ?? [:]
  225. }
  226. }
  227. public func clear() {
  228. self.lock.withLock {
  229. _ = self.storage.removeValue(forKey: self.threadId)
  230. }
  231. }
  232. public func with(metadata: Logger.Metadata, _ body: () throws -> Void) rethrows {
  233. metadata.forEach { self[$0] = $1 }
  234. defer {
  235. metadata.keys.forEach { self[$0] = nil }
  236. }
  237. try body()
  238. }
  239. public func with<T>(metadata: Logger.Metadata, _ body: () throws -> T) rethrows -> T {
  240. metadata.forEach { self[$0] = $1 }
  241. defer {
  242. metadata.keys.forEach { self[$0] = nil }
  243. }
  244. return try body()
  245. }
  246. // for testing
  247. internal func flush() {
  248. self.lock.withLock {
  249. self.storage.removeAll()
  250. }
  251. }
  252. private var threadId: Int {
  253. #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
  254. return Int(pthread_mach_thread_np(pthread_self()))
  255. #elseif os(Windows)
  256. return Int(GetCurrentThreadId())
  257. #else
  258. return Int(pthread_self())
  259. #endif
  260. }
  261. }
  262. internal extension NSLock {
  263. func withLock<T>(_ body: () -> T) -> T {
  264. self.lock()
  265. defer {
  266. self.unlock()
  267. }
  268. return body()
  269. }
  270. }
  271. internal struct TestLibrary {
  272. private let logger = Logger(label: "TestLibrary")
  273. private let queue = DispatchQueue(label: "TestLibrary")
  274. public init() {}
  275. public func doSomething() {
  276. self.logger.info("TestLibrary::doSomething")
  277. }
  278. public func doSomethingAsync(completion: @escaping () -> Void) {
  279. // libraries that use global loggers and async, need to make sure they propagate the
  280. // logging metadata when creating a new thread
  281. let metadata = MDC.global.metadata
  282. self.queue.asyncAfter(deadline: .now() + 0.1) {
  283. MDC.global.with(metadata: metadata) {
  284. self.logger.info("TestLibrary::doSomethingAsync")
  285. completion()
  286. }
  287. }
  288. }
  289. }
  290. // Sendable
  291. #if compiler(>=5.6)
  292. extension TestLogHandler: @unchecked Sendable {}
  293. extension Recorder: @unchecked Sendable {}
  294. extension Config: @unchecked Sendable {}
  295. extension MDC: @unchecked Sendable {}
  296. #endif