DDLog+Combine.swift 4.0 KB

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