kafka-crab-js 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/README.md +0 -1
- package/index.d.ts +16 -17
- package/index.js +3 -4
- package/package.json +4 -4
- package/src/kafka/consumer/consumer_context.rs +29 -0
- package/src/kafka/consumer/consumer_thread.rs +196 -0
- package/src/kafka/{kafka_consumer.rs → consumer/kafka_consumer.rs} +48 -225
- package/src/kafka/consumer/mod.rs +3 -0
- package/src/kafka/kafka_admin.rs +4 -9
- package/src/kafka/kafka_client.rs +1 -1
- package/src/kafka/mod.rs +1 -1
package/Cargo.toml
CHANGED
package/README.md
CHANGED
package/index.d.ts
CHANGED
|
@@ -3,19 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
/* auto-generated by NAPI-RS */
|
|
5
5
|
|
|
6
|
-
export const enum SecurityProtocol {
|
|
7
|
-
Plaintext = 'Plaintext',
|
|
8
|
-
Ssl = 'Ssl',
|
|
9
|
-
SaslPlaintext = 'SaslPlaintext',
|
|
10
|
-
SaslSsl = 'SaslSsl'
|
|
11
|
-
}
|
|
12
|
-
export interface KafkaConfiguration {
|
|
13
|
-
brokers: string
|
|
14
|
-
clientId: string
|
|
15
|
-
securityProtocol?: SecurityProtocol
|
|
16
|
-
configuration?: Record<string, string>
|
|
17
|
-
logLevel?: string
|
|
18
|
-
}
|
|
19
6
|
export const enum CommitMode {
|
|
20
7
|
AutoCommit = 'AutoCommit',
|
|
21
8
|
Sync = 'Sync',
|
|
@@ -43,6 +30,19 @@ export interface ConsumerConfiguration {
|
|
|
43
30
|
enableAutoCommit?: boolean
|
|
44
31
|
configuration?: Record<string, string>
|
|
45
32
|
}
|
|
33
|
+
export const enum SecurityProtocol {
|
|
34
|
+
Plaintext = 'Plaintext',
|
|
35
|
+
Ssl = 'Ssl',
|
|
36
|
+
SaslPlaintext = 'SaslPlaintext',
|
|
37
|
+
SaslSsl = 'SaslSsl'
|
|
38
|
+
}
|
|
39
|
+
export interface KafkaConfiguration {
|
|
40
|
+
brokers: string
|
|
41
|
+
clientId: string
|
|
42
|
+
securityProtocol?: SecurityProtocol
|
|
43
|
+
configuration?: Record<string, string>
|
|
44
|
+
logLevel?: string
|
|
45
|
+
}
|
|
46
46
|
export interface ProducerConfiguration {
|
|
47
47
|
queueTimeout?: number
|
|
48
48
|
thrownOnError?: boolean
|
|
@@ -93,16 +93,15 @@ export interface MessageModel {
|
|
|
93
93
|
key?: Buffer
|
|
94
94
|
headers?: Record<string, Buffer>
|
|
95
95
|
}
|
|
96
|
+
export class KafkaConsumer {
|
|
97
|
+
startConsumer(callback: (error: Error | undefined, result: Payload) => Promise<ConsumerResult | undefined>): Promise<void>
|
|
98
|
+
}
|
|
96
99
|
export class KafkaClient {
|
|
97
100
|
readonly kafkaConfiguration: KafkaConfiguration
|
|
98
101
|
constructor(kafkaConfiguration: KafkaConfiguration)
|
|
99
102
|
createProducer(producerConfiguration: ProducerConfiguration): KafkaProducer
|
|
100
103
|
createConsumer(consumerConfiguration: ConsumerConfiguration): KafkaConsumer
|
|
101
104
|
}
|
|
102
|
-
export class ProducerHelper { }
|
|
103
|
-
export class KafkaConsumer {
|
|
104
|
-
startConsumer(callback: (error: Error | undefined, result: Payload) => Promise<ConsumerResult | undefined>): Promise<void>
|
|
105
|
-
}
|
|
106
105
|
export class KafkaProducer {
|
|
107
106
|
send(producerRecord: ProducerRecord): Promise<Array<RecordMetadata>>
|
|
108
107
|
}
|
package/index.js
CHANGED
|
@@ -252,14 +252,13 @@ if (!nativeBinding) {
|
|
|
252
252
|
throw new Error(`Failed to load native binding`)
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
const {
|
|
255
|
+
const { CommitMode, ConsumerResult, KafkaConsumer, SecurityProtocol, KafkaClient, KafkaProducer, AutoOffsetReset, PartitionPosition } = nativeBinding
|
|
256
256
|
|
|
257
|
-
module.exports.SecurityProtocol = SecurityProtocol
|
|
258
|
-
module.exports.KafkaClient = KafkaClient
|
|
259
257
|
module.exports.CommitMode = CommitMode
|
|
260
258
|
module.exports.ConsumerResult = ConsumerResult
|
|
261
|
-
module.exports.ProducerHelper = ProducerHelper
|
|
262
259
|
module.exports.KafkaConsumer = KafkaConsumer
|
|
260
|
+
module.exports.SecurityProtocol = SecurityProtocol
|
|
261
|
+
module.exports.KafkaClient = KafkaClient
|
|
263
262
|
module.exports.KafkaProducer = KafkaProducer
|
|
264
263
|
module.exports.AutoOffsetReset = AutoOffsetReset
|
|
265
264
|
module.exports.PartitionPosition = PartitionPosition
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kafka-crab-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"napi": {
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
},
|
|
57
57
|
"packageManager": "yarn@3.6.0",
|
|
58
58
|
"optionalDependencies": {
|
|
59
|
-
"kafka-crab-js-darwin-arm64": "0.
|
|
60
|
-
"kafka-crab-js-darwin-x64": "0.
|
|
61
|
-
"kafka-crab-js-linux-x64-gnu": "0.
|
|
59
|
+
"kafka-crab-js-darwin-arm64": "0.4.1",
|
|
60
|
+
"kafka-crab-js-darwin-x64": "0.4.1",
|
|
61
|
+
"kafka-crab-js-linux-x64-gnu": "0.4.1"
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
use rdkafka::consumer::StreamConsumer;
|
|
2
|
+
use tracing::info;
|
|
3
|
+
|
|
4
|
+
use rdkafka::{
|
|
5
|
+
client::ClientContext,
|
|
6
|
+
consumer::{ConsumerContext, Rebalance},
|
|
7
|
+
error::KafkaResult,
|
|
8
|
+
topic_partition_list::TopicPartitionList,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
pub type LoggingConsumer = StreamConsumer<CustomContext>;
|
|
12
|
+
|
|
13
|
+
pub struct CustomContext;
|
|
14
|
+
|
|
15
|
+
impl ClientContext for CustomContext {}
|
|
16
|
+
|
|
17
|
+
impl ConsumerContext for CustomContext {
|
|
18
|
+
fn pre_rebalance(&self, rebalance: &Rebalance) {
|
|
19
|
+
info!("Pre rebalance {:?}", rebalance);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fn post_rebalance(&self, rebalance: &Rebalance) {
|
|
23
|
+
info!("Post rebalance {:?}", rebalance);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fn commit_callback(&self, result: KafkaResult<()>, _offsets: &TopicPartitionList) {
|
|
27
|
+
info!("Committing offsets: {:?}. Offset: {:?}", result, _offsets);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeFunction};
|
|
2
|
+
|
|
3
|
+
use rdkafka::{
|
|
4
|
+
consumer::{stream_consumer::StreamConsumer, CommitMode as RdKfafkaCommitMode, Consumer},
|
|
5
|
+
message::{BorrowedMessage, Header, OwnedHeaders},
|
|
6
|
+
producer::FutureRecord,
|
|
7
|
+
Message,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
use std::time::Duration;
|
|
11
|
+
use tracing::{debug, error, info, warn};
|
|
12
|
+
|
|
13
|
+
use crate::kafka::{
|
|
14
|
+
consumer::kafka_consumer::RETRY_COUNTER_NAME,
|
|
15
|
+
kafka_util::{
|
|
16
|
+
kakfa_headers_to_hashmap, kakfa_headers_to_hashmap_buffer, ExtractValueOnKafkaHashMap,
|
|
17
|
+
},
|
|
18
|
+
model::Payload,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
use super::kafka_consumer::{ConsumerResult, ProducerHelper};
|
|
22
|
+
|
|
23
|
+
use super::consumer_context::CustomContext;
|
|
24
|
+
|
|
25
|
+
pub struct ConsumerThread {
|
|
26
|
+
pub stream_consumer: StreamConsumer<CustomContext>,
|
|
27
|
+
pub func: ThreadsafeFunction<Payload>,
|
|
28
|
+
pub commit_mode: Option<RdKfafkaCommitMode>,
|
|
29
|
+
pub retries: i32,
|
|
30
|
+
pub next_topic_on_fail: Option<String>,
|
|
31
|
+
pub producer: ProducerHelper,
|
|
32
|
+
pub pause_consumer_duration: Option<Duration>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl ConsumerThread {
|
|
36
|
+
pub async fn start(&self) {
|
|
37
|
+
loop {
|
|
38
|
+
if let Some(duration) = &self.pause_consumer_duration {
|
|
39
|
+
tracing::debug!("Pause consumer for {}", duration.as_millis());
|
|
40
|
+
tokio::time::sleep(*duration).await;
|
|
41
|
+
};
|
|
42
|
+
match self.stream_consumer.recv().await {
|
|
43
|
+
Err(e) => error!("Error while receiving from stream consumer: {:?}", e),
|
|
44
|
+
Ok(message) => {
|
|
45
|
+
let message_result = self.process_message(&message).await;
|
|
46
|
+
match self.handle_message_result(&message, message_result).await {
|
|
47
|
+
Ok(_) => {
|
|
48
|
+
debug!(
|
|
49
|
+
"Message consumed successfully. Topic: {}, Partition: {}, Offset: {}",
|
|
50
|
+
message.topic(),
|
|
51
|
+
message.partition(),
|
|
52
|
+
message.offset()
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
Err(err) => {
|
|
56
|
+
error!("Message processing failed. Error: {:?}", err);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async fn process_message(
|
|
65
|
+
&self,
|
|
66
|
+
message: &BorrowedMessage<'_>,
|
|
67
|
+
) -> anyhow::Result<Option<ConsumerResult>> {
|
|
68
|
+
match message.payload_view::<[u8]>() {
|
|
69
|
+
None => Ok(None),
|
|
70
|
+
Some(Ok(payload)) => {
|
|
71
|
+
let payload_js = create_payload(message, payload);
|
|
72
|
+
match self
|
|
73
|
+
.func
|
|
74
|
+
.call_async::<Promise<Option<ConsumerResult>>>(Ok(payload_js))
|
|
75
|
+
.await
|
|
76
|
+
{
|
|
77
|
+
Ok(js_result) => match js_result.await {
|
|
78
|
+
Ok(value) => match value {
|
|
79
|
+
None | Some(ConsumerResult::Ok) => {
|
|
80
|
+
if let Some(commit_mode) = self.commit_mode {
|
|
81
|
+
match self.stream_consumer.commit_message(message, commit_mode) {
|
|
82
|
+
Ok(_) => Ok(Some(ConsumerResult::Ok)),
|
|
83
|
+
Err(e) => Err(anyhow::Error::new(e)),
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
Ok(Some(ConsumerResult::Ok))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
Some(ConsumerResult::Retry) => Ok(Some(ConsumerResult::Retry)),
|
|
90
|
+
},
|
|
91
|
+
Err(_) => {
|
|
92
|
+
warn!("Error while calling return function");
|
|
93
|
+
Ok(Some(ConsumerResult::Ok))
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
Err(err) => {
|
|
97
|
+
warn!("Error while calling function: {:?}", err.to_string());
|
|
98
|
+
Ok(Some(ConsumerResult::Retry))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
Some(_) => Err(anyhow::Error::msg(
|
|
103
|
+
"Error while deserializing message payload",
|
|
104
|
+
)),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async fn handle_message_result(
|
|
109
|
+
&self,
|
|
110
|
+
message: &BorrowedMessage<'_>,
|
|
111
|
+
message_result: anyhow::Result<Option<ConsumerResult>>,
|
|
112
|
+
) -> anyhow::Result<()> {
|
|
113
|
+
match message_result {
|
|
114
|
+
Ok(consumer_result) => match consumer_result {
|
|
115
|
+
Some(result) => match result {
|
|
116
|
+
ConsumerResult::Ok => Ok(()),
|
|
117
|
+
ConsumerResult::Retry => match self.send_to_retry_strategy(message).await {
|
|
118
|
+
Ok(_) => {
|
|
119
|
+
debug!("Message sent to retry strategy");
|
|
120
|
+
Ok(())
|
|
121
|
+
}
|
|
122
|
+
Err(e) => Err(anyhow::Error::msg(format!(
|
|
123
|
+
"Error while sending message to retry strategy: {:?}",
|
|
124
|
+
e
|
|
125
|
+
))),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
None => Ok(()),
|
|
129
|
+
},
|
|
130
|
+
Err(err) => Err(err),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async fn send_to_retry_strategy(&self, message: &BorrowedMessage<'_>) -> anyhow::Result<()> {
|
|
135
|
+
let retry_counter: usize = {
|
|
136
|
+
let headers_map = kakfa_headers_to_hashmap(message.headers());
|
|
137
|
+
let mut counter = headers_map.get_value(RETRY_COUNTER_NAME).unwrap_or(1);
|
|
138
|
+
if self.retries > 0 {
|
|
139
|
+
counter += 1;
|
|
140
|
+
}
|
|
141
|
+
counter
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
let next_topic = if self.retries > 0 && retry_counter <= self.retries.try_into().unwrap() {
|
|
145
|
+
Some(message.topic().to_string())
|
|
146
|
+
} else {
|
|
147
|
+
self.next_topic_on_fail.clone()
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
match next_topic {
|
|
151
|
+
Some(next_topic) => {
|
|
152
|
+
info!("Message sent to next topic: {}", next_topic);
|
|
153
|
+
let new_headers = message
|
|
154
|
+
.headers()
|
|
155
|
+
.unwrap_or(OwnedHeaders::new().as_borrowed())
|
|
156
|
+
.detach()
|
|
157
|
+
.insert(Header {
|
|
158
|
+
key: RETRY_COUNTER_NAME,
|
|
159
|
+
value: Some(retry_counter.to_string().as_str()),
|
|
160
|
+
});
|
|
161
|
+
let key = message.key().unwrap_or(&[]);
|
|
162
|
+
|
|
163
|
+
let record = FutureRecord::to(&next_topic)
|
|
164
|
+
.key(key)
|
|
165
|
+
.headers(new_headers)
|
|
166
|
+
.payload(message.payload().unwrap_or(&[]));
|
|
167
|
+
|
|
168
|
+
let _result = self
|
|
169
|
+
.producer
|
|
170
|
+
.future_producer
|
|
171
|
+
.send(record, self.producer.queue_timeout)
|
|
172
|
+
.await
|
|
173
|
+
.map_err(|e| anyhow::Error::new(e.0))?;
|
|
174
|
+
Ok(())
|
|
175
|
+
}
|
|
176
|
+
None => {
|
|
177
|
+
debug!("There is no next topic. Commit message");
|
|
178
|
+
Ok(())
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fn create_payload(message: &BorrowedMessage<'_>, payload: &[u8]) -> Payload {
|
|
185
|
+
let key: Option<Buffer> = message.key().map(|bytes| bytes.into());
|
|
186
|
+
let headers = Some(kakfa_headers_to_hashmap_buffer(message.headers()));
|
|
187
|
+
let payload_js = Payload::new(
|
|
188
|
+
payload.into(),
|
|
189
|
+
key,
|
|
190
|
+
headers,
|
|
191
|
+
message.topic().to_owned(),
|
|
192
|
+
message.partition(),
|
|
193
|
+
message.offset(),
|
|
194
|
+
);
|
|
195
|
+
payload_js
|
|
196
|
+
}
|
|
@@ -1,58 +1,33 @@
|
|
|
1
1
|
use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeFunction, Result};
|
|
2
2
|
|
|
3
3
|
use rdkafka::{
|
|
4
|
-
client::ClientContext,
|
|
5
4
|
config::RDKafkaLogLevel,
|
|
6
|
-
consumer::{
|
|
7
|
-
|
|
8
|
-
Rebalance,
|
|
9
|
-
},
|
|
10
|
-
error::KafkaResult,
|
|
11
|
-
message::{BorrowedMessage, Header, OwnedHeaders},
|
|
12
|
-
producer::{FutureProducer, FutureRecord},
|
|
5
|
+
consumer::{stream_consumer::StreamConsumer, CommitMode as RdKfafkaCommitMode, Consumer},
|
|
6
|
+
producer::FutureProducer,
|
|
13
7
|
topic_partition_list::TopicPartitionList,
|
|
14
|
-
ClientConfig,
|
|
8
|
+
ClientConfig,
|
|
15
9
|
};
|
|
16
10
|
|
|
17
11
|
use std::{collections::HashMap, time::Duration, vec};
|
|
18
12
|
use tracing::{debug, error, info, warn};
|
|
19
13
|
|
|
20
|
-
use crate::kafka::
|
|
21
|
-
convert_to_rdkafka_offset, kakfa_headers_to_hashmap, ExtractValueOnKafkaHashMap,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
use super::{
|
|
14
|
+
use crate::kafka::{
|
|
25
15
|
kafka_admin::KafkaAdmin,
|
|
26
16
|
kafka_client::KafkaClient,
|
|
27
|
-
kafka_util::{
|
|
17
|
+
kafka_util::{convert_to_rdkafka_offset, AnyhowToNapiError},
|
|
28
18
|
model::{OffsetModel, PartitionPosition, Payload},
|
|
29
19
|
};
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const DEFAULT_DLQ_TOPIC_SUFFIX: &str = "-dlq";
|
|
36
|
-
|
|
37
|
-
type LoggingConsumer = StreamConsumer<CustomContext>;
|
|
38
|
-
|
|
39
|
-
pub struct CustomContext;
|
|
40
|
-
|
|
41
|
-
impl ClientContext for CustomContext {}
|
|
42
|
-
|
|
43
|
-
impl ConsumerContext for CustomContext {
|
|
44
|
-
fn pre_rebalance(&self, rebalance: &Rebalance) {
|
|
45
|
-
info!("Pre rebalance {:?}", rebalance);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
fn post_rebalance(&self, rebalance: &Rebalance) {
|
|
49
|
-
info!("Post rebalance {:?}", rebalance);
|
|
50
|
-
}
|
|
21
|
+
use super::{
|
|
22
|
+
consumer_context::{CustomContext, LoggingConsumer},
|
|
23
|
+
consumer_thread::ConsumerThread,
|
|
24
|
+
};
|
|
51
25
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
26
|
+
pub const RETRY_COUNTER_NAME: &str = "kafka-crab-js-retry-counter";
|
|
27
|
+
pub const DEFAULT_QUEUE_TIMEOUT: u64 = 5000;
|
|
28
|
+
pub const DEFAULT_PAUSE_DURATION: i64 = 1000;
|
|
29
|
+
pub const DEFAULT_RETRY_TOPIC_SUFFIX: &str = "-retry";
|
|
30
|
+
pub const DEFAULT_DLQ_TOPIC_SUFFIX: &str = "-dlq";
|
|
56
31
|
|
|
57
32
|
#[napi(string_enum)]
|
|
58
33
|
#[derive(Debug, PartialEq)]
|
|
@@ -81,10 +56,9 @@ pub enum ConsumerResult {
|
|
|
81
56
|
}
|
|
82
57
|
|
|
83
58
|
#[derive(Clone)]
|
|
84
|
-
#[napi]
|
|
85
59
|
pub struct ProducerHelper {
|
|
86
|
-
future_producer: FutureProducer,
|
|
87
|
-
queue_timeout: Duration,
|
|
60
|
+
pub future_producer: FutureProducer,
|
|
61
|
+
pub queue_timeout: Duration,
|
|
88
62
|
}
|
|
89
63
|
|
|
90
64
|
fn setup_future_producer(
|
|
@@ -149,6 +123,11 @@ impl KafkaConsumer {
|
|
|
149
123
|
})
|
|
150
124
|
}
|
|
151
125
|
|
|
126
|
+
// #[napi(ts_args_type = "callback: (error: Error) => Promise<ConsumerResult | undefined>")]
|
|
127
|
+
// pub async fn create_consumer(&self) -> Result<()> {
|
|
128
|
+
// Ok(())
|
|
129
|
+
// }
|
|
130
|
+
|
|
152
131
|
#[napi(
|
|
153
132
|
ts_args_type = "callback: (error: Error | undefined, result: Payload) => Promise<ConsumerResult | undefined>"
|
|
154
133
|
)]
|
|
@@ -286,29 +265,46 @@ impl KafkaConsumer {
|
|
|
286
265
|
enable_auto_commit.unwrap_or(true).to_string(),
|
|
287
266
|
)
|
|
288
267
|
.set_log_level(RDKafkaLogLevel::Debug)
|
|
289
|
-
.create_with_context(context)
|
|
290
|
-
.expect("Consumer creation failed");
|
|
268
|
+
.create_with_context(context)?;
|
|
291
269
|
|
|
292
270
|
if create_topic.unwrap_or(true) {
|
|
271
|
+
info!("Creating topic: {:?}", topic);
|
|
293
272
|
let admin = KafkaAdmin::new(&self.client_config);
|
|
294
|
-
admin.create_topic(topic).await
|
|
273
|
+
let result = admin.create_topic(topic).await;
|
|
274
|
+
if let Err(e) = result {
|
|
275
|
+
warn!("Fail to create topic {:?}", e);
|
|
276
|
+
return Err(anyhow::Error::msg(format!("Fail to create topic: {:?}", e)));
|
|
277
|
+
}
|
|
278
|
+
info!("Topic created: {:?}", topic)
|
|
295
279
|
}
|
|
296
280
|
|
|
297
281
|
if let Some(offset) = convert_to_rdkafka_offset(offset) {
|
|
298
282
|
debug!("Setting offset to: {:?}", offset);
|
|
299
|
-
let metadata = consumer
|
|
300
|
-
.fetch_metadata(Some(topic), Duration::from_millis(1500))
|
|
301
|
-
.expect("Fail to retrive metadata from consumer");
|
|
283
|
+
let metadata = consumer.fetch_metadata(Some(topic), Duration::from_millis(1500))?;
|
|
302
284
|
|
|
303
285
|
metadata.topics().iter().for_each(|meta_topic| {
|
|
304
286
|
let mut tpl = TopicPartitionList::new();
|
|
305
287
|
meta_topic.partitions().iter().for_each(|meta_partition| {
|
|
306
288
|
tpl.add_partition(topic, meta_partition.id());
|
|
307
289
|
});
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
290
|
+
//TODO: Handle error
|
|
291
|
+
match tpl.set_all_offsets(offset) {
|
|
292
|
+
Ok(_) => {
|
|
293
|
+
debug!("Offset set to: {:?}", offset);
|
|
294
|
+
}
|
|
295
|
+
Err(e) => {
|
|
296
|
+
error!("Fail to set offset: {:?}", e)
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
//TODO: Handle error
|
|
300
|
+
match consumer.assign(&tpl) {
|
|
301
|
+
Ok(_) => {
|
|
302
|
+
debug!("Assigning topic: {:?}", topic);
|
|
303
|
+
}
|
|
304
|
+
Err(e) => {
|
|
305
|
+
error!("Fail to assign topic: {:?}", e);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
312
308
|
});
|
|
313
309
|
}
|
|
314
310
|
|
|
@@ -328,176 +324,3 @@ impl KafkaConsumer {
|
|
|
328
324
|
Ok(consumer)
|
|
329
325
|
}
|
|
330
326
|
}
|
|
331
|
-
|
|
332
|
-
pub struct ConsumerThread {
|
|
333
|
-
stream_consumer: StreamConsumer<CustomContext>,
|
|
334
|
-
func: ThreadsafeFunction<Payload>,
|
|
335
|
-
commit_mode: Option<RdKfafkaCommitMode>,
|
|
336
|
-
retries: i32,
|
|
337
|
-
next_topic_on_fail: Option<String>,
|
|
338
|
-
producer: ProducerHelper,
|
|
339
|
-
pause_consumer_duration: Option<Duration>,
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
impl ConsumerThread {
|
|
343
|
-
pub async fn start(&self) {
|
|
344
|
-
loop {
|
|
345
|
-
if let Some(duration) = &self.pause_consumer_duration {
|
|
346
|
-
tracing::debug!("Pause consumer for {}", duration.as_millis());
|
|
347
|
-
tokio::time::sleep(*duration).await;
|
|
348
|
-
};
|
|
349
|
-
match self.stream_consumer.recv().await {
|
|
350
|
-
Err(e) => error!("Error while receiving from stream consumer: {:?}", e),
|
|
351
|
-
Ok(message) => {
|
|
352
|
-
let message_result = self.process_message(&message).await;
|
|
353
|
-
match self.handle_message_result(&message, message_result).await {
|
|
354
|
-
Ok(_) => {
|
|
355
|
-
debug!(
|
|
356
|
-
"Message consumed successfully. Topic: {}, Partition: {}, Offset: {}",
|
|
357
|
-
message.topic(),
|
|
358
|
-
message.partition(),
|
|
359
|
-
message.offset()
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
Err(err) => {
|
|
363
|
-
error!("Message processing failed. Error: {:?}", err);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async fn process_message(
|
|
372
|
-
&self,
|
|
373
|
-
message: &BorrowedMessage<'_>,
|
|
374
|
-
) -> anyhow::Result<Option<ConsumerResult>> {
|
|
375
|
-
match message.payload_view::<[u8]>() {
|
|
376
|
-
None => Ok(None),
|
|
377
|
-
Some(Ok(payload)) => {
|
|
378
|
-
let payload_js = create_payload(message, payload);
|
|
379
|
-
match self
|
|
380
|
-
.func
|
|
381
|
-
.call_async::<Promise<Option<ConsumerResult>>>(Ok(payload_js))
|
|
382
|
-
.await
|
|
383
|
-
{
|
|
384
|
-
Ok(js_result) => match js_result.await {
|
|
385
|
-
Ok(value) => match value {
|
|
386
|
-
None | Some(ConsumerResult::Ok) => {
|
|
387
|
-
if let Some(commit_mode) = self.commit_mode {
|
|
388
|
-
match self.stream_consumer.commit_message(message, commit_mode) {
|
|
389
|
-
Ok(_) => Ok(Some(ConsumerResult::Ok)),
|
|
390
|
-
Err(e) => Err(anyhow::Error::new(e)),
|
|
391
|
-
}
|
|
392
|
-
} else {
|
|
393
|
-
Ok(Some(ConsumerResult::Ok))
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
Some(ConsumerResult::Retry) => Ok(Some(ConsumerResult::Retry)),
|
|
397
|
-
},
|
|
398
|
-
Err(_) => {
|
|
399
|
-
warn!("Error while calling return function");
|
|
400
|
-
Ok(Some(ConsumerResult::Ok))
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
Err(err) => {
|
|
404
|
-
warn!("Error while calling function: {:?}", err.to_string());
|
|
405
|
-
Ok(Some(ConsumerResult::Retry))
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
Some(_) => Err(anyhow::Error::msg(
|
|
410
|
-
"Error while deserializing message payload",
|
|
411
|
-
)),
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async fn handle_message_result(
|
|
416
|
-
&self,
|
|
417
|
-
message: &BorrowedMessage<'_>,
|
|
418
|
-
message_result: anyhow::Result<Option<ConsumerResult>>,
|
|
419
|
-
) -> anyhow::Result<()> {
|
|
420
|
-
match message_result {
|
|
421
|
-
Ok(consumer_result) => match consumer_result {
|
|
422
|
-
Some(result) => match result {
|
|
423
|
-
ConsumerResult::Ok => Ok(()),
|
|
424
|
-
ConsumerResult::Retry => match self.send_to_retry_strategy(message).await {
|
|
425
|
-
Ok(_) => {
|
|
426
|
-
debug!("Message sent to retry strategy");
|
|
427
|
-
Ok(())
|
|
428
|
-
}
|
|
429
|
-
Err(e) => Err(anyhow::Error::msg(format!(
|
|
430
|
-
"Error while sending message to retry strategy: {:?}",
|
|
431
|
-
e
|
|
432
|
-
))),
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
None => Ok(()),
|
|
436
|
-
},
|
|
437
|
-
Err(err) => Err(err),
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
async fn send_to_retry_strategy(&self, message: &BorrowedMessage<'_>) -> anyhow::Result<()> {
|
|
442
|
-
let retry_counter: usize = {
|
|
443
|
-
let headers_map = kakfa_headers_to_hashmap(message.headers());
|
|
444
|
-
let mut counter = headers_map.get_value(RETRY_COUNTER_NAME).unwrap_or(1);
|
|
445
|
-
if self.retries > 0 {
|
|
446
|
-
counter += 1;
|
|
447
|
-
}
|
|
448
|
-
counter
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
let next_topic = if self.retries > 0 && retry_counter <= self.retries.try_into().unwrap() {
|
|
452
|
-
Some(message.topic().to_string())
|
|
453
|
-
} else {
|
|
454
|
-
self.next_topic_on_fail.clone()
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
match next_topic {
|
|
458
|
-
Some(next_topic) => {
|
|
459
|
-
info!("Message sent to next topic: {}", next_topic);
|
|
460
|
-
let new_headers = message
|
|
461
|
-
.headers()
|
|
462
|
-
.unwrap_or(OwnedHeaders::new().as_borrowed())
|
|
463
|
-
.detach()
|
|
464
|
-
.insert(Header {
|
|
465
|
-
key: RETRY_COUNTER_NAME,
|
|
466
|
-
value: Some(retry_counter.to_string().as_str()),
|
|
467
|
-
});
|
|
468
|
-
let key = message.key().unwrap_or(&[]);
|
|
469
|
-
|
|
470
|
-
let record = FutureRecord::to(&next_topic)
|
|
471
|
-
.key(key)
|
|
472
|
-
.headers(new_headers)
|
|
473
|
-
.payload(message.payload().unwrap_or(&[]));
|
|
474
|
-
|
|
475
|
-
let _result = self
|
|
476
|
-
.producer
|
|
477
|
-
.future_producer
|
|
478
|
-
.send(record, self.producer.queue_timeout)
|
|
479
|
-
.await
|
|
480
|
-
.map_err(|e| anyhow::Error::new(e.0))?;
|
|
481
|
-
Ok(())
|
|
482
|
-
}
|
|
483
|
-
None => {
|
|
484
|
-
debug!("There is no next topic. Commit message");
|
|
485
|
-
Ok(())
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
fn create_payload(message: &BorrowedMessage<'_>, payload: &[u8]) -> Payload {
|
|
492
|
-
let key: Option<Buffer> = message.key().map(|bytes| bytes.into());
|
|
493
|
-
let headers = Some(kakfa_headers_to_hashmap_buffer(message.headers()));
|
|
494
|
-
let payload_js = Payload::new(
|
|
495
|
-
payload.into(),
|
|
496
|
-
key,
|
|
497
|
-
headers,
|
|
498
|
-
message.topic().to_owned(),
|
|
499
|
-
message.partition(),
|
|
500
|
-
message.offset(),
|
|
501
|
-
);
|
|
502
|
-
payload_js
|
|
503
|
-
}
|
package/src/kafka/kafka_admin.rs
CHANGED
|
@@ -33,14 +33,9 @@ impl<'a> KafkaAdmin<'a> {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
async fn fetch_config_resource(&self) -> Result<HashMap<String, String>, KafkaError> {
|
|
36
|
-
let consumer: BaseConsumer = self
|
|
37
|
-
.client_config
|
|
38
|
-
.create()
|
|
39
|
-
.expect("Consumer creation failed");
|
|
36
|
+
let consumer: BaseConsumer = self.client_config.create()?;
|
|
40
37
|
|
|
41
|
-
let metadata = consumer
|
|
42
|
-
.fetch_metadata(None, Duration::from_secs(5))
|
|
43
|
-
.expect("Failed to fetch metadata");
|
|
38
|
+
let metadata = consumer.fetch_metadata(None, Duration::from_secs(5))?;
|
|
44
39
|
|
|
45
40
|
for broker in metadata.brokers() {
|
|
46
41
|
info!(
|
|
@@ -63,9 +58,9 @@ impl<'a> KafkaAdmin<'a> {
|
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
pub async fn create_topic(&self, topic_name: &str) -> anyhow::Result<()> {
|
|
61
|
+
debug!("Fetching broker properties");
|
|
66
62
|
let broker_properties = self.fetch_config_resource().await?.clone();
|
|
67
|
-
|
|
68
|
-
// If you want print all properties => dbg!(broker_properties.clone());
|
|
63
|
+
debug!("Broker properties: {:?}", broker_properties);
|
|
69
64
|
|
|
70
65
|
self
|
|
71
66
|
.admin_client
|
|
@@ -12,7 +12,7 @@ use rdkafka::{
|
|
|
12
12
|
use tracing::{info, Level};
|
|
13
13
|
|
|
14
14
|
use super::{
|
|
15
|
-
kafka_consumer::{ConsumerConfiguration, KafkaConsumer},
|
|
15
|
+
consumer::kafka_consumer::{ConsumerConfiguration, KafkaConsumer},
|
|
16
16
|
kafka_producer::{KafkaProducer, ProducerConfiguration},
|
|
17
17
|
};
|
|
18
18
|
|