|
13 | 13 | //===----------------------------------------------------------------------===// |
14 | 14 |
|
15 | 15 | import Crdkafka |
| 16 | +import Logging |
16 | 17 |
|
17 | 18 | /// A collection of helper functions wrapping common `rd_kafka_conf_*` functions in Swift. |
18 | 19 | struct RDKafkaConfig { |
19 | 20 | typealias KafkaAcknowledgementResult = Result<KafkaAcknowledgedMessage, KafkaAcknowledgedMessageError> |
20 | 21 | /// Wraps a Swift closure inside of a class to be able to pass it to `librdkafka` as an `OpaquePointer`. |
21 | | - final class CapturedClosure { |
22 | | - typealias Closure = (KafkaAcknowledgementResult?) -> Void |
23 | | - let closure: Closure |
| 22 | + final class CapturedClosures { |
| 23 | + typealias DeliveryReportClosure = (KafkaAcknowledgementResult?) -> Void |
| 24 | + var deliveryReportClosure: DeliveryReportClosure? |
24 | 25 |
|
25 | | - init(_ closure: @escaping Closure) { |
26 | | - self.closure = closure |
27 | | - } |
| 26 | + typealias LoggingClosure = (Int32, UnsafePointer<CChar>, UnsafePointer<CChar>) -> Void |
| 27 | + var loggingClosure: LoggingClosure? |
| 28 | + |
| 29 | + init() { } |
28 | 30 | } |
29 | 31 |
|
30 | 32 | /// Create a new `rd_kafka_conf_t` object in memory and initialize it with the given configuration properties. |
@@ -63,46 +65,119 @@ struct RDKafkaConfig { |
63 | 65 | } |
64 | 66 | } |
65 | 67 |
|
66 | | - /// A Swift wrapper for `rd_kafka_conf_set_dr_msg_cb`. |
67 | | - /// Defines a function that is called upon every message acknowledgement. |
| 68 | + /// Registers passed closures as callbacks and sets the application's opaque pointer that will be passed to callbacks |
| 69 | + /// - Parameter type: Kafka client type: `Consumer` or `Producer` |
68 | 70 | /// - Parameter configPointer: An `OpaquePointer` pointing to the `rd_kafka_conf_t` object in memory. |
69 | | - /// - Parameter callback: A closure that is invoked upon message acknowledgement. |
70 | | - /// - Returns: A ``CapturedClosure`` object that must me retained by the caller as long as acknowledgements are received. |
71 | | - static func setDeliveryReportCallback( |
| 71 | + /// - Parameter deliveryReportCallback: A closure that is invoked upon message acknowledgement. |
| 72 | + /// - Parameter logger: Logger instance |
| 73 | + /// - Returns: A ``CapturedClosures`` object that must me retained by the caller as long as it exists. |
| 74 | + static func setCallbackClosures( |
72 | 75 | configPointer: OpaquePointer, |
73 | | - _ callback: @escaping ((KafkaAcknowledgementResult?) -> Void) |
74 | | - ) -> CapturedClosure { |
75 | | - let capturedClosure = CapturedClosure(callback) |
76 | | - // Pass the captured closure to the C closure as an opaque object |
77 | | - let opaquePointer: UnsafeMutableRawPointer? = Unmanaged.passUnretained(capturedClosure).toOpaque() |
| 76 | + deliveryReportCallback: CapturedClosures.DeliveryReportClosure? = nil, |
| 77 | + logger: Logger |
| 78 | + ) -> CapturedClosures { |
| 79 | + let closures = CapturedClosures() |
| 80 | + |
| 81 | + // Pass the the reference to Opaque as an opaque object |
| 82 | + let opaquePointer: UnsafeMutableRawPointer? = Unmanaged.passUnretained(closures).toOpaque() |
78 | 83 | rd_kafka_conf_set_opaque( |
79 | 84 | configPointer, |
80 | 85 | opaquePointer |
81 | 86 | ) |
82 | 87 |
|
| 88 | + // Set delivery report callback |
| 89 | + if let deliveryReportCallback { |
| 90 | + Self.setDeliveryReportCallback(configPointer: configPointer, capturedClosures: closures, deliveryReportCallback) |
| 91 | + } |
| 92 | + // Set logging callback |
| 93 | + Self.setLoggingCallback(configPointer: configPointer, capturedClosures: closures, logger: logger) |
| 94 | + |
| 95 | + return closures |
| 96 | + } |
| 97 | + |
| 98 | + /// A Swift wrapper for `rd_kafka_conf_set_dr_msg_cb`. |
| 99 | + /// Defines a function that is called upon every message acknowledgement. |
| 100 | + /// - Parameter configPointer: An `OpaquePointer` pointing to the `rd_kafka_conf_t` object in memory. |
| 101 | + /// - Parameter callback: A closure that is invoked upon message acknowledgement. |
| 102 | + private static func setDeliveryReportCallback( |
| 103 | + configPointer: OpaquePointer, |
| 104 | + capturedClosures: CapturedClosures, |
| 105 | + _ deliveryReportCallback: @escaping RDKafkaConfig.CapturedClosures.DeliveryReportClosure |
| 106 | + ) { |
| 107 | + capturedClosures.deliveryReportClosure = deliveryReportCallback |
| 108 | + |
83 | 109 | // Create a C closure that calls the captured closure |
84 | 110 | let callbackWrapper: ( |
85 | 111 | @convention(c) (OpaquePointer?, UnsafePointer<rd_kafka_message_t>?, UnsafeMutableRawPointer?) -> Void |
86 | 112 | ) = { _, messagePointer, opaquePointer in |
87 | 113 | guard let opaquePointer = opaquePointer else { |
88 | | - fatalError("Could not resolve reference to KafkaProducer instance") |
| 114 | + fatalError("Could not resolve reference to CapturedClosures") |
89 | 115 | } |
90 | | - let opaque = Unmanaged<CapturedClosure>.fromOpaque(opaquePointer).takeUnretainedValue() |
| 116 | + let closures = Unmanaged<CapturedClosures>.fromOpaque(opaquePointer).takeUnretainedValue() |
91 | 117 |
|
92 | | - let actualCallback = opaque.closure |
| 118 | + guard let actualCallback = closures.deliveryReportClosure else { |
| 119 | + fatalError("Delivery report callback is set, but user closure is not defined") |
| 120 | + } |
93 | 121 | let messageResult = Self.convertMessageToAcknowledgementResult(messagePointer: messagePointer) |
94 | 122 | actualCallback(messageResult) |
95 | | - |
96 | | - // The messagePointer is automatically destroyed by librdkafka |
97 | | - // For safety reasons, we only use it inside of this callback |
98 | 123 | } |
99 | 124 |
|
100 | 125 | rd_kafka_conf_set_dr_msg_cb( |
101 | 126 | configPointer, |
102 | 127 | callbackWrapper |
103 | 128 | ) |
| 129 | + } |
| 130 | + |
| 131 | + /// A Swift wrapper for `rd_kafka_conf_set_log_cb`. |
| 132 | + /// Defines a function that is called upon every log and redirects output to ``logger``. |
| 133 | + /// - Parameter configPointer: An `OpaquePointer` pointing to the `rd_kafka_conf_t` object in memory. |
| 134 | + /// - Parameter logger: Logger instance |
| 135 | + private static func setLoggingCallback( |
| 136 | + configPointer: OpaquePointer, |
| 137 | + capturedClosures: CapturedClosures, |
| 138 | + logger: Logger |
| 139 | + ) { |
| 140 | + let loggingClosure: RDKafkaConfig.CapturedClosures.LoggingClosure = { level, fac, buf in |
| 141 | + // Mapping according to https://en.wikipedia.org/wiki/Syslog |
| 142 | + switch level { |
| 143 | + case 0 ... 2: /* Emergency, Alert, Critical */ |
| 144 | + logger.critical(Logger.Message(stringLiteral: String(cString: buf)), source: String(cString: fac)) |
| 145 | + case 3: /* Error */ |
| 146 | + logger.error(Logger.Message(stringLiteral: String(cString: buf)), source: String(cString: fac)) |
| 147 | + case 4: /* Warning */ |
| 148 | + logger.warning(Logger.Message(stringLiteral: String(cString: buf)), source: String(cString: fac)) |
| 149 | + case 5: /* Notice */ |
| 150 | + logger.notice(Logger.Message(stringLiteral: String(cString: buf)), source: String(cString: fac)) |
| 151 | + case 6: /* Informational */ |
| 152 | + logger.info(Logger.Message(stringLiteral: String(cString: buf)), source: String(cString: fac)) |
| 153 | + default: /* Debug */ |
| 154 | + logger.debug(Logger.Message(stringLiteral: String(cString: buf)), source: String(cString: fac)) |
| 155 | + } |
| 156 | + } |
| 157 | + capturedClosures.loggingClosure = loggingClosure |
| 158 | + |
| 159 | + let loggingWrapper: ( |
| 160 | + @convention(c) (OpaquePointer?, Int32, UnsafePointer<CChar>?, UnsafePointer<CChar>?) -> Void |
| 161 | + ) = { rkKafkaT, level, fac, buf in |
| 162 | + guard let fac, let buf else { |
| 163 | + return |
| 164 | + } |
| 165 | + |
| 166 | + guard let opaquePointer = rd_kafka_opaque(rkKafkaT) else { |
| 167 | + fatalError("Could not resolve reference to CapturedClosures") |
| 168 | + } |
| 169 | + let opaque = Unmanaged<CapturedClosures>.fromOpaque(opaquePointer).takeUnretainedValue() |
| 170 | + |
| 171 | + guard let closure = opaque.loggingClosure else { |
| 172 | + fatalError("Could not resolve logger instance") |
| 173 | + } |
| 174 | + closure(level, fac, buf) |
| 175 | + } |
104 | 176 |
|
105 | | - return capturedClosure |
| 177 | + rd_kafka_conf_set_log_cb( |
| 178 | + configPointer, |
| 179 | + loggingWrapper |
| 180 | + ) |
106 | 181 | } |
107 | 182 |
|
108 | 183 | /// Convert an unsafe`rd_kafka_message_t` object to a safe ``KafkaAcknowledgementResult``. |
|
0 commit comments