DDLogHandler.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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. public import CocoaLumberjack
  16. public import Logging
  17. /// A swift-log ``LogHandler`` implementation that forwards messages to a given ``DDLog`` instance.
  18. public struct DDLogHandler: LogHandler {
  19. @usableFromInline
  20. struct Configuration: Sendable {
  21. @usableFromInline
  22. struct SyncLogging: Sendable {
  23. @usableFromInline
  24. let tresholdLevel: Logging.Logger.Level
  25. @usableFromInline
  26. let metadataKey: Logging.Logger.Metadata.Key
  27. }
  28. @usableFromInline
  29. let log: DDLog
  30. @usableFromInline
  31. let syncLogging: SyncLogging
  32. }
  33. @usableFromInline
  34. struct LoggerInfo: Sendable {
  35. @usableFromInline
  36. struct MetadataSources: Sendable {
  37. @usableFromInline
  38. var provider: Logging.Logger.MetadataProvider?
  39. @usableFromInline
  40. var logger: Logging.Logger.Metadata = [:]
  41. }
  42. @usableFromInline
  43. let label: String
  44. @usableFromInline
  45. var logLevel: Logging.Logger.Level
  46. @usableFromInline
  47. var metadataSources: MetadataSources
  48. // Not removed due to `@usableFromInline`
  49. @usableFromInline
  50. @available(*, deprecated, renamed: "metadataSources.logger")
  51. var metadata: Logging.Logger.Metadata {
  52. get { metadataSources.logger }
  53. set { metadataSources.logger = newValue }
  54. }
  55. }
  56. @usableFromInline
  57. let config: Configuration
  58. @usableFromInline
  59. var loggerInfo: LoggerInfo
  60. @inlinable
  61. public var logLevel: Logging.Logger.Level {
  62. get { loggerInfo.logLevel }
  63. set { loggerInfo.logLevel = newValue }
  64. }
  65. @inlinable
  66. public var metadataProvider: Logging.Logger.MetadataProvider? {
  67. get { loggerInfo.metadataSources.provider }
  68. set { loggerInfo.metadataSources.provider = newValue }
  69. }
  70. @inlinable
  71. public var metadata: Logging.Logger.Metadata {
  72. get { loggerInfo.metadataSources.logger }
  73. set { loggerInfo.metadataSources.logger = newValue }
  74. }
  75. @inlinable
  76. public subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? {
  77. get { metadata[metadataKey] }
  78. set { metadata[metadataKey] = newValue }
  79. }
  80. private init(config: Configuration, loggerInfo: LoggerInfo) {
  81. self.config = config
  82. self.loggerInfo = loggerInfo
  83. }
  84. /// Returns whether a message with the given level and the given metadata should be logged asynchronous.
  85. /// - Parameters:
  86. /// - level: The level at which the message was logged.
  87. /// - metadata: The metadata associated with the message.
  88. /// - Returns: Whether to log the message asynchronous.
  89. @usableFromInline
  90. func _logAsync(level: Logging.Logger.Level, metadata: Logging.Logger.Metadata?) -> Bool {
  91. if level >= config.syncLogging.tresholdLevel {
  92. // Easiest check -> level is above treshold. Not async.
  93. return false
  94. } else if case .stringConvertible(let logSynchronous as Bool) = metadata?[config.syncLogging.metadataKey] {
  95. // There's a metadata value, return it's value. We need to invert it since it defines whether to log _synchronous_.
  96. return !logSynchronous
  97. } else {
  98. // If we're below the treshold and no metadata value is set -> we're logging async.
  99. return true
  100. }
  101. }
  102. @inlinable
  103. public func log(level: Logging.Logger.Level,
  104. message: Logging.Logger.Message,
  105. metadata: Logging.Logger.Metadata?,
  106. source: String,
  107. file: String,
  108. function: String,
  109. line: UInt) {
  110. let slMessage = SwiftLogMessage(loggerLabel: loggerInfo.label,
  111. loggerMetadata: loggerInfo.metadataSources.logger,
  112. loggerProvidedMetadata: loggerInfo.metadataSources.provider?.get(),
  113. message: message,
  114. level: level,
  115. metadata: metadata,
  116. source: source,
  117. file: file,
  118. function: function,
  119. line: line)
  120. config.log.log(asynchronous: _logAsync(level: level, metadata: metadata), message: slMessage)
  121. }
  122. }
  123. /// A typealias for the "old" log handler factory.
  124. @preconcurrency
  125. public typealias OldLogHandlerFactory = @Sendable (String) -> any LogHandler
  126. /// A typealias for the log handler factory.
  127. @preconcurrency
  128. public typealias LogHandlerFactory = @Sendable (String, Logging.Logger.MetadataProvider?) -> any LogHandler
  129. extension DDLogHandler {
  130. /// The default key to control per message whether to log it synchronous or asynchronous.
  131. public static var defaultSynchronousLoggingMetadataKey: Logging.Logger.Metadata.Key {
  132. "log-synchronous"
  133. }
  134. /// Creates a new ``LogHandler`` factory using ``DDLogHandler`` with the given parameters.
  135. /// - Parameters:
  136. /// - log: The ``DDLog`` instance to use for logging. Defaults to ``DDLog/sharedInstance``.
  137. /// - defaultLogLevel: The default log level for new loggers. Defaults to ``Logging/Logger/Level/info``.
  138. /// - syncLoggingTreshold: The level as of which log messages should be logged synchronously instead of asynchronously. Defaults to ``Logging/Logger/Level/error``.
  139. /// - synchronousLoggingMetadataKey: The metadata key to check on messages to decide whether to log synchronous or asynchronous. Defaults to ``DDLogHandler/defaultSynchronousLoggingMetadataKey``.
  140. /// - Returns: A new ``LogHandler`` factory using ``DDLogHandler`` that can be passed to ``LoggingSystem/bootstrap``.
  141. /// - SeeAlso: ``DDLog``, ``LoggingSystem/boostrap``
  142. public static func handlerFactory(
  143. for log: DDLog = .sharedInstance,
  144. defaultLogLevel: Logging.Logger.Level = .info,
  145. loggingSynchronousAsOf syncLoggingTreshold: Logging.Logger.Level = .error,
  146. synchronousLoggingMetadataKey: Logging.Logger.Metadata.Key = DDLogHandler.defaultSynchronousLoggingMetadataKey
  147. ) -> LogHandlerFactory {
  148. let config = DDLogHandler.Configuration(
  149. log: log,
  150. syncLogging: .init(tresholdLevel: syncLoggingTreshold,
  151. metadataKey: synchronousLoggingMetadataKey)
  152. )
  153. return {
  154. DDLogHandler(config: config, loggerInfo: .init(label: $0, logLevel: defaultLogLevel, metadataSources: .init(provider: $1)))
  155. }
  156. }
  157. /// Creates a new ``LogHandler`` factory using ``DDLogHandler`` with the given parameters.
  158. /// - Parameters:
  159. /// - log: The ``DDLog`` instance to use for logging. Defaults to ``DDLog/sharedInstance``.
  160. /// - defaultLogLevel: The default log level for new loggers. Defaults to ``Logging/Logger/Level/info``.
  161. /// - syncLoggingTreshold: The level as of which log messages should be logged synchronously instead of asynchronously. Defaults to ``Logging/Logger/Level/error``.
  162. /// - synchronousLoggingMetadataKey: The metadata key to check on messages to decide whether to log synchronous or asynchronous. Defaults to ``DDLogHandler/defaultSynchronousLoggingMetadataKey``.
  163. /// - Returns: A new ``LogHandler`` factory using ``DDLogHandler`` that can be passed to ``LoggingSystem/bootstrap``.
  164. /// - SeeAlso: ``DDLog``, ``LoggingSystem/boostrap``
  165. @inlinable
  166. public static func handlerFactory(
  167. for log: DDLog = .sharedInstance,
  168. defaultLogLevel: Logging.Logger.Level = .info,
  169. loggingSynchronousAsOf syncLoggingTreshold: Logging.Logger.Level = .error,
  170. synchronousLoggingMetadataKey: Logging.Logger.Metadata.Key = DDLogHandler.defaultSynchronousLoggingMetadataKey
  171. ) -> OldLogHandlerFactory {
  172. let factory: LogHandlerFactory = handlerFactory(
  173. for: log,
  174. defaultLogLevel: defaultLogLevel,
  175. loggingSynchronousAsOf: syncLoggingTreshold,
  176. synchronousLoggingMetadataKey: synchronousLoggingMetadataKey
  177. )
  178. return { factory($0, nil) }
  179. }
  180. }
  181. extension LoggingSystem {
  182. /// Bootraps the logging system with a new ``LogHandler`` factory using ``DDLogHandler``.
  183. /// - Parameters:
  184. /// - log: The ``DDLog`` instance to use for logging. Defaults to ``DDLog/sharedInstance``.
  185. /// - defaultLogLevel: The default log level for new loggers. Defaults to ``Logging/Logger/Level/info``.
  186. /// - syncLoggingTreshold: The level as of which log messages should be logged synchronously instead of asynchronously. Defaults to ``Logging/Logger/Level/error``.
  187. /// - synchronousLoggingMetadataKey: The metadata key to check on messages to decide whether to log synchronous or asynchronous. Defaults to ``DDLogHandler/defaultSynchronousLoggingMetadataKey``.
  188. /// - metadataProvider: The (global) metadata provider to use with the setup. Defaults to `nil`.
  189. /// - SeeAlso: ``DDLogHandler/handlerFactory``, ``LoggingSystem/bootstrap``
  190. @inlinable
  191. public static func bootstrapWithCocoaLumberjack(
  192. for log: DDLog = .sharedInstance,
  193. defaultLogLevel: Logging.Logger.Level = .info,
  194. loggingSynchronousAsOf syncLoggingTreshold: Logging.Logger.Level = .error,
  195. synchronousLoggingMetadataKey: Logging.Logger.Metadata.Key = DDLogHandler.defaultSynchronousLoggingMetadataKey,
  196. metadataProvider: Logging.Logger.MetadataProvider? = nil
  197. ) {
  198. bootstrap(DDLogHandler.handlerFactory(for: log,
  199. defaultLogLevel: defaultLogLevel,
  200. loggingSynchronousAsOf: syncLoggingTreshold,
  201. synchronousLoggingMetadataKey: synchronousLoggingMetadataKey),
  202. metadataProvider: metadataProvider)
  203. }
  204. }