skywalking/reporter/
kafka.rs

1// Licensed to the Apache Software Foundation (ASF) under one or more
2// contributor license agreements.  See the NOTICE file distributed with
3// this work for additional information regarding copyright ownership.
4// The ASF licenses this file to You under the Apache License, Version 2.0
5// (the "License"); you may not use this file except in compliance with
6// the License.  You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17//! Kafka implementation of [Report].
18
19use super::{CollectItemConsume, CollectItemProduce};
20use crate::reporter::{CollectItem, Report};
21use rdkafka::{
22    config::ClientConfig as RDKafkaClientConfig,
23    producer::{FutureProducer, FutureRecord},
24};
25use std::{
26    collections::HashMap,
27    error,
28    future::{Future, pending},
29    pin::Pin,
30    sync::{
31        Arc,
32        atomic::{AtomicBool, Ordering::Relaxed},
33    },
34    time::Duration,
35};
36use tokio::{select, spawn, sync::mpsc, task::JoinHandle, try_join};
37use tracing::error;
38
39/// Kafka reporter error.
40#[derive(Debug, thiserror::Error)]
41pub enum Error {
42    /// ksKafka error.
43    #[error(transparent)]
44    RdKafka(#[from] rdkafka::error::KafkaError),
45
46    /// kafka topic not found
47    #[error("topic not found: {topic}")]
48    TopicNotFound {
49        /// Name of kafka topic.
50        topic: String,
51    },
52}
53
54/// Log level for Kafka client.
55#[derive(Debug, Clone, Copy)]
56pub enum LogLevel {
57    /// Critical level.
58    Critical,
59    /// Error level.
60    Error,
61    /// Warning level.
62    Warning,
63    /// Notice level.
64    Notice,
65    /// Info level.
66    Info,
67    /// Debug level.
68    Debug,
69}
70
71impl From<LogLevel> for rdkafka::config::RDKafkaLogLevel {
72    fn from(level: LogLevel) -> Self {
73        match level {
74            LogLevel::Critical => rdkafka::config::RDKafkaLogLevel::Critical,
75            LogLevel::Error => rdkafka::config::RDKafkaLogLevel::Error,
76            LogLevel::Warning => rdkafka::config::RDKafkaLogLevel::Warning,
77            LogLevel::Notice => rdkafka::config::RDKafkaLogLevel::Notice,
78            LogLevel::Info => rdkafka::config::RDKafkaLogLevel::Info,
79            LogLevel::Debug => rdkafka::config::RDKafkaLogLevel::Debug,
80        }
81    }
82}
83
84/// Configuration for Kafka client.
85#[derive(Debug, Clone)]
86pub struct ClientConfig {
87    /// Configuration parameters as key-value pairs.
88    params: HashMap<String, String>,
89    /// Log level for the client.
90    log_level: Option<LogLevel>,
91}
92
93impl ClientConfig {
94    /// Create a new empty configuration.
95    pub fn new() -> Self {
96        Self {
97            params: HashMap::new(),
98            log_level: None,
99        }
100    }
101
102    /// Set a configuration parameter.
103    pub fn set<K, V>(&mut self, key: K, value: V) -> &mut Self
104    where
105        K: Into<String>,
106        V: Into<String>,
107    {
108        self.params.insert(key.into(), value.into());
109        self
110    }
111
112    /// Set log level.
113    pub fn set_log_level(&mut self, level: LogLevel) -> &mut Self {
114        self.log_level = Some(level);
115        self
116    }
117
118    /// Convert to rdkafka ClientConfig.
119    fn to_rdkafka_config(&self) -> RDKafkaClientConfig {
120        let mut config = RDKafkaClientConfig::new();
121        for (key, value) in &self.params {
122            config.set(key, value);
123        }
124        if let Some(log_level) = self.log_level {
125            config.set_log_level(log_level.into());
126        }
127        config
128    }
129}
130
131impl Default for ClientConfig {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137type DynErrHandler = dyn Fn(&str, &dyn error::Error) + Send + Sync + 'static;
138
139fn default_err_handle(message: &str, err: &dyn error::Error) {
140    error!(?err, "{}", message);
141}
142
143#[derive(Default)]
144struct State {
145    is_closing: AtomicBool,
146}
147
148impl State {
149    fn is_closing(&self) -> bool {
150        self.is_closing.load(Relaxed)
151    }
152}
153
154/// The Kafka reporter plugin support report traces, metrics, logs, instance
155/// properties to Kafka cluster.
156pub struct KafkaReportBuilder<P, C> {
157    state: Arc<State>,
158    producer: Arc<P>,
159    consumer: C,
160    client_config: ClientConfig,
161    namespace: Option<String>,
162    err_handle: Arc<DynErrHandler>,
163}
164
165impl KafkaReportBuilder<mpsc::UnboundedSender<CollectItem>, mpsc::UnboundedReceiver<CollectItem>> {
166    /// Create builder, with client configuration.
167    pub fn new(client_config: ClientConfig) -> Self {
168        let (producer, consumer) = mpsc::unbounded_channel();
169        Self::new_with_pc(client_config, producer, consumer)
170    }
171}
172
173impl<P: CollectItemProduce, C: CollectItemConsume> KafkaReportBuilder<P, C> {
174    /// Special purpose, used for user-defined produce and consume operations,
175    /// usually you can use [KafkaReportBuilder::new].
176    pub fn new_with_pc(client_config: ClientConfig, producer: P, consumer: C) -> Self {
177        Self {
178            state: Default::default(),
179            producer: Arc::new(producer),
180            consumer,
181            client_config,
182            namespace: None,
183            err_handle: Arc::new(default_err_handle),
184        }
185    }
186
187    /// Set error handle. By default, the error will be logged.
188    pub fn with_err_handle(
189        mut self,
190        handle: impl Fn(&str, &dyn error::Error) + Send + Sync + 'static,
191    ) -> Self {
192        self.err_handle = Arc::new(handle);
193        self
194    }
195
196    /// Use to isolate multi OAP server when using same Kafka cluster (final
197    /// topic name will append namespace before Kafka topics with - ).
198    pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
199        self.namespace = Some(namespace.into());
200        self
201    }
202
203    /// Build the Reporter implemented [Report] in the foreground, and the
204    /// handle to push data to kafka in the background.
205    pub async fn build(self) -> Result<(KafkaReporter<P>, KafkaReporting<C>), Error> {
206        let kafka_producer = KafkaProducer::new(
207            self.client_config.to_rdkafka_config().create()?,
208            self.err_handle.clone(),
209            self.namespace,
210        )
211        .await?;
212        Ok((
213            KafkaReporter {
214                state: self.state.clone(),
215                producer: self.producer,
216                err_handle: self.err_handle,
217            },
218            KafkaReporting {
219                state: self.state,
220                consumer: self.consumer,
221                kafka_producer,
222                shutdown_signal: Box::pin(pending()),
223            },
224        ))
225    }
226}
227
228/// The kafka reporter implemented [Report].
229pub struct KafkaReporter<P> {
230    state: Arc<State>,
231    producer: Arc<P>,
232    err_handle: Arc<DynErrHandler>,
233}
234
235impl<P> Clone for KafkaReporter<P> {
236    #[inline]
237    fn clone(&self) -> Self {
238        Self {
239            state: self.state.clone(),
240            producer: self.producer.clone(),
241            err_handle: self.err_handle.clone(),
242        }
243    }
244}
245
246impl<P: CollectItemProduce> Report for KafkaReporter<P> {
247    fn report(&self, item: CollectItem) {
248        if !self.state.is_closing() {
249            if let Err(e) = self.producer.produce(item) {
250                (self.err_handle)("report collect item failed", &*e);
251            }
252        }
253    }
254}
255
256/// The handle to push data to kafka.
257pub struct KafkaReporting<C> {
258    state: Arc<State>,
259    consumer: C,
260    kafka_producer: KafkaProducer,
261    shutdown_signal: Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>,
262}
263
264impl<C: CollectItemConsume> KafkaReporting<C> {
265    /// Quit when shutdown_signal received.
266    ///
267    /// Accept a `shutdown_signal` argument as a graceful shutdown signal.
268    pub fn with_graceful_shutdown(
269        mut self,
270        shutdown_signal: impl Future<Output = ()> + Send + Sync + 'static,
271    ) -> Self {
272        self.shutdown_signal = Box::pin(shutdown_signal);
273        self
274    }
275
276    /// Spawn the reporting in background.
277    pub fn spawn(self) -> ReportingJoinHandle {
278        let handle = spawn(async move {
279            let KafkaReporting {
280                state,
281                mut consumer,
282                mut kafka_producer,
283                shutdown_signal,
284            } = self;
285
286            let (shutdown_tx, mut shutdown_rx) = mpsc::unbounded_channel();
287
288            let work_fut = async move {
289                loop {
290                    select! {
291                        item = consumer.consume() => {
292                            match item {
293                                Ok(Some(item)) => {
294                                    kafka_producer.produce(item).await;
295                                }
296                                Ok(None) => break,
297                                Err(err) => return Err(crate::Error::Other(err)),
298                            }
299                        }
300                        _ =  shutdown_rx.recv() => break,
301                    }
302                }
303
304                state.is_closing.store(true, Relaxed);
305
306                // Flush.
307                loop {
308                    match consumer.try_consume().await {
309                        Ok(Some(item)) => {
310                            kafka_producer.produce(item).await;
311                        }
312                        Ok(None) => break,
313                        Err(err) => return Err(err.into()),
314                    }
315                }
316
317                Ok::<_, crate::Error>(())
318            };
319
320            let shutdown_fut = async move {
321                shutdown_signal.await;
322                shutdown_tx
323                    .send(())
324                    .map_err(|e| crate::Error::Other(Box::new(e)))?;
325                Ok(())
326            };
327
328            try_join!(work_fut, shutdown_fut)?;
329
330            Ok(())
331        });
332        ReportingJoinHandle { handle }
333    }
334}
335
336/// Handle of [KafkaReporting::spawn].
337pub struct ReportingJoinHandle {
338    handle: JoinHandle<crate::Result<()>>,
339}
340
341impl Future for ReportingJoinHandle {
342    type Output = crate::Result<()>;
343
344    fn poll(
345        mut self: std::pin::Pin<&mut Self>,
346        cx: &mut std::task::Context<'_>,
347    ) -> std::task::Poll<Self::Output> {
348        Pin::new(&mut self.handle).poll(cx).map(|rs| rs?)
349    }
350}
351
352struct TopicNames {
353    segment: String,
354    meter: String,
355    log: String,
356    #[cfg(feature = "management")]
357    management: String,
358}
359
360impl TopicNames {
361    const TOPIC_LOG: &str = "skywalking-logs";
362    #[cfg(feature = "management")]
363    const TOPIC_MANAGEMENT: &str = "skywalking-managements";
364    const TOPIC_METER: &str = "skywalking-meters";
365    const TOPIC_SEGMENT: &str = "skywalking-segments";
366
367    fn new(namespace: Option<&str>) -> Self {
368        Self {
369            segment: Self::real_topic_name(namespace, Self::TOPIC_SEGMENT),
370            meter: Self::real_topic_name(namespace, Self::TOPIC_METER),
371            log: Self::real_topic_name(namespace, Self::TOPIC_LOG),
372            #[cfg(feature = "management")]
373            management: Self::real_topic_name(namespace, Self::TOPIC_MANAGEMENT),
374        }
375    }
376
377    fn real_topic_name(namespace: Option<&str>, topic_name: &str) -> String {
378        namespace
379            .map(|namespace| format!("{}-{}", namespace, topic_name))
380            .unwrap_or_else(|| topic_name.to_string())
381    }
382}
383
384struct KafkaProducer {
385    topic_names: TopicNames,
386    client: FutureProducer,
387    err_handle: Arc<DynErrHandler>,
388}
389
390impl KafkaProducer {
391    async fn new(
392        client: FutureProducer,
393        err_handle: Arc<DynErrHandler>,
394        namespace: Option<String>,
395    ) -> Result<Self, Error> {
396        let topic_names = TopicNames::new(namespace.as_deref());
397        Ok(Self {
398            client,
399            err_handle,
400            topic_names,
401        })
402    }
403
404    async fn produce(&mut self, item: CollectItem) {
405        let (topic_name, key) = match &item {
406            CollectItem::Trace(item) => (
407                &self.topic_names.segment,
408                item.trace_segment_id.as_bytes().to_vec(),
409            ),
410            CollectItem::Log(item) => (&self.topic_names.log, item.service.as_bytes().to_vec()),
411            CollectItem::Meter(item) => (
412                &self.topic_names.meter,
413                item.service_instance.as_bytes().to_vec(),
414            ),
415            #[cfg(feature = "management")]
416            CollectItem::Instance(item) => (
417                &self.topic_names.management,
418                format!("register-{}", &item.service_instance).into_bytes(),
419            ),
420            #[cfg(feature = "management")]
421            CollectItem::Ping(item) => (
422                &self.topic_names.log,
423                item.service_instance.as_bytes().to_vec(),
424            ),
425        };
426
427        let payload = item.encode_to_vec();
428        let record = FutureRecord::to(topic_name).payload(&payload).key(&key);
429
430        if let Err((err, _)) = self.client.send(record, Duration::from_secs(0)).await {
431            (self.err_handle)("Collect data to kafka failed", &err);
432        }
433    }
434}