1use std::{collections::HashMap, io, net::SocketAddr, time::Duration};
2
3use base64::prelude::{BASE64_STANDARD, Engine as _};
4use bytes::{Buf, Bytes, BytesMut};
5use chrono::Utc;
6use rmp_serde::{Deserializer, Serializer, decode};
7use serde::{Deserialize, Serialize};
8use smallvec::{SmallVec, smallvec};
9use tokio_util::codec::Decoder;
10use vector_lib::{
11 codecs::{BytesDeserializerConfig, StreamDecodingError},
12 config::{LegacyKey, LogNamespace},
13 configurable::configurable_component,
14 ipallowlist::IpAllowlistConfig,
15 lookup::{OwnedValuePath, lookup_v2::parse_value_path, metadata_path, owned_value_path, path},
16 schema::Definition,
17};
18use vrl::value::{Kind, Value, kind::Collection};
19
20use super::util::decompression::{CappedDecoder, max_decompressed_size_bytes};
21use super::util::net::{SocketListenAddr, TcpSource, TcpSourceAck, TcpSourceAcker};
22use crate::{
23 config::{
24 DataType, GenerateConfig, Resource, SourceAcknowledgementsConfig, SourceConfig,
25 SourceContext, SourceOutput, log_schema,
26 },
27 event::{Event, LogEvent},
28 internal_events::{FluentMessageDecodeError, FluentMessageReceived},
29 serde::bool_or_struct,
30 tcp::TcpKeepaliveConfig,
31 tls::{MaybeTlsSettings, TlsSourceConfig},
32};
33
34mod message;
35use self::message::{FluentEntry, FluentMessage, FluentRecord, FluentTag, FluentTimestamp};
36
37#[configurable_component(source("fluent", "Collect logs from a Fluentd or Fluent Bit agent."))]
39#[derive(Clone, Debug)]
40pub struct FluentConfig {
41 #[serde(flatten)]
42 mode: FluentMode,
43
44 #[configurable(metadata(docs::hidden))]
46 #[serde(default)]
47 log_namespace: Option<bool>,
48}
49
50#[configurable_component(no_deser)]
52#[derive(Clone, Debug)]
53#[serde(tag = "mode", rename_all = "snake_case")]
54#[configurable(metadata(docs::enum_tag_description = "The type of socket to use."))]
55#[allow(clippy::large_enum_variant)] pub enum FluentMode {
57 Tcp(FluentTcpConfig),
59
60 #[cfg(unix)]
62 Unix(FluentUnixConfig),
63}
64
65mod deser {
71 use super::*;
72
73 #[allow(clippy::large_enum_variant)]
74 #[derive(Deserialize)]
75 #[serde(tag = "mode")]
76 enum FluentModeTagged {
77 #[serde(rename = "tcp")]
78 Tcp(FluentTcpConfig),
79
80 #[cfg(unix)]
81 #[serde(rename = "unix")]
82 Unix(FluentUnixConfig),
83 }
84
85 #[derive(Deserialize)]
86 #[serde(untagged)]
87 enum FluentModeDe {
88 Tagged(FluentModeTagged),
89
90 Untagged(FluentTcpConfig),
92 }
93
94 impl<'de> Deserialize<'de> for FluentMode {
95 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96 where
97 D: serde::Deserializer<'de>,
98 {
99 Ok(match FluentModeDe::deserialize(deserializer)? {
100 FluentModeDe::Tagged(FluentModeTagged::Tcp(config)) => FluentMode::Tcp(config),
101 #[cfg(unix)]
102 FluentModeDe::Tagged(FluentModeTagged::Unix(config)) => FluentMode::Unix(config),
103 FluentModeDe::Untagged(config) => FluentMode::Tcp(config),
104 })
105 }
106 }
107
108 #[cfg(test)]
109 mod tests {
110 use super::*;
111
112 #[test]
113 fn test_tcp_default_mode() {
114 let json_data = serde_json::json!({
115 "address": "0.0.0.0:2020",
116 "connection_limit": 2
117 });
118
119 let parsed: FluentConfig = serde_json::from_value(json_data).unwrap();
120 assert!(matches!(parsed.mode, FluentMode::Tcp(c) if c.connection_limit.unwrap() == 2));
121 }
122
123 #[test]
124 fn test_tcp_explicit_mode() {
125 let json_data = serde_json::json!({
126 "mode": "tcp",
127 "address": "0.0.0.0:2020",
128 "connection_limit": 2
129 });
130
131 let parsed: FluentConfig = serde_json::from_value(json_data).unwrap();
132 assert!(matches!(parsed.mode, FluentMode::Tcp(c) if c.connection_limit.unwrap() == 2));
133 }
134
135 #[test]
136 fn test_invalid_unix_mode() {
137 let json_data = serde_json::json!({
138 "mode": "unix",
139 "address": "0.0.0.0:2020",
140 "connection_limit": 2
141 });
142
143 assert!(serde_json::from_value::<FluentConfig>(json_data).is_err());
144 }
145
146 #[cfg(unix)]
147 #[test]
148 fn test_valid_unix_mode() {
149 let json_data = serde_json::json!({
150 "mode": "unix",
151 "path": "/foo"
152 });
153
154 let parsed: FluentConfig = serde_json::from_value(json_data).unwrap();
155 assert!(
156 matches!(parsed.mode, FluentMode::Unix(c) if c.path.to_string_lossy() == "/foo")
157 );
158 }
159 }
160}
161
162#[configurable_component]
164#[derive(Clone, Debug)]
165#[serde(deny_unknown_fields)]
166pub struct FluentTcpConfig {
167 #[configurable(derived)]
168 address: SocketListenAddr,
169
170 #[configurable(metadata(docs::type_unit = "connections"))]
172 connection_limit: Option<u32>,
173
174 #[configurable(derived)]
175 keepalive: Option<TcpKeepaliveConfig>,
176
177 #[configurable(derived)]
178 pub permit_origin: Option<IpAllowlistConfig>,
179
180 #[configurable(metadata(docs::type_unit = "bytes"))]
184 #[configurable(metadata(docs::examples = 65536))]
185 receive_buffer_bytes: Option<usize>,
186
187 #[configurable(derived)]
188 tls: Option<TlsSourceConfig>,
189
190 #[configurable(derived)]
191 #[serde(default, deserialize_with = "bool_or_struct")]
192 acknowledgements: SourceAcknowledgementsConfig,
193}
194
195impl FluentTcpConfig {
196 fn build(
197 &self,
198 cx: SourceContext,
199 log_namespace: LogNamespace,
200 ) -> crate::Result<super::Source> {
201 let source = FluentSource::new(log_namespace);
202 let shutdown_secs = Duration::from_secs(30);
203 let tls_config = self.tls.as_ref().map(|tls| tls.tls_config.clone());
204 let tls_client_metadata_key = self
205 .tls
206 .as_ref()
207 .and_then(|tls| tls.client_metadata_key.clone())
208 .and_then(|k| k.path);
209 let tls = MaybeTlsSettings::from_config(tls_config.as_ref(), true)?;
210 source.run(
211 self.address,
212 self.keepalive,
213 shutdown_secs,
214 tls,
215 None, tls_client_metadata_key,
217 self.receive_buffer_bytes,
218 None,
219 cx,
220 self.acknowledgements,
221 self.connection_limit,
222 self.permit_origin.clone().map(Into::into),
223 FluentConfig::NAME,
224 log_namespace,
225 )
226 }
227}
228
229#[configurable_component]
231#[derive(Clone, Debug)]
232#[serde(deny_unknown_fields)]
233#[cfg(unix)]
234pub struct FluentUnixConfig {
235 #[configurable(metadata(docs::examples = "/path/to/socket"))]
239 pub path: std::path::PathBuf,
240
241 #[configurable(metadata(docs::examples = 0o777))]
246 #[configurable(metadata(docs::examples = 0o600))]
247 #[configurable(metadata(docs::examples = 508))]
248 pub socket_file_mode: Option<u32>,
249}
250
251#[cfg(unix)]
252impl FluentUnixConfig {
253 fn build(
254 &self,
255 cx: SourceContext,
256 log_namespace: LogNamespace,
257 ) -> crate::Result<super::Source> {
258 let source = FluentSource::new(log_namespace);
259
260 crate::sources::util::build_unix_stream_source(
261 self.path.clone(),
262 self.socket_file_mode,
263 source.decoder(),
264 move |events, host| source.handle_events_impl(events, host.into()),
265 cx.shutdown,
266 cx.out,
267 )
268 }
269}
270
271impl GenerateConfig for FluentConfig {
272 fn generate_config() -> toml::Value {
273 toml::Value::try_from(Self {
274 mode: FluentMode::Tcp(FluentTcpConfig {
275 address: SocketListenAddr::SocketAddr("0.0.0.0:24224".parse().unwrap()),
276 keepalive: None,
277 permit_origin: None,
278 tls: None,
279 receive_buffer_bytes: None,
280 acknowledgements: Default::default(),
281 connection_limit: Some(2),
282 }),
283 log_namespace: None,
284 })
285 .unwrap()
286 }
287}
288
289#[async_trait::async_trait]
290#[typetag::serde(name = "fluent")]
291impl SourceConfig for FluentConfig {
292 async fn build(&self, cx: SourceContext) -> crate::Result<super::Source> {
293 let log_namespace = cx.log_namespace(self.log_namespace);
294 match &self.mode {
295 FluentMode::Tcp(t) => t.build(cx, log_namespace),
296 #[cfg(unix)]
297 FluentMode::Unix(u) => u.build(cx, log_namespace),
298 }
299 }
300
301 fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
302 let log_namespace = global_log_namespace.merge(self.log_namespace);
303 let schema_definition = self.schema_definition(log_namespace);
304
305 vec![SourceOutput::new_maybe_logs(
306 DataType::Log,
307 schema_definition,
308 )]
309 }
310
311 fn resources(&self) -> Vec<Resource> {
312 match &self.mode {
313 FluentMode::Tcp(tcp) => vec![tcp.address.as_tcp_resource()],
314 #[cfg(unix)]
315 FluentMode::Unix(_) => vec![],
316 }
317 }
318
319 fn can_acknowledge(&self) -> bool {
320 matches!(self.mode, FluentMode::Tcp(_))
321 }
322}
323
324impl FluentConfig {
325 fn schema_definition(&self, log_namespace: LogNamespace) -> Definition {
327 let host_key = log_schema()
329 .host_key()
330 .cloned()
331 .map(LegacyKey::InsertIfEmpty);
332
333 let tag_key = parse_value_path("tag").ok().map(LegacyKey::Overwrite);
334
335 let tls_client_metadata_path = match &self.mode {
336 FluentMode::Tcp(tcp) => tcp
337 .tls
338 .as_ref()
339 .and_then(|tls| tls.client_metadata_key.as_ref())
340 .and_then(|k| k.path.clone())
341 .map(LegacyKey::Overwrite),
342 #[cfg(unix)]
343 FluentMode::Unix(_) => None,
344 };
345
346 let mut schema_definition = BytesDeserializerConfig
349 .schema_definition(log_namespace)
350 .with_standard_vector_source_metadata()
351 .with_source_metadata(
352 FluentConfig::NAME,
353 host_key,
354 &owned_value_path!("host"),
355 Kind::bytes(),
356 Some("host"),
357 )
358 .with_source_metadata(
359 FluentConfig::NAME,
360 tag_key,
361 &owned_value_path!("tag"),
362 Kind::bytes(),
363 None,
364 )
365 .with_source_metadata(
366 FluentConfig::NAME,
367 None,
368 &owned_value_path!("timestamp"),
369 Kind::timestamp(),
370 Some("timestamp"),
371 )
372 .with_source_metadata(
374 FluentConfig::NAME,
375 None,
376 &owned_value_path!("record"),
377 Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
378 None,
379 )
380 .with_source_metadata(
381 Self::NAME,
382 tls_client_metadata_path,
383 &owned_value_path!("tls_client_metadata"),
384 Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
385 None,
386 );
387
388 if log_namespace == LogNamespace::Legacy {
390 schema_definition = schema_definition.unknown_fields(Kind::bytes());
391 }
392
393 schema_definition
394 }
395}
396
397#[derive(Debug, Clone)]
398struct FluentSource {
399 log_namespace: LogNamespace,
400 legacy_host_key_path: Option<OwnedValuePath>,
401}
402
403impl FluentSource {
404 fn new(log_namespace: LogNamespace) -> Self {
405 Self {
406 log_namespace,
407 legacy_host_key_path: log_schema().host_key().cloned(),
408 }
409 }
410
411 fn handle_events_impl(&self, events: &mut [Event], host: Value) {
412 for event in events {
413 let log = event.as_mut_log();
414
415 let legacy_host_key = self
416 .legacy_host_key_path
417 .as_ref()
418 .map(LegacyKey::InsertIfEmpty);
419
420 self.log_namespace.insert_source_metadata(
421 FluentConfig::NAME,
422 log,
423 legacy_host_key,
424 path!("host"),
425 host.clone(),
426 );
427 }
428 }
429}
430
431impl TcpSource for FluentSource {
432 type Error = DecodeError;
433 type Item = FluentFrame;
434 type Decoder = FluentDecoder;
435 type Acker = FluentAcker;
436
437 fn decoder(&self) -> Self::Decoder {
438 FluentDecoder::new(self.log_namespace)
439 }
440
441 fn handle_events(&self, events: &mut [Event], host: SocketAddr) {
442 self.handle_events_impl(events, host.ip().to_string().into())
443 }
444
445 fn build_acker(&self, frame: &[Self::Item]) -> Self::Acker {
446 FluentAcker::new(frame)
447 }
448}
449
450#[derive(Debug)]
451pub enum DecodeError {
452 IO(io::Error),
453 Decode(decode::Error),
454 UnknownCompression(String),
455 UnexpectedValue(rmpv::Value),
456 FrameTooLarge {
461 size: usize,
462 max: usize,
463 },
464}
465
466impl std::fmt::Display for DecodeError {
467 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468 match self {
469 DecodeError::IO(err) => write!(f, "{err}"),
470 DecodeError::Decode(err) => write!(f, "{err}"),
471 DecodeError::UnknownCompression(compression) => {
472 write!(f, "unknown compression: {compression}")
473 }
474 DecodeError::UnexpectedValue(value) => {
475 write!(f, "unexpected msgpack value, ignoring: {value}")
476 }
477 DecodeError::FrameTooLarge { size, max } => {
478 write!(
479 f,
480 "fluent frame exceeds maximum size before decoding: {size} bytes buffered, limit is {max} bytes"
481 )
482 }
483 }
484 }
485}
486
487impl StreamDecodingError for DecodeError {
488 fn can_continue(&self) -> bool {
489 match self {
490 DecodeError::IO(_) => false,
491 DecodeError::Decode(_) => true,
492 DecodeError::UnknownCompression(_) => true,
493 DecodeError::UnexpectedValue(_) => true,
494 DecodeError::FrameTooLarge { .. } => false,
497 }
498 }
499}
500
501impl From<io::Error> for DecodeError {
502 fn from(e: io::Error) -> Self {
503 DecodeError::IO(e)
504 }
505}
506
507impl From<decode::Error> for DecodeError {
508 fn from(e: decode::Error) -> Self {
509 DecodeError::Decode(e)
510 }
511}
512
513#[derive(Debug, Clone)]
514struct FluentDecoder {
515 log_namespace: LogNamespace,
516 max_frame_size: usize,
520}
521
522impl FluentDecoder {
523 fn new(log_namespace: LogNamespace) -> Self {
524 Self {
525 log_namespace,
526 max_frame_size: max_decompressed_size_bytes(),
527 }
528 }
529
530 fn handle_message(
531 &mut self,
532 message: Result<FluentMessage, DecodeError>,
533 byte_size: usize,
534 ) -> Result<Option<(FluentFrame, usize)>, DecodeError> {
535 let log_namespace = &self.log_namespace;
536
537 match message? {
538 FluentMessage::Message(tag, timestamp, record) => {
539 let event = Event::from(FluentEvent {
540 tag,
541 timestamp,
542 record,
543 log_namespace,
544 });
545 let frame = FluentFrame {
546 events: smallvec![event],
547 chunk: None,
548 };
549 Ok(Some((frame, byte_size)))
550 }
551 FluentMessage::MessageWithOptions(tag, timestamp, record, options) => {
552 let event = Event::from(FluentEvent {
553 tag,
554 timestamp,
555 record,
556 log_namespace,
557 });
558 let frame = FluentFrame {
559 events: smallvec![event],
560 chunk: options.chunk,
561 };
562 Ok(Some((frame, byte_size)))
563 }
564 FluentMessage::Forward(tag, entries) => {
565 let events = entries
566 .into_iter()
567 .map(|FluentEntry(timestamp, record)| {
568 Event::from(FluentEvent {
569 tag: tag.clone(),
570 timestamp,
571 record,
572 log_namespace,
573 })
574 })
575 .collect();
576 let frame = FluentFrame {
577 events,
578 chunk: None,
579 };
580 Ok(Some((frame, byte_size)))
581 }
582 FluentMessage::ForwardWithOptions(tag, entries, options) => {
583 let events = entries
584 .into_iter()
585 .map(|FluentEntry(timestamp, record)| {
586 Event::from(FluentEvent {
587 tag: tag.clone(),
588 timestamp,
589 record,
590 log_namespace,
591 })
592 })
593 .collect();
594 let frame = FluentFrame {
595 events,
596 chunk: options.chunk,
597 };
598 Ok(Some((frame, byte_size)))
599 }
600 FluentMessage::PackedForward(tag, bin) => {
601 let mut buf = BytesMut::from(&bin[..]);
602
603 let mut events = smallvec![];
604 while let Some(FluentEntry(timestamp, record)) =
605 FluentEntryStreamDecoder.decode(&mut buf)?
606 {
607 events.push(Event::from(FluentEvent {
608 tag: tag.clone(),
609 timestamp,
610 record,
611 log_namespace,
612 }));
613 }
614 let frame = FluentFrame {
615 events,
616 chunk: None,
617 };
618 Ok(Some((frame, byte_size)))
619 }
620 FluentMessage::PackedForwardWithOptions(tag, bin, options) => {
621 let buf = match options.compressed.as_deref() {
622 Some("gzip") => CappedDecoder::gzip(io::Cursor::new(bin.into_vec()))
625 .decompress()
626 .map_err(Into::into),
627 Some("text") | None => Ok(bin.into_vec()),
628 Some(s) => Err(DecodeError::UnknownCompression(s.to_owned())),
629 }?;
630
631 let mut buf = BytesMut::from(&buf[..]);
632
633 let mut events = smallvec![];
634 while let Some(FluentEntry(timestamp, record)) =
635 FluentEntryStreamDecoder.decode(&mut buf)?
636 {
637 events.push(Event::from(FluentEvent {
638 tag: tag.clone(),
639 timestamp,
640 record,
641 log_namespace,
642 }));
643 }
644 let frame = FluentFrame {
645 events,
646 chunk: options.chunk,
647 };
648 Ok(Some((frame, byte_size)))
649 }
650 FluentMessage::Heartbeat(rmpv::Value::Nil) => Ok(None),
651 FluentMessage::Heartbeat(value) => Err(DecodeError::UnexpectedValue(value)),
652 }
653 }
654}
655
656impl Decoder for FluentDecoder {
657 type Item = (FluentFrame, usize);
658 type Error = DecodeError;
659
660 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
661 loop {
662 if src.is_empty() {
663 return Ok(None);
664 }
665
666 let (byte_size, res) = {
667 let mut des = Deserializer::new(io::Cursor::new(&src[..]));
668
669 let res = Deserialize::deserialize(&mut des).map_err(DecodeError::Decode);
670
671 if let Err(DecodeError::Decode(
673 decode::Error::InvalidDataRead(ref custom)
674 | decode::Error::InvalidMarkerRead(ref custom),
675 )) = res
676 && custom.kind() == io::ErrorKind::UnexpectedEof
677 {
678 if src.len() > self.max_frame_size {
684 return Err(DecodeError::FrameTooLarge {
685 size: src.len(),
686 max: self.max_frame_size,
687 });
688 }
689 return Ok(None);
690 }
691
692 (des.position() as usize, res)
693 };
694
695 src.advance(byte_size);
696
697 let maybe_item = self.handle_message(res, byte_size).inspect_err(|error| {
698 let base64_encoded_message = BASE64_STANDARD.encode(&src[..]);
699 emit!(FluentMessageDecodeError {
700 error,
701 base64_encoded_message
702 });
703 })?;
704 if let Some(item) = maybe_item {
705 return Ok(Some(item));
706 }
707 }
708 }
709}
710
711#[derive(Clone, Debug)]
713struct FluentEntryStreamDecoder;
714
715impl Decoder for FluentEntryStreamDecoder {
716 type Item = FluentEntry;
717 type Error = DecodeError;
718
719 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
720 if src.is_empty() {
721 return Ok(None);
722 }
723 let (byte_size, res) = {
724 let mut des = Deserializer::new(io::Cursor::new(&src[..]));
725
726 let res = Deserialize::deserialize(&mut des).map_err(DecodeError::Decode);
728
729 if let Err(DecodeError::Decode(decode::Error::InvalidDataRead(ref custom))) = res
730 && custom.kind() == io::ErrorKind::UnexpectedEof
731 {
732 return Ok(None);
733 }
734
735 let byte_size = des.position();
736
737 emit!(FluentMessageReceived { byte_size });
738
739 (byte_size as usize, res)
740 };
741
742 src.advance(byte_size);
743
744 res
745 }
746}
747
748struct FluentAcker {
749 chunks: Vec<String>,
750}
751
752impl FluentAcker {
753 fn new(frames: &[FluentFrame]) -> Self {
754 Self {
755 chunks: frames.iter().filter_map(|f| f.chunk.clone()).collect(),
756 }
757 }
758}
759
760impl TcpSourceAcker for FluentAcker {
761 fn build_ack(self, ack: TcpSourceAck) -> Option<Bytes> {
762 if self.chunks.is_empty() {
763 return None;
764 }
765
766 let mut buf = Vec::new();
767 let mut ser = Serializer::new(&mut buf);
768 let mut ack_map = HashMap::new();
769
770 for chunk in self.chunks {
771 ack_map.clear();
772 if let TcpSourceAck::Ack = ack {
773 ack_map.insert("ack", chunk);
774 };
775 ack_map.serialize(&mut ser).unwrap();
776 }
777 Some(buf.into())
778 }
779}
780
781#[derive(Debug, PartialEq)]
783struct FluentEvent<'a> {
784 tag: FluentTag,
785 timestamp: FluentTimestamp,
786 record: FluentRecord,
787 log_namespace: &'a LogNamespace,
788}
789
790impl From<FluentEvent<'_>> for Event {
791 fn from(frame: FluentEvent) -> Event {
792 LogEvent::from(frame).into()
793 }
794}
795
796struct FluentFrame {
797 events: SmallVec<[Event; 1]>,
798 chunk: Option<String>,
799}
800
801impl From<FluentFrame> for SmallVec<[Event; 1]> {
802 fn from(frame: FluentFrame) -> Self {
803 frame.events
804 }
805}
806
807impl From<FluentEvent<'_>> for LogEvent {
808 fn from(frame: FluentEvent) -> LogEvent {
809 let FluentEvent {
810 tag,
811 timestamp,
812 record,
813 log_namespace,
814 } = frame;
815
816 let mut log = LogEvent::default();
817
818 log_namespace.insert_vector_metadata(
819 &mut log,
820 log_schema().source_type_key(),
821 path!("source_type"),
822 Bytes::from_static(FluentConfig::NAME.as_bytes()),
823 );
824
825 match log_namespace {
826 LogNamespace::Vector => {
827 log.insert(metadata_path!(FluentConfig::NAME, "timestamp"), timestamp);
828 log.insert(metadata_path!("vector", "ingest_timestamp"), Utc::now());
829 }
830 LogNamespace::Legacy => {
831 log.maybe_insert(log_schema().timestamp_key_target_path(), timestamp);
832 }
833 }
834
835 log_namespace.insert_source_metadata(
836 FluentConfig::NAME,
837 &mut log,
838 Some(LegacyKey::Overwrite(path!("tag"))),
839 path!("tag"),
840 tag,
841 );
842
843 for (key, value) in record.into_iter() {
844 let value: Value = value.into();
845 log_namespace.insert_source_metadata(
846 FluentConfig::NAME,
847 &mut log,
848 Some(LegacyKey::Overwrite(path!(key.as_str()))),
849 path!("record", key.as_str()),
850 value,
851 );
852 }
853 log
854 }
855}
856
857#[cfg(test)]
858mod tests {
859 use bytes::BytesMut;
860 use chrono::{DateTime, Utc};
861 use rmp_serde::Serializer;
862 use serde::Serialize;
863 use tokio::{
864 io::{AsyncReadExt, AsyncWriteExt},
865 time::{Duration, error::Elapsed, timeout},
866 };
867 use tokio_util::codec::Decoder;
868 use vector_lib::{assert_event_data_eq, lookup::OwnedTargetPath, schema::Definition};
869 use vrl::event_path;
870 use vrl::value::{ObjectMap, Value, kind::Collection};
871
872 use super::{message::FluentMessageOptions, *};
873 use crate::{
874 SourceSender,
875 config::{SourceConfig, SourceContext},
876 event::EventStatus,
877 test_util::{self, addr::next_addr, trace_init, wait_for_tcp},
878 };
879
880 #[test]
881 fn generate_config() {
882 crate::test_util::test_generate_config::<FluentConfig>();
883 }
884
885 fn mock_event(name: &str, timestamp: &str) -> Event {
891 Event::Log(LogEvent::from(ObjectMap::from([
892 ("message".into(), Value::from(name)),
893 (
894 log_schema().source_type_key().unwrap().to_string().into(),
895 Value::from(FluentConfig::NAME),
896 ),
897 ("tag".into(), Value::from("tag.name")),
898 (
899 "timestamp".into(),
900 Value::Timestamp(DateTime::parse_from_rfc3339(timestamp).unwrap().into()),
901 ),
902 ])))
903 }
904
905 #[test]
906 fn decode_message_mode() {
907 let message: Vec<u8> = vec![
913 147, 168, 116, 97, 103, 46, 110, 97, 109, 101, 206, 85, 236, 230, 248, 129, 167, 109,
914 101, 115, 115, 97, 103, 101, 163, 98, 97, 114,
915 ];
916
917 let expected = mock_event("bar", "2015-09-07T01:23:04Z");
918 let got = decode_all(message.clone()).unwrap();
919 assert_event_data_eq!(got.0[0], expected);
920 assert_eq!(got.1, message.len());
921 }
922
923 #[test]
924 fn decode_message_mode_with_options() {
925 let message: Vec<u8> = vec![
932 148, 168, 116, 97, 103, 46, 110, 97, 109, 101, 206, 85, 236, 230, 248, 129, 167, 109,
933 101, 115, 115, 97, 103, 101, 163, 98, 97, 114, 129, 164, 115, 105, 122, 101, 1,
934 ];
935
936 let expected = mock_event("bar", "2015-09-07T01:23:04Z");
937 let got = decode_all(message.clone()).unwrap();
938 assert_eq!(got.1, message.len());
939 assert_event_data_eq!(got.0[0], expected);
940 }
941
942 #[test]
943 fn decode_forward_mode() {
944 let message: Vec<u8> = vec![
953 146, 168, 116, 97, 103, 46, 110, 97, 109, 101, 147, 146, 206, 85, 236, 230, 248, 129,
954 167, 109, 101, 115, 115, 97, 103, 101, 163, 102, 111, 111, 146, 206, 85, 236, 230, 249,
955 129, 167, 109, 101, 115, 115, 97, 103, 101, 163, 98, 97, 114, 146, 206, 85, 236, 230,
956 250, 129, 167, 109, 101, 115, 115, 97, 103, 101, 163, 98, 97, 122,
957 ];
958
959 let expected = [
960 mock_event("foo", "2015-09-07T01:23:04Z"),
961 mock_event("bar", "2015-09-07T01:23:05Z"),
962 mock_event("baz", "2015-09-07T01:23:06Z"),
963 ];
964 let got = decode_all(message.clone()).unwrap();
965
966 assert_eq!(got.1, message.len());
967 assert_event_data_eq!(got.0[0], expected[0]);
968 assert_event_data_eq!(got.0[1], expected[1]);
969 assert_event_data_eq!(got.0[2], expected[2]);
970 }
971
972 #[test]
973 fn decode_forward_mode_with_options() {
974 let message: Vec<u8> = vec![
984 147, 168, 116, 97, 103, 46, 110, 97, 109, 101, 147, 146, 206, 85, 236, 230, 248, 129,
985 167, 109, 101, 115, 115, 97, 103, 101, 163, 102, 111, 111, 146, 206, 85, 236, 230, 249,
986 129, 167, 109, 101, 115, 115, 97, 103, 101, 163, 98, 97, 114, 146, 206, 85, 236, 230,
987 250, 129, 167, 109, 101, 115, 115, 97, 103, 101, 163, 98, 97, 122, 129, 164, 115, 105,
988 122, 101, 3,
989 ];
990
991 let expected = [
992 mock_event("foo", "2015-09-07T01:23:04Z"),
993 mock_event("bar", "2015-09-07T01:23:05Z"),
994 mock_event("baz", "2015-09-07T01:23:06Z"),
995 ];
996
997 let got = decode_all(message.clone()).unwrap();
998
999 assert_eq!(got.1, message.len());
1000
1001 assert_event_data_eq!(got.0[0], expected[0]);
1002 assert_event_data_eq!(got.0[1], expected[1]);
1003 assert_event_data_eq!(got.0[2], expected[2]);
1004 }
1005
1006 #[test]
1007 fn decode_packed_forward_mode() {
1008 let message: Vec<u8> = vec![
1018 147, 168, 116, 97, 103, 46, 110, 97, 109, 101, 196, 57, 146, 206, 85, 236, 230, 248,
1019 129, 167, 109, 101, 115, 115, 97, 103, 101, 163, 102, 111, 111, 146, 206, 85, 236, 230,
1020 249, 129, 167, 109, 101, 115, 115, 97, 103, 101, 163, 98, 97, 114, 146, 206, 85, 236,
1021 230, 250, 129, 167, 109, 101, 115, 115, 97, 103, 101, 163, 98, 97, 122, 129, 167, 109,
1022 101, 115, 115, 97, 103, 101, 163, 102, 111, 111,
1023 ];
1024
1025 let expected = [
1026 mock_event("foo", "2015-09-07T01:23:04Z"),
1027 mock_event("bar", "2015-09-07T01:23:05Z"),
1028 mock_event("baz", "2015-09-07T01:23:06Z"),
1029 ];
1030
1031 let got = decode_all(message.clone()).unwrap();
1032
1033 assert_eq!(got.1, message.len());
1034 assert_event_data_eq!(got.0[0], expected[0]);
1035 assert_event_data_eq!(got.0[1], expected[1]);
1036 assert_event_data_eq!(got.0[2], expected[2]);
1037 }
1038
1039 #[test]
1041 fn decode_compressed_packed_forward_mode() {
1042 let message: Vec<u8> = vec![
1053 147, 168, 116, 97, 103, 46, 110, 97, 109, 101, 196, 55, 31, 139, 8, 0, 245, 10, 168,
1054 96, 0, 3, 155, 116, 46, 244, 205, 179, 31, 141, 203, 115, 83, 139, 139, 19, 211, 83,
1055 23, 167, 229, 231, 79, 2, 9, 253, 68, 8, 37, 37, 22, 129, 133, 126, 33, 11, 85, 1, 0,
1056 53, 3, 158, 28, 57, 0, 0, 0, 129, 170, 99, 111, 109, 112, 114, 101, 115, 115, 101, 100,
1057 164, 103, 122, 105, 112,
1058 ];
1059
1060 let expected = [
1061 mock_event("foo", "2015-09-07T01:23:04Z"),
1062 mock_event("bar", "2015-09-07T01:23:05Z"),
1063 mock_event("baz", "2015-09-07T01:23:06Z"),
1064 ];
1065
1066 let got = decode_all(message.clone()).unwrap();
1067
1068 assert_eq!(got.1, message.len());
1069 assert_event_data_eq!(got.0[0], expected[0]);
1070 assert_event_data_eq!(got.0[1], expected[1]);
1071 assert_event_data_eq!(got.0[2], expected[2]);
1072 }
1073
1074 fn decode_all(message: Vec<u8>) -> Result<(SmallVec<[Event; 1]>, usize), DecodeError> {
1075 let mut buf = BytesMut::from(&message[..]);
1076
1077 let mut decoder = FluentDecoder::new(LogNamespace::default());
1078
1079 let (frame, byte_size) = decoder.decode(&mut buf)?.unwrap();
1080 Ok((frame.into(), byte_size))
1081 }
1082
1083 #[test]
1084 fn decode_incomplete_frame_requests_more_data() {
1085 let partial: Vec<u8> = vec![0x92, 0xb0, b't', b'a', b'g'];
1089 let mut buf = BytesMut::from(&partial[..]);
1090 let mut decoder = FluentDecoder::new(LogNamespace::default());
1091 assert!(matches!(decoder.decode(&mut buf), Ok(None)));
1092 assert_eq!(buf.len(), partial.len());
1094 }
1095
1096 #[test]
1097 fn decode_oversized_frame_is_rejected() {
1098 let max_frame_size = 8;
1104 let partial: Vec<u8> = vec![0x92, 0xb0, b't', b'a', b'g', b'.', b'n', b'a', b'm', b'e'];
1105 assert!(partial.len() > max_frame_size);
1106
1107 let mut buf = BytesMut::from(&partial[..]);
1108 let mut decoder = FluentDecoder {
1109 log_namespace: LogNamespace::default(),
1110 max_frame_size,
1111 };
1112
1113 let error = match decoder.decode(&mut buf) {
1114 Err(error) => error,
1115 Ok(_) => panic!("expected FrameTooLarge error, got Ok"),
1116 };
1117 assert!(
1118 matches!(error, DecodeError::FrameTooLarge { size, max } if size == partial.len() && max == max_frame_size),
1119 "unexpected error: {error:?}"
1120 );
1121 assert!(!error.can_continue());
1123 }
1124
1125 #[tokio::test]
1126 async fn ack_delivered_without_chunk() {
1127 let (result, output) = check_acknowledgements(EventStatus::Delivered, false).await;
1128 assert!(result.is_err()); assert!(output.is_empty());
1130 }
1131
1132 #[tokio::test]
1133 async fn ack_delivered_with_chunk() {
1134 let (result, output) = check_acknowledgements(EventStatus::Delivered, true).await;
1135 assert_eq!(result.unwrap().unwrap(), output.len());
1136 let expected: Vec<u8> = vec![0x81, 0xa3, 0x61, 0x63]; assert_eq!(output[..expected.len()], expected);
1138 }
1139
1140 #[tokio::test]
1141 async fn ack_failed_without_chunk() {
1142 let (result, output) = check_acknowledgements(EventStatus::Rejected, false).await;
1143 assert_eq!(result.unwrap().unwrap(), output.len());
1144 assert!(output.is_empty());
1145 }
1146
1147 #[tokio::test]
1148 async fn ack_failed_with_chunk() {
1149 let (result, output) = check_acknowledgements(EventStatus::Rejected, true).await;
1150 assert_eq!(result.unwrap().unwrap(), output.len());
1151 let expected: Vec<u8> = vec![0x80]; assert_eq!(output, expected);
1153 }
1154
1155 async fn check_acknowledgements(
1156 status: EventStatus,
1157 with_chunk: bool,
1158 ) -> (Result<Result<usize, std::io::Error>, Elapsed>, Bytes) {
1159 trace_init();
1160
1161 let (sender, recv) = SourceSender::new_test_finalize(status);
1162 let (_guard, address) = next_addr();
1163 let source = FluentConfig {
1164 mode: FluentMode::Tcp(FluentTcpConfig {
1165 address: address.into(),
1166 tls: None,
1167 keepalive: None,
1168 permit_origin: None,
1169 receive_buffer_bytes: None,
1170 acknowledgements: true.into(),
1171 connection_limit: None,
1172 }),
1173 log_namespace: None,
1174 }
1175 .build(SourceContext::new_test(sender, None))
1176 .await
1177 .unwrap();
1178 tokio::spawn(source);
1179 wait_for_tcp(address).await;
1180
1181 let msg = uuid::Uuid::new_v4().to_string();
1182 let tag = uuid::Uuid::new_v4().to_string();
1183 let req = build_req(&tag, &[("field", &msg)], with_chunk);
1184
1185 let sender = tokio::spawn(async move {
1186 let mut socket = tokio::net::TcpStream::connect(address).await.unwrap();
1187 socket.write_all(&req).await.unwrap();
1188
1189 let mut output = BytesMut::new();
1190 (
1191 timeout(Duration::from_millis(250), socket.read_buf(&mut output)).await,
1192 output,
1193 )
1194 });
1195 let events = test_util::collect_n(recv, 1).await;
1196 let (result, output) = sender.await.unwrap();
1197
1198 assert_eq!(events.len(), 1);
1199 let log = events[0].as_log();
1200 assert_eq!(log.get(event_path!("field")).unwrap(), &msg.into());
1201 assert!(matches!(
1202 log.get(event_path!("host")).unwrap(),
1203 Value::Bytes(_)
1204 ));
1205 assert!(matches!(
1206 log.get(event_path!("timestamp")).unwrap(),
1207 Value::Timestamp(_)
1208 ));
1209 assert_eq!(log.get(event_path!("tag")).unwrap(), &tag.into());
1210
1211 (result, output.into())
1212 }
1213
1214 fn build_req(tag: &str, fields: &[(&str, &str)], with_chunk: bool) -> Vec<u8> {
1215 let mut record = FluentRecord::default();
1216 for (tag, value) in fields {
1217 record.insert((*tag).into(), rmpv::Value::String((*value).into()).into());
1218 }
1219 let chunk = with_chunk.then(|| BASE64_STANDARD.encode(uuid::Uuid::new_v4().as_bytes()));
1220 let req = FluentMessage::MessageWithOptions(
1221 tag.into(),
1222 FluentTimestamp::Unix(Utc::now()),
1223 record,
1224 FluentMessageOptions {
1225 chunk,
1226 ..Default::default()
1227 },
1228 );
1229 let mut buf = Vec::new();
1230 req.serialize(&mut Serializer::new(&mut buf)).unwrap();
1231 buf
1232 }
1233
1234 #[test]
1235 fn output_schema_definition_vector_namespace() {
1236 let config = FluentConfig {
1237 mode: FluentMode::Tcp(FluentTcpConfig {
1238 address: SocketListenAddr::SocketAddr("0.0.0.0:24224".parse().unwrap()),
1239 tls: None,
1240 keepalive: None,
1241 permit_origin: None,
1242 receive_buffer_bytes: None,
1243 acknowledgements: false.into(),
1244 connection_limit: None,
1245 }),
1246 log_namespace: Some(true),
1247 };
1248
1249 let definitions = config
1250 .outputs(LogNamespace::Vector)
1251 .remove(0)
1252 .schema_definition(true);
1253
1254 let expected_definition =
1255 Definition::new_with_default_metadata(Kind::bytes(), [LogNamespace::Vector])
1256 .with_meaning(OwnedTargetPath::event_root(), "message")
1257 .with_metadata_field(
1258 &owned_value_path!("vector", "source_type"),
1259 Kind::bytes(),
1260 None,
1261 )
1262 .with_metadata_field(&owned_value_path!("fluent", "tag"), Kind::bytes(), None)
1263 .with_metadata_field(
1264 &owned_value_path!("fluent", "timestamp"),
1265 Kind::timestamp(),
1266 Some("timestamp"),
1267 )
1268 .with_metadata_field(
1269 &owned_value_path!("fluent", "record"),
1270 Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
1271 None,
1272 )
1273 .with_metadata_field(
1274 &owned_value_path!("vector", "ingest_timestamp"),
1275 Kind::timestamp(),
1276 None,
1277 )
1278 .with_metadata_field(
1279 &owned_value_path!("fluent", "host"),
1280 Kind::bytes(),
1281 Some("host"),
1282 )
1283 .with_metadata_field(
1284 &owned_value_path!("fluent", "tls_client_metadata"),
1285 Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
1286 None,
1287 );
1288
1289 assert_eq!(definitions, Some(expected_definition))
1290 }
1291
1292 #[test]
1293 fn output_schema_definition_legacy_namespace() {
1294 let config = FluentConfig {
1295 mode: FluentMode::Tcp(FluentTcpConfig {
1296 address: SocketListenAddr::SocketAddr("0.0.0.0:24224".parse().unwrap()),
1297 tls: None,
1298 keepalive: None,
1299 permit_origin: None,
1300 receive_buffer_bytes: None,
1301 acknowledgements: false.into(),
1302 connection_limit: None,
1303 }),
1304 log_namespace: None,
1305 };
1306
1307 let definitions = config
1308 .outputs(LogNamespace::Legacy)
1309 .remove(0)
1310 .schema_definition(true);
1311
1312 let expected_definition = Definition::new_with_default_metadata(
1313 Kind::object(Collection::empty()),
1314 [LogNamespace::Legacy],
1315 )
1316 .with_event_field(
1317 &owned_value_path!("message"),
1318 Kind::bytes(),
1319 Some("message"),
1320 )
1321 .with_event_field(&owned_value_path!("source_type"), Kind::bytes(), None)
1322 .with_event_field(&owned_value_path!("tag"), Kind::bytes(), None)
1323 .with_event_field(&owned_value_path!("timestamp"), Kind::timestamp(), None)
1324 .with_event_field(&owned_value_path!("host"), Kind::bytes(), Some("host"))
1325 .unknown_fields(Kind::bytes());
1326
1327 assert_eq!(definitions, Some(expected_definition))
1328 }
1329}
1330
1331#[cfg(all(test, feature = "fluent-integration-tests"))]
1332mod integration_tests {
1333 use std::{fs::File, io::Write, net::SocketAddr, time::Duration};
1334
1335 use futures::Stream;
1336 use tokio::time::sleep;
1337 use vector_lib::event::{Event, EventStatus};
1338 use vrl::event_path;
1339
1340 use crate::{
1341 SourceSender,
1342 config::{SourceConfig, SourceContext},
1343 docker::Container,
1344 sources::fluent::{FluentConfig, FluentMode, FluentTcpConfig},
1345 test_util::{
1346 addr::{PortGuard, next_addr, next_addr_for_ip},
1347 collect_ready,
1348 components::{SOCKET_PUSH_SOURCE_TAGS, assert_source_compliance},
1349 random_string, wait_for_tcp,
1350 },
1351 };
1352
1353 const FLUENT_BIT_IMAGE: &str = "fluent/fluent-bit";
1354 const FLUENT_BIT_TAG: &str = "1.7";
1355 const FLUENTD_IMAGE: &str = "fluent/fluentd";
1356 const FLUENTD_TAG: &str = "v1.12";
1357
1358 fn make_file(name: &str, content: &str) -> tempfile::TempDir {
1359 let dir = tempfile::tempdir().unwrap();
1360 let mut file = File::create(dir.path().join(name)).unwrap();
1361 write!(&mut file, "{content}").unwrap();
1362 dir
1363 }
1364
1365 #[tokio::test]
1366 async fn fluentbit() {
1367 test_fluentbit(EventStatus::Delivered).await;
1368 }
1369
1370 #[tokio::test]
1371 async fn fluentbit_rejection() {
1372 test_fluentbit(EventStatus::Rejected).await;
1373 }
1374
1375 async fn test_fluentbit(status: EventStatus) {
1376 assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async move {
1377 let (_guard, test_address) = next_addr();
1378 let (out, source_address, _guard) = source(status).await;
1379
1380 let dir = make_file(
1381 "fluent-bit.conf",
1382 &format!(
1383 r#"
1384[SERVICE]
1385 Grace 0
1386 Flush 1
1387 Daemon off
1388
1389[INPUT]
1390 Name http
1391 Host {listen_host}
1392 Port {listen_port}
1393
1394[OUTPUT]
1395 Name forward
1396 Match *
1397 Host host.docker.internal
1398 Port {send_port}
1399 Require_ack_response true
1400 "#,
1401 listen_host = test_address.ip(),
1402 listen_port = test_address.port(),
1403 send_port = source_address.port(),
1404 ),
1405 );
1406
1407 let msg = random_string(64);
1408 let body = serde_json::json!({ "message": msg });
1409
1410 let events = Container::new(FLUENT_BIT_IMAGE, FLUENT_BIT_TAG)
1411 .bind(dir.path().display(), "/fluent-bit/etc")
1412 .run(async move {
1413 wait_for_tcp(test_address).await;
1414 reqwest::Client::new()
1415 .post(format!("http://{test_address}/"))
1416 .header("content-type", "application/json")
1417 .body(body.to_string())
1418 .send()
1419 .await
1420 .unwrap();
1421 sleep(Duration::from_secs(2)).await;
1422
1423 collect_ready(out).await
1424 })
1425 .await;
1426
1427 assert_eq!(events.len(), 1);
1428 let log = events[0].as_log();
1429 assert_eq!(log["tag"], "http.0".into());
1430 assert_eq!(log["message"], msg.into());
1431 assert!(log.get(event_path!("timestamp")).is_some());
1432 assert!(log.get(event_path!("host")).is_some());
1433 })
1434 .await;
1435 }
1436
1437 #[tokio::test]
1438 async fn fluentd() {
1439 test_fluentd(EventStatus::Delivered, "").await;
1440 }
1441
1442 #[tokio::test]
1443 async fn fluentd_gzip() {
1444 test_fluentd(EventStatus::Delivered, "compress gzip").await;
1445 }
1446
1447 #[tokio::test]
1448 async fn fluentd_rejection() {
1449 test_fluentd(EventStatus::Rejected, "").await;
1450 }
1451
1452 async fn test_fluentd(status: EventStatus, options: &str) {
1453 assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async move {
1454 let (_guard, test_address) = next_addr();
1455 let (out, source_address, _guard) = source(status).await;
1456
1457 let config = format!(
1458 r#"
1459<source>
1460 @type http
1461 bind {http_host}
1462 port {http_port}
1463</source>
1464
1465<match *>
1466 @type forward
1467 <server>
1468 name local
1469 host host.docker.internal
1470 port {port}
1471 </server>
1472 <buffer>
1473 flush_mode immediate
1474 </buffer>
1475 require_ack_response true
1476 ack_response_timeout 1
1477 {options}
1478</match>
1479"#,
1480 http_host = test_address.ip(),
1481 http_port = test_address.port(),
1482 port = source_address.port(),
1483 options = options
1484 );
1485
1486 let dir = make_file("fluent.conf", &config);
1487
1488 let msg = random_string(64);
1489 let body = serde_json::json!({ "message": msg });
1490
1491 let events = Container::new(FLUENTD_IMAGE, FLUENTD_TAG)
1492 .bind(dir.path().display(), "/fluentd/etc")
1493 .run(async move {
1494 wait_for_tcp(test_address).await;
1495 reqwest::Client::new()
1496 .post(format!("http://{test_address}/"))
1497 .header("content-type", "application/json")
1498 .body(body.to_string())
1499 .send()
1500 .await
1501 .unwrap();
1502 sleep(Duration::from_secs(2)).await;
1503 collect_ready(out).await
1504 })
1505 .await;
1506
1507 assert_eq!(events.len(), 1);
1508 assert_eq!(events[0].as_log()["tag"], "".into());
1509 assert_eq!(events[0].as_log()["message"], msg.into());
1510 assert!(events[0].as_log().get(event_path!("timestamp")).is_some());
1511 assert!(events[0].as_log().get(event_path!("host")).is_some());
1512 })
1513 .await;
1514 }
1515
1516 async fn source(
1517 status: EventStatus,
1518 ) -> (impl Stream<Item = Event> + Unpin, SocketAddr, PortGuard) {
1519 let (sender, recv) = SourceSender::new_test_finalize(status);
1520 let (_guard, address) =
1521 next_addr_for_ip(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED));
1522 tokio::spawn(async move {
1523 FluentConfig {
1524 mode: FluentMode::Tcp(FluentTcpConfig {
1525 address: address.into(),
1526 tls: None,
1527 keepalive: None,
1528 permit_origin: None,
1529 receive_buffer_bytes: None,
1530 acknowledgements: false.into(),
1531 connection_limit: None,
1532 }),
1533 log_namespace: None,
1534 }
1535 .build(SourceContext::new_test(sender, None))
1536 .await
1537 .unwrap()
1538 .await
1539 .unwrap()
1540 });
1541 wait_for_tcp(address).await;
1542 (recv, address, _guard)
1543 }
1544}