DDLog+Combine.swift 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2024, 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. #if arch(arm64) || arch(x86_64)
  16. #if canImport(Combine)
  17. import Combine
  18. #if SWIFT_PACKAGE
  19. import CocoaLumberjack
  20. import CocoaLumberjackSwiftSupport
  21. #endif
  22. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
  23. extension DDLog {
  24. // MARK: - Subscription
  25. private final class Subscription<S: Subscriber>: NSObject, DDLogger, Combine.Subscription
  26. where S.Input == DDLogMessage
  27. { // swiftlint:disable:this opening_brace
  28. private var subscriber: S?
  29. private weak var log: DDLog?
  30. /// Not used but `DDLogger` requires it.
  31. /// The preferred way to achieve this is to use the `map` Combine operator of the publisher.
  32. /// Example:
  33. /// ```
  34. /// DDLog.sharedInstance.messagePublisher()
  35. /// .map { message in /* format message */ }
  36. /// .sink(receiveValue: { formattedMessage in /* process formattedMessage */ })
  37. /// ```
  38. #if compiler(>=5.7)
  39. var logFormatter: (any DDLogFormatter)?
  40. #else
  41. var logFormatter: DDLogFormatter?
  42. #endif
  43. init(log: DDLog, with logLevel: DDLogLevel, subscriber: S) {
  44. self.subscriber = subscriber
  45. self.log = log
  46. super.init()
  47. log.add(self, with: logLevel)
  48. }
  49. func request(_ demand: Subscribers.Demand) {
  50. // The log messages are endless until canceled, so we won't do any demand management.
  51. // Combine operators can be used to deal with it as needed.
  52. }
  53. func cancel() {
  54. log?.remove(self)
  55. subscriber = nil
  56. }
  57. func log(message logMessage: DDLogMessage) {
  58. _ = subscriber?.receive(logMessage)
  59. }
  60. }
  61. // MARK: - Publisher
  62. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
  63. public struct MessagePublisher: Combine.Publisher {
  64. public typealias Output = DDLogMessage
  65. public typealias Failure = Never
  66. private let log: DDLog
  67. private let logLevel: DDLogLevel
  68. public init(log: DDLog, with logLevel: DDLogLevel) {
  69. self.log = log
  70. self.logLevel = logLevel
  71. }
  72. public func receive<S>(subscriber: S)
  73. where S: Subscriber, S.Failure == Failure, S.Input == Output
  74. { // swiftlint:disable:this opening_brace
  75. let subscription = Subscription(log: log, with: logLevel, subscriber: subscriber)
  76. subscriber.receive(subscription: subscription)
  77. }
  78. }
  79. /**
  80. * Creates a message publisher.
  81. *
  82. * The publisher will add and remove loggers as subscriptions are added and removed.
  83. *
  84. * The level that you provide here is a preemptive filter (for performance).
  85. * That is, the level specified here will be used to filter out logMessages so that
  86. * the logger is never even invoked for the messages.
  87. *
  88. * More information:
  89. * See -[DDLog addLogger:with:]
  90. *
  91. * - Parameter logLevel: preemptive filter of the message returned by the publisher. All levels are sent by default
  92. * - Returns: A MessagePublisher that emits LogMessages filtered by the specified logLevel
  93. **/
  94. public func messagePublisher(with logLevel: DDLogLevel = .all) -> MessagePublisher {
  95. MessagePublisher(log: self, with: logLevel)
  96. }
  97. }
  98. @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
  99. extension Publisher where Output == DDLogMessage {
  100. #if compiler(>=5.7)
  101. public func formatted(with formatter: any DDLogFormatter) -> Publishers.CompactMap<Self, String> {
  102. compactMap { formatter.format(message: $0) }
  103. }
  104. #else
  105. public func formatted(with formatter: DDLogFormatter) -> Publishers.CompactMap<Self, String> {
  106. compactMap { formatter.format(message: $0) }
  107. }
  108. #endif
  109. }
  110. #endif
  111. #endif