CapturingLogHandler.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /*
  2. * Copyright 2020, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import Logging
  17. import NIOConcurrencyHelpers
  18. import struct Foundation.Date
  19. import class Foundation.DateFormatter
  20. /// A `LogHandler` factory which captures all logs emitted by the handlers it makes.
  21. internal class CapturingLogHandlerFactory {
  22. private var lock = NIOLock()
  23. private var _logs: [CapturedLog] = []
  24. private var logFormatter: CapturedLogFormatter?
  25. init(printWhenCaptured: Bool) {
  26. if printWhenCaptured {
  27. self.logFormatter = CapturedLogFormatter()
  28. } else {
  29. self.logFormatter = nil
  30. }
  31. }
  32. /// Returns all captured logs and empties the store of captured logs.
  33. func clearCapturedLogs() -> [CapturedLog] {
  34. return self.lock.withLock {
  35. let logs = self._logs
  36. self._logs.removeAll()
  37. return logs
  38. }
  39. }
  40. /// Make a `LogHandler` whose logs will be recorded by this factory.
  41. func make(_ label: String) -> LogHandler {
  42. return CapturingLogHandler(label: label) { log in
  43. self.lock.withLock {
  44. self._logs.append(log)
  45. }
  46. // If we have a formatter, print the log as well.
  47. if let formatter = self.logFormatter {
  48. print(formatter.string(for: log))
  49. }
  50. }
  51. }
  52. }
  53. /// A captured log.
  54. internal struct CapturedLog {
  55. var label: String
  56. var level: Logger.Level
  57. var message: Logger.Message
  58. var metadata: Logger.Metadata
  59. var source: String
  60. var file: String
  61. var function: String
  62. var line: UInt
  63. var date: Date
  64. }
  65. /// A log handler which captures all logs it records.
  66. internal struct CapturingLogHandler: LogHandler {
  67. private let capture: (CapturedLog) -> Void
  68. internal let label: String
  69. internal var metadata: Logger.Metadata = [:]
  70. internal var logLevel: Logger.Level = .trace
  71. fileprivate init(label: String, capture: @escaping (CapturedLog) -> Void) {
  72. self.label = label
  73. self.capture = capture
  74. }
  75. internal func log(
  76. level: Logger.Level,
  77. message: Logger.Message,
  78. metadata: Logger.Metadata?,
  79. source: String,
  80. file: String,
  81. function: String,
  82. line: UInt
  83. ) {
  84. let merged: Logger.Metadata
  85. if let metadata = metadata {
  86. merged = self.metadata.merging(metadata, uniquingKeysWith: { _, new in new })
  87. } else {
  88. merged = self.metadata
  89. }
  90. let log = CapturedLog(
  91. label: self.label,
  92. level: level,
  93. message: message,
  94. metadata: merged,
  95. source: source,
  96. file: file,
  97. function: function,
  98. line: line,
  99. date: Date()
  100. )
  101. self.capture(log)
  102. }
  103. internal subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
  104. get {
  105. return self.metadata[metadataKey]
  106. }
  107. set {
  108. self.metadata[metadataKey] = newValue
  109. }
  110. }
  111. }
  112. struct CapturedLogFormatter {
  113. private var dateFormatter: DateFormatter
  114. init() {
  115. self.dateFormatter = DateFormatter()
  116. // We don't care about the date.
  117. self.dateFormatter.dateFormat = "HH:mm:ss.SSS"
  118. }
  119. func string(for log: CapturedLog) -> String {
  120. let date = self.dateFormatter.string(from: log.date)
  121. let level = log.level.short
  122. // Format the metadata.
  123. let formattedMetadata = log.metadata
  124. .sorted(by: { $0.key < $1.key })
  125. .map { key, value in "\(key)=\(value)" }
  126. .joined(separator: " ")
  127. return "\(date) \(level) \(log.label): \(log.message) { \(formattedMetadata) }"
  128. }
  129. }
  130. extension Logger.Level {
  131. fileprivate var short: String {
  132. switch self {
  133. case .info:
  134. return "I"
  135. case .debug:
  136. return "D"
  137. case .warning:
  138. return "W"
  139. case .error:
  140. return "E"
  141. case .critical:
  142. return "C"
  143. case .trace:
  144. return "T"
  145. case .notice:
  146. return "N"
  147. }
  148. }
  149. }