1use std::{
2 collections::{BTreeMap, VecDeque},
3 convert::TryFrom,
4 io,
5 net::SocketAddr,
6 num::NonZeroUsize,
7 time::Duration,
8};
9
10use bytes::{Buf, Bytes, BytesMut};
11use smallvec::{SmallVec, smallvec};
12use snafu::{ResultExt, Snafu};
13use tokio_util::codec::Decoder;
14use vector_lib::{
15 codecs::{BytesDeserializerConfig, StreamDecodingError},
16 config::{LegacyKey, LogNamespace},
17 configurable::configurable_component,
18 ipallowlist::IpAllowlistConfig,
19 lookup::{OwnedValuePath, event_path, metadata_path, owned_value_path, path},
20 schema::Definition,
21};
22use vrl::value::{KeyString, Kind, kind::Collection};
23
24use super::util::decompression::{
25 CappedDecoder, max_decompressed_size_bytes, max_zlib_compressed_frame_size_bytes,
26};
27use super::util::net::{SocketListenAddr, TcpSource, TcpSourceAck, TcpSourceAcker};
28use crate::{
29 config::{
30 DataType, GenerateConfig, Resource, SourceAcknowledgementsConfig, SourceConfig,
31 SourceContext, SourceOutput, log_schema,
32 },
33 event::{Event, LogEvent, Value},
34 serde::bool_or_struct,
35 tcp::TcpKeepaliveConfig,
36 tls::{MaybeTlsSettings, TlsSourceConfig},
37 types,
38};
39
40#[configurable_component(source("logstash", "Collect logs from a Logstash agent."))]
42#[derive(Clone, Debug)]
43pub struct LogstashConfig {
44 #[configurable(derived)]
45 address: SocketListenAddr,
46
47 #[configurable(derived)]
48 #[configurable(metadata(docs::advanced))]
49 keepalive: Option<TcpKeepaliveConfig>,
50
51 #[configurable(derived)]
52 pub permit_origin: Option<IpAllowlistConfig>,
53
54 #[configurable(derived)]
55 tls: Option<TlsSourceConfig>,
56
57 #[configurable(metadata(docs::type_unit = "bytes"))]
59 #[configurable(metadata(docs::examples = 65536))]
60 #[configurable(metadata(docs::advanced))]
61 receive_buffer_bytes: Option<usize>,
62
63 #[configurable(metadata(docs::type_unit = "connections"))]
65 #[configurable(metadata(docs::advanced))]
66 connection_limit: Option<u32>,
67
68 #[configurable(derived)]
69 #[serde(default, deserialize_with = "bool_or_struct")]
70 acknowledgements: SourceAcknowledgementsConfig,
71
72 #[configurable(metadata(docs::hidden))]
74 #[serde(default)]
75 log_namespace: Option<bool>,
76}
77
78impl LogstashConfig {
79 fn schema_definition(&self, log_namespace: LogNamespace) -> Definition {
81 let host_key = log_schema()
83 .host_key()
84 .cloned()
85 .map(LegacyKey::InsertIfEmpty);
86
87 let tls_client_metadata_path = self
88 .tls
89 .as_ref()
90 .and_then(|tls| tls.client_metadata_key.as_ref())
91 .and_then(|k| k.path.clone())
92 .map(LegacyKey::Overwrite);
93
94 BytesDeserializerConfig
95 .schema_definition(log_namespace)
96 .with_standard_vector_source_metadata()
97 .with_source_metadata(
98 LogstashConfig::NAME,
99 None,
100 &owned_value_path!("timestamp"),
101 Kind::timestamp().or_undefined(),
102 Some("timestamp"),
103 )
104 .with_source_metadata(
105 LogstashConfig::NAME,
106 host_key,
107 &owned_value_path!("host"),
108 Kind::bytes(),
109 Some("host"),
110 )
111 .with_source_metadata(
112 Self::NAME,
113 tls_client_metadata_path,
114 &owned_value_path!("tls_client_metadata"),
115 Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
116 None,
117 )
118 }
119}
120
121impl Default for LogstashConfig {
122 fn default() -> Self {
123 Self {
124 address: SocketListenAddr::SocketAddr("0.0.0.0:5044".parse().unwrap()),
125 keepalive: None,
126 permit_origin: None,
127 tls: None,
128 receive_buffer_bytes: None,
129 acknowledgements: Default::default(),
130 connection_limit: None,
131 log_namespace: None,
132 }
133 }
134}
135
136impl GenerateConfig for LogstashConfig {
137 fn generate_config() -> toml::Value {
138 toml::Value::try_from(LogstashConfig::default()).unwrap()
139 }
140}
141
142#[async_trait::async_trait]
143#[typetag::serde(name = "logstash")]
144impl SourceConfig for LogstashConfig {
145 async fn build(&self, cx: SourceContext) -> crate::Result<super::Source> {
146 let log_namespace = cx.log_namespace(self.log_namespace);
147 let source = LogstashSource {
148 timestamp_converter: types::Conversion::Timestamp(cx.globals.timezone()),
149 legacy_host_key_path: log_schema().host_key().cloned(),
150 log_namespace,
151 };
152 let shutdown_secs = Duration::from_secs(30);
153 let tls_config = self.tls.as_ref().map(|tls| tls.tls_config.clone());
154 let tls_client_metadata_key = self
155 .tls
156 .as_ref()
157 .and_then(|tls| tls.client_metadata_key.clone())
158 .and_then(|k| k.path);
159
160 let tls = MaybeTlsSettings::from_config(tls_config.as_ref(), true)?;
161 source.run(
162 self.address,
163 self.keepalive,
164 shutdown_secs,
165 tls,
166 None, tls_client_metadata_key,
168 self.receive_buffer_bytes,
169 None,
170 cx,
171 self.acknowledgements,
172 self.connection_limit,
173 self.permit_origin.clone().map(Into::into),
174 LogstashConfig::NAME,
175 log_namespace,
176 )
177 }
178
179 fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
180 vec![SourceOutput::new_maybe_logs(
183 DataType::Log,
184 self.schema_definition(global_log_namespace.merge(self.log_namespace)),
185 )]
186 }
187
188 fn resources(&self) -> Vec<Resource> {
189 vec![self.address.as_tcp_resource()]
190 }
191
192 fn can_acknowledge(&self) -> bool {
193 true
194 }
195}
196
197#[derive(Debug, Clone)]
198struct LogstashSource {
199 timestamp_converter: types::Conversion,
200 log_namespace: LogNamespace,
201 legacy_host_key_path: Option<OwnedValuePath>,
202}
203
204impl TcpSource for LogstashSource {
205 type Error = DecodeError;
206 type Item = LogstashEventFrame;
207 type Decoder = LogstashDecoder;
208 type Acker = LogstashAcker;
209
210 fn decoder(&self) -> Self::Decoder {
211 LogstashDecoder::new()
212 }
213
214 fn handle_events(&self, events: &mut [Event], host: SocketAddr) {
215 let now = chrono::Utc::now();
216 for event in events {
217 let log = event.as_mut_log();
218
219 self.log_namespace.insert_vector_metadata(
220 log,
221 log_schema().source_type_key(),
222 path!("source_type"),
223 Bytes::from_static(LogstashConfig::NAME.as_bytes()),
224 );
225
226 let log_timestamp = log.get(event_path!("@timestamp")).and_then(|timestamp| {
227 self.timestamp_converter
228 .convert::<Value>(timestamp.coerce_to_bytes())
229 .ok()
230 });
231
232 match self.log_namespace {
237 LogNamespace::Vector => {
238 if let Some(timestamp) = log_timestamp {
239 log.insert(metadata_path!(LogstashConfig::NAME, "timestamp"), timestamp);
240 }
241 log.insert(metadata_path!("vector", "ingest_timestamp"), now);
242 }
243 LogNamespace::Legacy => {
244 if let Some(timestamp_key) = log_schema().timestamp_key_target_path() {
245 log.insert(
246 timestamp_key,
247 log_timestamp.unwrap_or_else(|| Value::from(now)),
248 );
249 }
250 }
251 }
252
253 self.log_namespace.insert_source_metadata(
254 LogstashConfig::NAME,
255 log,
256 self.legacy_host_key_path
257 .as_ref()
258 .map(LegacyKey::InsertIfEmpty),
259 path!("host"),
260 host.ip().to_string(),
261 );
262 }
263 }
264
265 fn build_acker(&self, frames: &[Self::Item]) -> Self::Acker {
266 LogstashAcker::new(frames)
267 }
268}
269
270struct LogstashAcker {
271 acknowledgements: SmallVec<[(LogstashProtocolVersion, u32); 1]>,
290}
291
292impl LogstashAcker {
293 fn new(frames: &[LogstashEventFrame]) -> Self {
294 let acknowledgements = frames
295 .iter()
296 .filter(|frame| frame.window_end)
298 .map(|frame| (frame.protocol, frame.sequence_number))
299 .collect();
300
301 Self { acknowledgements }
302 }
303}
304
305impl TcpSourceAcker for LogstashAcker {
306 fn build_ack(self, ack: TcpSourceAck) -> Option<Bytes> {
308 match ack {
309 TcpSourceAck::Ack if !self.acknowledgements.is_empty() => {
310 let mut bytes: Vec<u8> = Vec::with_capacity(self.acknowledgements.len() * 6);
311 for (protocol_version, sequence_number) in self.acknowledgements {
312 bytes.push(protocol_version.into());
313 bytes.push(LogstashFrameType::Ack.into());
314 bytes.extend(sequence_number.to_be_bytes().iter());
315 }
316 Some(Bytes::from(bytes))
317 }
318 _ => None,
319 }
320 }
321}
322
323#[derive(Debug)]
324enum LogstashDecoderReadState {
325 ReadProtocol,
326 ReadType(LogstashProtocolVersion),
327 ReadFrame(LogstashProtocolVersion, LogstashFrameType),
328 PendingFrames(VecDeque<(LogstashEventFrame, usize)>),
329}
330
331#[derive(Debug)]
332struct LogstashDecoder {
333 state: LogstashDecoderReadState,
334 window_events_remaining: Option<NonZeroUsize>,
338 nested: bool,
345}
346
347impl LogstashDecoder {
348 const fn new() -> Self {
349 Self::new_with_window_events_remaining(None)
350 }
351
352 const fn new_with_window_events_remaining(
353 window_events_remaining: Option<NonZeroUsize>,
354 ) -> Self {
355 Self {
356 state: LogstashDecoderReadState::ReadProtocol,
357 window_events_remaining,
358 nested: false,
359 }
360 }
361
362 const fn new_nested(window_events_remaining: Option<NonZeroUsize>) -> Self {
363 Self {
364 state: LogstashDecoderReadState::ReadProtocol,
365 window_events_remaining,
366 nested: true,
367 }
368 }
369
370 const fn annotate_frame(&mut self, frame: &mut LogstashEventFrame) {
381 match self.window_events_remaining {
382 Some(remaining) if remaining.get() == 1 => {
383 frame.window_end = true;
384 self.window_events_remaining = None;
385 }
386 Some(remaining) => {
387 frame.window_end = false;
388 self.window_events_remaining = NonZeroUsize::new(remaining.get() - 1); }
390 None => {
391 frame.window_end = true;
394 }
395 }
396 }
397}
398
399#[derive(Debug, Snafu)]
400pub enum DecodeError {
401 #[snafu(display("i/o error: {}", source))]
402 IO { source: io::Error },
403 #[snafu(display("Unknown logstash protocol version: {}", version))]
404 UnknownProtocolVersion { version: char },
405 #[snafu(display("Unknown logstash protocol message type: {}", frame_type))]
406 UnknownFrameType { frame_type: char },
407 #[snafu(display("Failed to decode JSON frame: {}", source))]
408 JsonFrameFailedDecode { source: serde_json::Error },
409 #[snafu(display("Failed to decompress compressed frame: {}", source))]
410 DecompressionFailed { source: io::Error },
411 #[snafu(display(
412 "Received a WindowSize frame before the current window completed ({remaining} events still expected)"
413 ))]
414 PrematureWindowSize { remaining: usize },
415 #[snafu(display("Compressed frame contains a nested compressed frame"))]
416 NestedCompressedFrame,
417}
418
419impl StreamDecodingError for DecodeError {
420 fn can_continue(&self) -> bool {
421 false
427 }
428}
429
430impl From<io::Error> for DecodeError {
431 fn from(source: io::Error) -> Self {
432 DecodeError::IO { source }
433 }
434}
435
436#[derive(Debug, Clone, Copy)]
437enum LogstashProtocolVersion {
438 V1, V2, }
441
442impl From<LogstashProtocolVersion> for u8 {
443 fn from(frame_type: LogstashProtocolVersion) -> u8 {
444 use LogstashProtocolVersion::*;
445
446 match frame_type {
447 V1 => b'1',
448 V2 => b'2',
449 }
450 }
451}
452
453impl TryFrom<u8> for LogstashProtocolVersion {
454 type Error = DecodeError;
455
456 fn try_from(frame_type: u8) -> Result<LogstashProtocolVersion, DecodeError> {
457 use LogstashProtocolVersion::*;
458
459 match frame_type {
460 b'1' => Ok(V1),
461 b'2' => Ok(V2),
462 version => Err(DecodeError::UnknownProtocolVersion {
463 version: version as char,
464 }),
465 }
466 }
467}
468
469#[derive(Debug, Clone, Copy)]
470enum LogstashFrameType {
471 Ack, WindowSize, Data, Json, Compressed, }
477
478impl From<LogstashFrameType> for u8 {
479 fn from(frame_type: LogstashFrameType) -> u8 {
480 use LogstashFrameType::*;
481
482 match frame_type {
483 Ack => b'A',
484 WindowSize => b'W',
485 Data => b'D',
486 Json => b'J',
487 Compressed => b'C',
488 }
489 }
490}
491
492impl TryFrom<u8> for LogstashFrameType {
493 type Error = DecodeError;
494
495 fn try_from(frame_type: u8) -> Result<LogstashFrameType, DecodeError> {
496 use LogstashFrameType::*;
497
498 match frame_type {
499 b'A' => Ok(Ack),
500 b'W' => Ok(WindowSize),
501 b'D' => Ok(Data),
502 b'J' => Ok(Json),
503 b'C' => Ok(Compressed),
504 frame_type => Err(DecodeError::UnknownFrameType {
505 frame_type: frame_type as char,
506 }),
507 }
508 }
509}
510
511#[derive(Debug)]
513struct LogstashEventFrame {
514 protocol: LogstashProtocolVersion,
515 sequence_number: u32,
516 fields: BTreeMap<KeyString, serde_json::Value>,
517 window_end: bool,
520}
521
522struct DecodedCompressedFrames {
523 frames: VecDeque<(LogstashEventFrame, usize)>,
524 window_events_remaining: Option<NonZeroUsize>,
525}
526
527impl Decoder for LogstashDecoder {
530 type Item = (LogstashEventFrame, usize);
531 type Error = DecodeError;
532
533 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
534 loop {
541 self.state = match self.state {
542 LogstashDecoderReadState::PendingFrames(ref mut frames) => {
544 match frames.pop_front() {
545 Some(frame) => return Ok(Some(frame)),
546 None => LogstashDecoderReadState::ReadProtocol,
547 }
548 }
549 LogstashDecoderReadState::ReadProtocol => {
550 if src.remaining() < 1 {
551 return Ok(None);
552 }
553
554 use LogstashProtocolVersion::*;
555
556 match LogstashProtocolVersion::try_from(src.get_u8())? {
557 V1 => LogstashDecoderReadState::ReadType(V1),
558 V2 => LogstashDecoderReadState::ReadType(V2),
559 }
560 }
561 LogstashDecoderReadState::ReadType(protocol) => {
562 if src.remaining() < 1 {
563 return Ok(None);
564 }
565
566 use LogstashFrameType::*;
567
568 match LogstashFrameType::try_from(src.get_u8())? {
569 WindowSize => LogstashDecoderReadState::ReadFrame(protocol, WindowSize),
570 Data => LogstashDecoderReadState::ReadFrame(protocol, Data),
571 Json => LogstashDecoderReadState::ReadFrame(protocol, Json),
572 Compressed => LogstashDecoderReadState::ReadFrame(protocol, Compressed),
573 Ack => LogstashDecoderReadState::ReadFrame(protocol, Ack),
574 }
575 }
576 LogstashDecoderReadState::ReadFrame(_protocol, LogstashFrameType::WindowSize) => {
598 if let Some(remaining) = self.window_events_remaining {
608 return Err(DecodeError::PrematureWindowSize {
609 remaining: remaining.get(),
610 });
611 }
612
613 if src.remaining() < 4 {
614 return Ok(None);
615 }
616
617 let window_size = src.get_u32() as usize;
618 self.window_events_remaining = NonZeroUsize::new(window_size);
619
620 LogstashDecoderReadState::ReadProtocol
621 }
622 LogstashDecoderReadState::ReadFrame(_protocol, LogstashFrameType::Ack) => {
626 if src.remaining() < 4 {
627 return Ok(None);
628 }
629
630 let _sequence_number = src.get_u32();
631
632 LogstashDecoderReadState::ReadProtocol
633 }
634 LogstashDecoderReadState::ReadFrame(protocol, LogstashFrameType::Data) => {
636 let Some((mut frame, byte_size)) = decode_data_frame(protocol, src) else {
637 return Ok(None);
638 };
639 self.annotate_frame(&mut frame);
640
641 LogstashDecoderReadState::PendingFrames([(frame, byte_size)].into())
642 }
643 LogstashDecoderReadState::ReadFrame(protocol, LogstashFrameType::Json) => {
645 let Some((mut frame, byte_size)) = decode_json_frame(protocol, src)? else {
646 return Ok(None);
647 };
648 self.annotate_frame(&mut frame);
649
650 LogstashDecoderReadState::PendingFrames([(frame, byte_size)].into())
651 }
652 LogstashDecoderReadState::ReadFrame(_protocol, LogstashFrameType::Compressed) => {
660 if self.nested {
661 return Err(DecodeError::NestedCompressedFrame);
662 }
663
664 let Some(decoded) = decode_compressed_frame(src, self.window_events_remaining)?
665 else {
666 return Ok(None);
667 };
668 self.window_events_remaining = decoded.window_events_remaining;
669
670 LogstashDecoderReadState::PendingFrames(decoded.frames)
671 }
672 };
673 }
674 }
675}
676
677fn decode_data_frame(
679 protocol: LogstashProtocolVersion,
680 src: &mut BytesMut,
681) -> Option<(LogstashEventFrame, usize)> {
682 let mut rest = src.as_ref();
683
684 if rest.remaining() < 8 {
685 return None;
686 }
687 let sequence_number = rest.get_u32();
688 let pair_count = rest.get_u32();
689 if pair_count == 0 {
690 return None; }
692
693 let mut fields = BTreeMap::<KeyString, serde_json::Value>::new();
694 for _ in 0..pair_count {
695 let (key, value, right) = decode_pair(rest)?;
696 rest = right;
697
698 fields.insert(
699 String::from_utf8_lossy(key).into(),
700 String::from_utf8_lossy(value).into(),
701 );
702 }
703
704 let byte_size = bytes_remaining(src, rest);
705 src.advance(byte_size);
706
707 Some((
708 LogstashEventFrame {
709 protocol,
710 sequence_number,
711 fields,
712 window_end: false,
713 },
714 byte_size,
715 ))
716}
717
718fn decode_pair(mut rest: &[u8]) -> Option<(&[u8], &[u8], &[u8])> {
719 if rest.remaining() < 4 {
720 return None;
721 }
722 let key_length = rest.get_u32() as usize;
723
724 if rest.remaining() < key_length {
725 return None;
726 }
727 let (key, right) = rest.split_at(key_length);
728 rest = right;
729
730 if rest.remaining() < 4 {
731 return None;
732 }
733 let value_length = rest.get_u32() as usize;
734 if rest.remaining() < value_length {
735 return None;
736 }
737 let (value, right) = rest.split_at(value_length);
738 Some((key, value, right))
739}
740
741fn decode_json_frame(
742 protocol: LogstashProtocolVersion,
743 src: &mut BytesMut,
744) -> Result<Option<(LogstashEventFrame, usize)>, DecodeError> {
745 let mut rest = src.as_ref();
746
747 if rest.remaining() < 8 {
748 return Ok(None);
749 }
750 let sequence_number = rest.get_u32();
751 let payload_size = rest.get_u32() as usize;
752
753 if rest.remaining() < payload_size {
754 return Ok(None);
755 }
756
757 let (slice, right) = rest.split_at(payload_size);
758 rest = right;
759
760 let fields: BTreeMap<KeyString, serde_json::Value> =
761 serde_json::from_slice(slice).context(JsonFrameFailedDecodeSnafu {})?;
762
763 let byte_size = bytes_remaining(src, rest);
764 src.advance(byte_size);
765
766 Ok(Some((
767 LogstashEventFrame {
768 protocol,
769 sequence_number,
770 fields,
771 window_end: false,
772 },
773 byte_size,
774 )))
775}
776
777fn decode_compressed_frame(
778 src: &mut BytesMut,
779 window_events_remaining: Option<NonZeroUsize>,
780) -> Result<Option<DecodedCompressedFrames>, DecodeError> {
781 let mut rest = src.as_ref();
782
783 if rest.remaining() < 4 {
784 return Ok(None);
785 }
786 let payload_size = rest.get_u32() as usize;
787 let limit = max_decompressed_size_bytes();
788
789 let compressed_limit = max_zlib_compressed_frame_size_bytes();
794 if payload_size > compressed_limit {
795 return Err(DecodeError::DecompressionFailed {
796 source: io::Error::other(format!(
797 "compressed frame payload size {payload_size} exceeds limit of {compressed_limit} bytes"
798 )),
799 });
800 }
801
802 if rest.remaining() < payload_size {
803 return Ok(None);
804 }
805
806 let (slice, right) = rest.split_at(payload_size);
807 rest = right;
808
809 let res = CappedDecoder::zlib_with_limit(io::Cursor::new(slice), limit)
810 .decompress()
811 .map(|v| BytesMut::from(v.as_slice()))
812 .context(DecompressionFailedSnafu);
813
814 let byte_size = bytes_remaining(src, rest);
815 src.advance(byte_size);
816
817 let mut buf = res?;
818
819 let mut decoder = LogstashDecoder::new_nested(window_events_remaining);
820
821 let mut frames = VecDeque::new();
822
823 while let Some(s) = decoder.decode(&mut buf)? {
824 frames.push_back(s);
825 }
826 Ok(Some(DecodedCompressedFrames {
827 frames,
828 window_events_remaining: decoder.window_events_remaining,
829 }))
830}
831
832fn bytes_remaining(src: &BytesMut, rest: &[u8]) -> usize {
833 let remaining = rest.remaining();
834 src.remaining() - remaining
835}
836
837impl From<LogstashEventFrame> for Event {
838 fn from(frame: LogstashEventFrame) -> Self {
839 Event::Log(LogEvent::from(
840 frame
841 .fields
842 .into_iter()
843 .map(|(key, value)| (key, Value::from(value)))
844 .collect::<BTreeMap<_, _>>(),
845 ))
846 }
847}
848
849impl From<LogstashEventFrame> for SmallVec<[Event; 1]> {
850 fn from(frame: LogstashEventFrame) -> Self {
851 smallvec![frame.into()]
852 }
853}
854
855#[cfg(test)]
856mod test {
857 use std::io::Write;
858
859 use bytes::BufMut;
860 use flate2::{Compression, write::ZlibEncoder};
861 use futures::{Stream, StreamExt, stream};
862 use rand::{Rng, rng};
863 use tokio::io::{AsyncReadExt, AsyncWriteExt};
864 use vector_lib::codecs::ReadyFrames;
865 use vector_lib::lookup::OwnedTargetPath;
866 use vrl::event_path;
867 use vrl::value::kind::Collection;
868
869 use super::*;
870 use crate::{
871 SourceSender,
872 event::EventStatus,
873 test_util::{
874 addr::next_addr,
875 components::{SOCKET_PUSH_SOURCE_TAGS, assert_source_compliance},
876 spawn_collect_n, wait_for_tcp,
877 },
878 };
879
880 #[test]
881 fn generate_config() {
882 crate::test_util::test_generate_config::<LogstashConfig>();
883 }
884
885 #[tokio::test]
886 async fn test_delivered() {
887 test_protocol(EventStatus::Delivered, true).await;
888 }
889
890 #[tokio::test]
891 async fn test_failed() {
892 test_protocol(EventStatus::Rejected, false).await;
893 }
894
895 async fn start_logstash(
896 status: EventStatus,
897 ) -> (SocketAddr, impl Stream<Item = Event> + Unpin) {
898 let (sender, recv) = SourceSender::new_test_finalize(status);
899 let (_guard, address) = next_addr();
900 let source = LogstashConfig {
901 address: address.into(),
902 tls: None,
903 permit_origin: None,
904 keepalive: None,
905 receive_buffer_bytes: None,
906 acknowledgements: true.into(),
907 connection_limit: None,
908 log_namespace: None,
909 }
910 .build(SourceContext::new_test(sender, None))
911 .await
912 .unwrap();
913 tokio::spawn(source);
914 wait_for_tcp(address).await;
915 (address, recv)
916 }
917
918 async fn test_protocol(status: EventStatus, sends_ack: bool) {
919 let events = assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async {
920 let (address, recv) = start_logstash(status).await;
921 spawn_collect_n(
922 send_req(address, &[("message", "Hello, world!")], sends_ack),
923 recv,
924 1,
925 )
926 .await
927 })
928 .await;
929
930 assert_eq!(events.len(), 1);
931 let log = events[0].as_log();
932 assert_eq!(
933 log.get(event_path!("message")).unwrap().to_string_lossy(),
934 "Hello, world!".to_string()
935 );
936 assert_eq!(
937 log.get(event_path!("source_type"))
938 .unwrap()
939 .to_string_lossy(),
940 "logstash".to_string()
941 );
942 assert!(log.get(event_path!("host")).is_some());
943 assert!(log.get(event_path!("timestamp")).is_some());
944 }
945
946 fn push_req(req: &mut BytesMut, seq: u32, pairs: &[(&str, &str)]) {
947 req.put_u8(b'2');
948 req.put_u8(b'D');
949 req.put_u32(seq);
950 req.put_u32(pairs.len() as u32);
951 for (key, value) in pairs {
952 req.put_u32(key.len() as u32);
953 req.put(key.as_bytes());
954 req.put_u32(value.len() as u32);
955 req.put(value.as_bytes());
956 }
957 }
958
959 fn encode_req(seq: u32, pairs: &[(&str, &str)]) -> Bytes {
960 let mut req = BytesMut::new();
961 push_req(&mut req, seq, pairs);
962 req.into()
963 }
964
965 fn push_window_size(req: &mut BytesMut, size: u32) {
966 req.put_u8(b'2');
967 req.put_u8(b'W');
968 req.put_u32(size);
969 }
970
971 fn push_compressed(req: &mut BytesMut, inner: &[u8]) {
972 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
973 encoder.write_all(inner).unwrap();
974 let compressed = encoder.finish().unwrap();
975
976 req.put_u8(b'2');
977 req.put_u8(b'C');
978 req.put_u32(compressed.len() as u32);
979 req.put(compressed.as_slice());
980 }
981
982 fn decode_frames(mut src: BytesMut) -> Vec<(LogstashEventFrame, usize)> {
983 let mut decoder = LogstashDecoder::new();
984 let mut frames = Vec::new();
985
986 while let Some(frame) = decoder.decode(&mut src).unwrap() {
987 frames.push(frame);
988 }
989
990 assert_eq!(src.len(), 0);
991 frames
992 }
993
994 fn decode_acknowledgements(mut ack: Bytes) -> Vec<u32> {
995 let mut acknowledgements = Vec::new();
996
997 while !ack.is_empty() {
998 assert!(
999 ack.len() >= 6,
1000 "ack stream ended with {} trailing bytes",
1001 ack.len()
1002 );
1003 assert_eq!(ack.get_u8(), b'2');
1004 assert_eq!(ack.get_u8(), b'A');
1005 acknowledgements.push(ack.get_u32());
1006 }
1007
1008 acknowledgements
1009 }
1010
1011 fn decoded_sequence_numbers(decoded: &[(LogstashEventFrame, usize)]) -> Vec<u32> {
1012 decoded
1013 .iter()
1014 .map(|(frame, _)| frame.sequence_number)
1015 .collect::<Vec<_>>()
1016 }
1017
1018 fn assert_decoded_sequences(
1019 decoded: &[(LogstashEventFrame, usize)],
1020 expected_sequences: &[u32],
1021 ) {
1022 assert_eq!(decoded_sequence_numbers(decoded), expected_sequences);
1023 }
1024
1025 async fn assert_acknowledgements_for_ready_frames(
1026 decoded: Vec<(LogstashEventFrame, usize)>,
1027 expected_sequences: &[u32],
1028 expected_acknowledgements: &[u32],
1029 ) {
1030 assert_decoded_sequences(&decoded, expected_sequences);
1031
1032 let stream = stream::iter(decoded.into_iter().map(Ok::<_, DecodeError>));
1033 let mut ready = ReadyFrames::with_capacity(stream, 16);
1034 let (frames, _) = ready.next().await.unwrap().unwrap();
1035
1036 let acknowledgements = LogstashAcker::new(&frames)
1040 .build_ack(TcpSourceAck::Ack)
1041 .map_or_else(Vec::new, decode_acknowledgements);
1042
1043 assert!(ready.next().await.is_none());
1044 assert_eq!(acknowledgements, expected_acknowledgements);
1045 }
1046
1047 fn decode_frames_and_assert_sequences(
1048 src: BytesMut,
1049 expected_sequences: &[u32],
1050 ) -> Vec<(LogstashEventFrame, usize)> {
1051 let decoded = decode_frames(src);
1052 assert_decoded_sequences(&decoded, expected_sequences);
1053 decoded
1054 }
1055
1056 fn decode_frames_with_decoder(
1057 decoder: &mut LogstashDecoder,
1058 mut src: BytesMut,
1059 ) -> Vec<(LogstashEventFrame, usize)> {
1060 let mut frames = Vec::new();
1061
1062 while let Some(frame) = decoder.decode(&mut src).unwrap() {
1063 frames.push(frame);
1064 }
1065
1066 assert_eq!(src.len(), 0);
1067 frames
1068 }
1069
1070 fn decode_frames_with_decoder_and_assert_sequences(
1071 decoder: &mut LogstashDecoder,
1072 src: BytesMut,
1073 expected_sequences: &[u32],
1074 ) -> Vec<(LogstashEventFrame, usize)> {
1075 let decoded = decode_frames_with_decoder(decoder, src);
1076 assert_decoded_sequences(&decoded, expected_sequences);
1077 decoded
1078 }
1079
1080 #[test]
1081 fn v1_decoder_does_not_panic() {
1082 let seq = rng().random_range(1..u32::MAX);
1083 let req = encode_req(seq, &[("message", "Hello, World!")]);
1084 for i in 0..req.len() - 1 {
1085 assert!(
1086 decode_data_frame(LogstashProtocolVersion::V1, &mut BytesMut::from(&req[..i]))
1087 .is_none()
1088 );
1089 }
1090 }
1091
1092 #[test]
1099 fn malformed_json_frame_is_a_fatal_decode_error() {
1100 let mut decoder = LogstashDecoder::new();
1101 let mut src = BytesMut::new();
1102 src.put_u8(b'2');
1103 src.put_u8(b'J');
1104 src.put_u32(1); let bad = b"{ not valid json ";
1106 src.put_u32(bad.len() as u32); src.put(&bad[..]);
1108
1109 let err = decoder.decode(&mut src).unwrap_err();
1110 assert!(matches!(err, DecodeError::JsonFrameFailedDecode { .. }));
1111 assert!(
1112 !err.can_continue(),
1113 "a malformed JSON frame must be fatal so the connection closes",
1114 );
1115 }
1116
1117 #[test]
1118 fn malformed_compressed_frame_is_a_fatal_decode_error() {
1119 let mut decoder = LogstashDecoder::new();
1120 let mut src = BytesMut::new();
1121 src.put_u8(b'2');
1122 src.put_u8(b'C');
1123 let garbage = b"this is not a zlib stream";
1124 src.put_u32(garbage.len() as u32); src.put(&garbage[..]);
1126
1127 let err = decoder.decode(&mut src).unwrap_err();
1128 assert!(matches!(err, DecodeError::DecompressionFailed { .. }));
1129 assert!(!err.can_continue());
1130 }
1131
1132 #[test]
1133 fn premature_window_size_frame_is_a_fatal_decode_error() {
1134 let mut decoder = LogstashDecoder::new();
1147 let mut src = BytesMut::new();
1148 push_window_size(&mut src, 2);
1149 push_req(&mut src, 1, &[("message", "only one of two")]);
1150 push_window_size(&mut src, 5); assert!(decoder.decode(&mut src).unwrap().is_some());
1154
1155 let err = decoder.decode(&mut src).unwrap_err();
1158 assert!(matches!(err, DecodeError::PrematureWindowSize { .. }));
1159 assert!(
1160 !err.can_continue(),
1161 "a premature WindowSize must be fatal so the connection closes",
1162 );
1163 }
1164
1165 #[test]
1166 fn premature_window_size_inside_compressed_payload_is_fatal() {
1167 let mut inner = BytesMut::new();
1172 push_window_size(&mut inner, 2);
1173 push_req(&mut inner, 1, &[("message", "only one of two")]);
1174 push_window_size(&mut inner, 5); let mut req = BytesMut::new();
1177 push_compressed(&mut req, &inner);
1178
1179 let mut decoder = LogstashDecoder::new();
1180 let err = decoder.decode(&mut req).unwrap_err();
1181 assert!(matches!(err, DecodeError::PrematureWindowSize { .. }));
1182 assert!(
1183 !err.can_continue(),
1184 "a premature WindowSize inside a compressed frame must be fatal",
1185 );
1186 }
1187
1188 #[test]
1189 fn nested_compressed_frame_is_a_fatal_decode_error() {
1190 let mut inner = BytesMut::new();
1191 push_req(&mut inner, 1, &[("message", "should never be reached")]);
1192
1193 let mut middle = BytesMut::new();
1194 push_compressed(&mut middle, &inner);
1195
1196 let mut req = BytesMut::new();
1197 push_compressed(&mut req, &middle);
1198
1199 let mut decoder = LogstashDecoder::new();
1200 let err = decoder.decode(&mut req).unwrap_err();
1201 assert!(matches!(err, DecodeError::NestedCompressedFrame));
1202 assert!(!err.can_continue());
1203 }
1204
1205 #[tokio::test]
1206 async fn malformed_frame_closes_connection_without_ack() {
1207 let (address, _recv) = start_logstash(EventStatus::Delivered).await;
1208
1209 let mut socket = tokio::net::TcpStream::connect(address).await.unwrap();
1210
1211 let mut req = BytesMut::new();
1213 req.put_u8(b'2');
1214 req.put_u8(b'J');
1215 req.put_u32(1); let bad = b"{ not valid json ";
1217 req.put_u32(bad.len() as u32); req.put(&bad[..]);
1219 socket.write_all(&req).await.unwrap();
1220
1221 let mut output = BytesMut::new();
1224 let result = socket.read_buf(&mut output).await;
1225 assert!(
1226 matches!(result, Ok(0)) || result.is_err(),
1227 "expected the connection to close; read returned {result:?} with {output:?}",
1228 );
1229 assert!(
1230 output.is_empty(),
1231 "no ACK should be sent for a malformed frame, got {output:?}",
1232 );
1233 }
1234
1235 #[tokio::test]
1236 async fn distinct_windows_do_not_share_an_ack_domain() {
1237 let mut req = BytesMut::new();
1238 push_window_size(&mut req, 1);
1239 push_req(&mut req, 1, &[("message", "first window")]);
1240 push_window_size(&mut req, 2);
1241 push_req(&mut req, 1, &[("message", "second window first")]);
1242 push_req(&mut req, 2, &[("message", "second window second")]);
1243
1244 let decoded = decode_frames_and_assert_sequences(req, &[1, 1, 2]);
1245 assert_acknowledgements_for_ready_frames(decoded, &[1, 1, 2], &[1, 2]).await;
1246 }
1247
1248 #[tokio::test]
1249 async fn distinct_windows_with_monotonic_sequences_ack_the_first_window() {
1250 let mut req = BytesMut::new();
1251 push_window_size(&mut req, 2);
1252 push_req(&mut req, 1, &[("message", "first window first")]);
1253 push_req(&mut req, 2, &[("message", "first window second")]);
1254 push_window_size(&mut req, 2);
1255 push_req(&mut req, 3, &[("message", "second window first")]);
1256 push_req(&mut req, 4, &[("message", "second window second")]);
1257
1258 let decoded = decode_frames_and_assert_sequences(req, &[1, 2, 3, 4]);
1259 assert_acknowledgements_for_ready_frames(decoded, &[1, 2, 3, 4], &[2, 4]).await;
1260 }
1261
1262 #[tokio::test]
1263 async fn incomplete_window_is_not_acked() {
1264 let mut req = BytesMut::new();
1272 push_window_size(&mut req, 4);
1273 push_req(&mut req, 1, &[("message", "only event in partial window")]);
1274
1275 let decoded = decode_frames_and_assert_sequences(req, &[1]);
1276 assert_acknowledgements_for_ready_frames(decoded, &[1], &[]).await;
1277 }
1278
1279 #[tokio::test]
1280 async fn window_split_across_compressed_frames_acks_once_on_completion() {
1281 let mut first = BytesMut::new();
1290 push_req(&mut first, 1, &[("message", "w4 first")]);
1291 push_req(&mut first, 2, &[("message", "w4 second")]);
1292
1293 let mut second = BytesMut::new();
1294 push_req(&mut second, 3, &[("message", "w4 third")]);
1295 push_req(&mut second, 4, &[("message", "w4 fourth")]);
1296
1297 let mut req = BytesMut::new();
1301 push_window_size(&mut req, 4);
1302 push_compressed(&mut req, &first);
1303 push_compressed(&mut req, &second);
1304
1305 let decoded = decode_frames_and_assert_sequences(req, &[1, 2, 3, 4]);
1306 assert_acknowledgements_for_ready_frames(decoded, &[1, 2, 3, 4], &[4]).await;
1307 }
1308
1309 #[tokio::test]
1310 async fn window_larger_than_ready_frames_capacity_in_one_compressed_frame_acks_once() {
1311 const WINDOW: u32 = 5;
1312 const CAPACITY: usize = 2;
1313
1314 let mut inner = BytesMut::new();
1315 for seq in 1..=WINDOW {
1316 push_req(&mut inner, seq, &[("message", "event in oversized window")]);
1317 }
1318
1319 const SMALL_WINDOW: u32 = 2;
1326 let mut small_inner = BytesMut::new();
1327 for seq in 1..=SMALL_WINDOW {
1328 push_req(
1329 &mut small_inner,
1330 seq,
1331 &[("message", "event in small window")],
1332 );
1333 }
1334
1335 let mut req = BytesMut::new();
1338 push_window_size(&mut req, WINDOW);
1339 push_compressed(&mut req, &inner);
1340 push_window_size(&mut req, SMALL_WINDOW);
1341 push_compressed(&mut req, &small_inner);
1342
1343 let decoded = decode_frames_and_assert_sequences(req, &[1, 2, 3, 4, 5, 1, 2]);
1344
1345 let stream = stream::iter(decoded.into_iter().map(Ok::<_, DecodeError>));
1346 let mut ready = ReadyFrames::with_capacity(stream, CAPACITY);
1347 let mut acknowledgements = Vec::new();
1348
1349 while let Some(result) = ready.next().await {
1350 let (frames, _byte_size) = result.unwrap();
1351 let acks = LogstashAcker::new(&frames)
1352 .build_ack(TcpSourceAck::Ack)
1353 .map_or_else(Vec::new, decode_acknowledgements);
1354 acknowledgements.push(acks);
1355 }
1356
1357 assert_eq!(acknowledgements, vec![vec![], vec![], vec![5], vec![2]]);
1365
1366 let all_acks: Vec<u32> = acknowledgements.into_iter().flatten().collect();
1368 assert_eq!(
1369 all_acks,
1370 vec![5, 2],
1371 "each window must be ACKed exactly once"
1372 );
1373 }
1374
1375 #[tokio::test]
1376 async fn complete_window_then_incomplete_window_acks_only_the_complete_one() {
1377 let mut req = BytesMut::new();
1383 push_window_size(&mut req, 2);
1384 push_req(&mut req, 1, &[("message", "complete window first")]);
1385 push_req(&mut req, 2, &[("message", "complete window second")]);
1386 push_window_size(&mut req, 1000);
1387 push_req(&mut req, 1, &[("message", "partial window first")]);
1388 push_req(&mut req, 2, &[("message", "partial window second")]);
1389 push_req(&mut req, 3, &[("message", "partial window third")]);
1390
1391 let decoded = decode_frames_and_assert_sequences(req, &[1, 2, 1, 2, 3]);
1392 assert_acknowledgements_for_ready_frames(decoded, &[1, 2, 1, 2, 3], &[2]).await;
1393 }
1394
1395 #[tokio::test]
1396 async fn compressed_frames_preserve_inner_window_boundaries() {
1397 let mut inner = BytesMut::new();
1398 push_window_size(&mut inner, 2);
1399 push_req(&mut inner, 1, &[("message", "compressed first")]);
1400 push_req(&mut inner, 2, &[("message", "compressed second")]);
1401
1402 let mut req = BytesMut::new();
1403 push_compressed(&mut req, &inner);
1404
1405 let decoded = decode_frames_and_assert_sequences(req, &[1, 2]);
1406 assert_acknowledgements_for_ready_frames(decoded, &[1, 2], &[2]).await;
1407 }
1408
1409 #[tokio::test]
1410 async fn single_window_split_across_ready_frames_acks_only_on_completion() {
1411 let mut req = BytesMut::new();
1416 push_window_size(&mut req, 4);
1417 push_req(&mut req, 1, &[("message", "first")]);
1418 push_req(&mut req, 2, &[("message", "second")]);
1419 push_req(&mut req, 3, &[("message", "third")]);
1420 push_req(&mut req, 4, &[("message", "fourth")]);
1421
1422 let decoded = decode_frames_and_assert_sequences(req, &[1, 2, 3, 4]);
1423
1424 let stream = stream::iter(decoded.into_iter().map(Ok::<_, DecodeError>));
1425 let mut ready = ReadyFrames::with_capacity(stream, 2);
1426 let mut acknowledgements = Vec::new();
1427
1428 while let Some(result) = ready.next().await {
1429 let (frames, _byte_size) = result.unwrap();
1430 let acks = LogstashAcker::new(&frames)
1431 .build_ack(TcpSourceAck::Ack)
1432 .map_or_else(Vec::new, decode_acknowledgements);
1433 acknowledgements.push(acks);
1434 }
1435
1436 assert_eq!(acknowledgements, vec![vec![], vec![4]]);
1439 }
1440
1441 #[tokio::test]
1442 async fn fresh_window_after_completed_window_is_accepted() {
1443 let mut decoder = LogstashDecoder::new();
1451
1452 let mut first_batch = BytesMut::new();
1453 push_window_size(&mut first_batch, 1);
1454 push_req(&mut first_batch, 1, &[("message", "first window")]);
1455 let decoded =
1456 decode_frames_with_decoder_and_assert_sequences(&mut decoder, first_batch, &[1]);
1457 assert_acknowledgements_for_ready_frames(decoded, &[1], &[1]).await;
1458
1459 let mut second_batch = BytesMut::new();
1460 push_window_size(&mut second_batch, 1);
1461 push_req(
1462 &mut second_batch,
1463 1,
1464 &[("message", "fresh window after completion")],
1465 );
1466 let decoded =
1467 decode_frames_with_decoder_and_assert_sequences(&mut decoder, second_batch, &[1]);
1468 assert_acknowledgements_for_ready_frames(decoded, &[1], &[1]).await;
1469 }
1470
1471 async fn send_req(address: SocketAddr, pairs: &[(&str, &str)], sends_ack: bool) {
1472 let seq = rng().random_range(1..u32::MAX);
1473 let mut socket = tokio::net::TcpStream::connect(address).await.unwrap();
1474
1475 let req = encode_req(seq, pairs);
1476 socket.write_all(&req).await.unwrap();
1477
1478 let mut output = BytesMut::new();
1479 socket.read_buf(&mut output).await.unwrap();
1480
1481 if sends_ack {
1482 assert_eq!(output.get_u8(), b'2');
1483 assert_eq!(output.get_u8(), b'A');
1484 assert_eq!(output.get_u32(), seq);
1485 }
1486 assert_eq!(output.len(), 0);
1487 }
1488
1489 #[test]
1490 fn output_schema_definition_vector_namespace() {
1491 let config = LogstashConfig {
1492 log_namespace: Some(true),
1493 ..Default::default()
1494 };
1495
1496 let definitions = config
1497 .outputs(LogNamespace::Vector)
1498 .remove(0)
1499 .schema_definition(true);
1500
1501 let expected_definition =
1502 Definition::new_with_default_metadata(Kind::bytes(), [LogNamespace::Vector])
1503 .with_meaning(OwnedTargetPath::event_root(), "message")
1504 .with_metadata_field(
1505 &owned_value_path!("vector", "source_type"),
1506 Kind::bytes(),
1507 None,
1508 )
1509 .with_metadata_field(
1510 &owned_value_path!("vector", "ingest_timestamp"),
1511 Kind::timestamp(),
1512 None,
1513 )
1514 .with_metadata_field(
1515 &owned_value_path!(LogstashConfig::NAME, "timestamp"),
1516 Kind::timestamp().or_undefined(),
1517 Some("timestamp"),
1518 )
1519 .with_metadata_field(
1520 &owned_value_path!(LogstashConfig::NAME, "host"),
1521 Kind::bytes(),
1522 Some("host"),
1523 )
1524 .with_metadata_field(
1525 &owned_value_path!(LogstashConfig::NAME, "tls_client_metadata"),
1526 Kind::object(Collection::empty().with_unknown(Kind::bytes())).or_undefined(),
1527 None,
1528 );
1529
1530 assert_eq!(definitions, Some(expected_definition))
1531 }
1532
1533 #[test]
1534 fn output_schema_definition_legacy_namespace() {
1535 let config = LogstashConfig::default();
1536
1537 let definitions = config
1538 .outputs(LogNamespace::Legacy)
1539 .remove(0)
1540 .schema_definition(true);
1541
1542 let expected_definition = Definition::new_with_default_metadata(
1543 Kind::object(Collection::empty()),
1544 [LogNamespace::Legacy],
1545 )
1546 .with_event_field(
1547 &owned_value_path!("message"),
1548 Kind::bytes(),
1549 Some("message"),
1550 )
1551 .with_event_field(&owned_value_path!("source_type"), Kind::bytes(), None)
1552 .with_event_field(&owned_value_path!("timestamp"), Kind::timestamp(), None)
1553 .with_event_field(&owned_value_path!("host"), Kind::bytes(), Some("host"));
1554
1555 assert_eq!(definitions, Some(expected_definition))
1556 }
1557}
1558
1559#[cfg(all(test, feature = "logstash-integration-tests"))]
1560mod integration_tests {
1561 use std::time::Duration;
1562
1563 use futures::Stream;
1564 use tokio::time::timeout;
1565 use vrl::event_path;
1566
1567 use super::*;
1568 use crate::{
1569 SourceSender,
1570 config::SourceContext,
1571 event::EventStatus,
1572 test_util::{
1573 collect_n,
1574 components::{SOCKET_PUSH_SOURCE_TAGS, assert_source_compliance},
1575 wait_for_tcp,
1576 },
1577 tls::{TlsConfig, TlsEnableableConfig},
1578 };
1579
1580 fn heartbeat_address() -> String {
1581 std::env::var("HEARTBEAT_ADDRESS")
1582 .expect("Address of Beats Heartbeat service must be specified.")
1583 }
1584
1585 #[tokio::test]
1586 async fn beats_heartbeat() {
1587 let events = assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async {
1588 let out = source(heartbeat_address(), None).await;
1589
1590 timeout(Duration::from_secs(60), collect_n(out, 1))
1591 .await
1592 .unwrap()
1593 })
1594 .await;
1595
1596 assert!(!events.is_empty());
1597
1598 let log = events[0].as_log();
1599 assert_eq!(
1600 log.get(event_path!("@metadata", "beat")),
1601 Some(String::from("heartbeat").into()).as_ref()
1602 );
1603 assert_eq!(
1604 log.get(event_path!("summary", "up")),
1605 Some(1.into()).as_ref()
1606 );
1607 assert!(log.get(event_path!("timestamp")).is_some());
1608 assert!(log.get(event_path!("host")).is_some());
1609 }
1610
1611 fn logstash_address() -> String {
1612 std::env::var("LOGSTASH_ADDRESS")
1613 .expect("Listen address for `logstash` source must be specified.")
1614 }
1615
1616 #[tokio::test]
1617 async fn logstash() {
1618 let events = assert_source_compliance(&SOCKET_PUSH_SOURCE_TAGS, async {
1619 let out = source(
1620 logstash_address(),
1621 Some(TlsEnableableConfig {
1622 enabled: Some(true),
1623 options: TlsConfig {
1624 crt_file: Some(
1625 "tests/integration/shared/data/host.docker.internal.crt".into(),
1626 ),
1627 key_file: Some(
1628 "tests/integration/shared/data/host.docker.internal.key".into(),
1629 ),
1630 ..Default::default()
1631 },
1632 }),
1633 )
1634 .await;
1635
1636 timeout(Duration::from_secs(60), collect_n(out, 1))
1637 .await
1638 .unwrap()
1639 })
1640 .await;
1641
1642 assert!(!events.is_empty());
1643
1644 let log = events[0].as_log();
1645 assert!(
1646 log.get(event_path!("line"))
1647 .unwrap()
1648 .to_string_lossy()
1649 .contains("Hello World")
1650 );
1651 assert!(log.get(event_path!("host")).is_some());
1652 }
1653
1654 async fn source(
1655 address: String,
1656 tls: Option<TlsEnableableConfig>,
1657 ) -> impl Stream<Item = Event> + Unpin {
1658 let (sender, recv) = SourceSender::new_test_finalize(EventStatus::Delivered);
1659 let address: SocketAddr = address.parse().unwrap();
1660 let tls_config = TlsSourceConfig {
1661 client_metadata_key: None,
1662 tls_config: tls.unwrap_or_default(),
1663 };
1664 tokio::spawn(async move {
1665 LogstashConfig {
1666 address: address.into(),
1667 tls: Some(tls_config),
1668 keepalive: None,
1669 permit_origin: None,
1670 receive_buffer_bytes: None,
1671 acknowledgements: false.into(),
1672 connection_limit: None,
1673 log_namespace: None,
1674 }
1675 .build(SourceContext::new_test(sender, None))
1676 .await
1677 .unwrap()
1678 .await
1679 .unwrap()
1680 });
1681 wait_for_tcp(address).await;
1682 recv
1683 }
1684}