Skip to main content

vector/sources/aws_s3/
sqs.rs

1use std::{
2    collections::HashMap,
3    future::ready,
4    num::NonZeroUsize,
5    panic,
6    sync::{Arc, LazyLock},
7    time::{Duration, Instant},
8};
9
10use aws_sdk_s3::{Client as S3Client, operation::get_object::GetObjectError};
11use aws_sdk_sqs::{
12    Client as SqsClient,
13    operation::{
14        delete_message_batch::{DeleteMessageBatchError, DeleteMessageBatchOutput},
15        receive_message::ReceiveMessageError,
16        send_message_batch::{SendMessageBatchError, SendMessageBatchOutput},
17    },
18    types::{DeleteMessageBatchRequestEntry, Message, SendMessageBatchRequestEntry},
19};
20use aws_smithy_runtime_api::client::{orchestrator::HttpResponse, result::SdkError};
21use aws_types::region::Region;
22use bytes::Bytes;
23use chrono::{DateTime, TimeZone, Utc};
24use futures::{FutureExt, Stream, StreamExt, TryFutureExt};
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26use serde_with::serde_as;
27use smallvec::SmallVec;
28use snafu::{ResultExt, Snafu};
29use tokio::{pin, select};
30use tokio_util::codec::FramedRead;
31use vector_lib::{
32    codecs::decoding::FramingError,
33    config::{LegacyKey, LogNamespace, log_schema},
34    configurable::configurable_component,
35    event::MaybeAsLogMut,
36    internal_event::{
37        ByteSize, BytesReceived, CountByteSize, InternalEventHandle as _, Protocol, Registered,
38    },
39    lookup::{PathPrefix, metadata_path, path},
40    source_sender::SendError,
41};
42
43use crate::{
44    SourceSender,
45    aws::AwsTimeout,
46    codecs::Decoder,
47    common::backoff::ExponentialBackoff,
48    config::{SourceAcknowledgementsConfig, SourceContext},
49    event::{BatchNotifier, BatchStatus, EstimatedJsonEncodedSizeOf, Event, LogEvent},
50    internal_events::{
51        EventsReceived, S3ObjectProcessingFailed, S3ObjectProcessingSucceeded,
52        SqsMessageDeleteBatchError, SqsMessageDeletePartialError, SqsMessageDeleteSucceeded,
53        SqsMessageProcessingError, SqsMessageProcessingSucceeded, SqsMessageReceiveError,
54        SqsMessageReceiveSucceeded, SqsMessageSendBatchError, SqsMessageSentPartialError,
55        SqsMessageSentSucceeded, SqsS3EventRecordInvalidEventIgnored, StreamClosedError,
56    },
57    line_agg::{self, LineAgg},
58    shutdown::ShutdownSignal,
59    sources::aws_s3::AwsS3Config,
60    tls::TlsConfig,
61};
62
63static SUPPORTED_S3_EVENT_VERSION: LazyLock<semver::VersionReq> =
64    LazyLock::new(|| semver::VersionReq::parse("~2").unwrap());
65
66/// Configuration for deferring events based on their age.
67#[serde_as]
68#[configurable_component]
69#[derive(Clone, Debug, Default)]
70#[serde(deny_unknown_fields)]
71pub(super) struct DeferredConfig {
72    /// The URL of the queue to forward events to when they are older than `max_age_secs`.
73    #[configurable(metadata(
74        docs::examples = "https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue"
75    ))]
76    #[configurable(validation(format = "uri"))]
77    pub(super) queue_url: String,
78
79    /// Event must have been emitted within the last `max_age_secs` seconds to be processed.
80    ///
81    /// If the event is older, it is forwarded to the `queue_url` for later processing.
82    #[configurable(metadata(docs::type_unit = "seconds"))]
83    #[configurable(metadata(docs::examples = 3600))]
84    pub(super) max_age_secs: u64,
85}
86
87/// SQS configuration options.
88//
89// TODO: It seems awfully likely that we could re-use the existing configuration type for the `aws_sqs` source in some
90// way, given the near 100% overlap in configurable values.
91#[serde_as]
92#[configurable_component]
93#[derive(Clone, Debug, Derivative)]
94#[derivative(Default)]
95#[serde(deny_unknown_fields)]
96pub(super) struct Config {
97    /// The URL of the SQS queue to poll for bucket notifications.
98    #[configurable(metadata(
99        docs::examples = "https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue"
100    ))]
101    #[configurable(validation(format = "uri"))]
102    pub(super) queue_url: String,
103
104    /// How long to wait while polling the queue for new messages, in seconds.
105    ///
106    /// Generally, this should not be changed unless instructed to do so, as if messages are available,
107    /// they are always consumed, regardless of the value of `poll_secs`.
108    // NOTE: We restrict this to u32 for safe conversion to i32 later.
109    // NOTE: This value isn't used as a `Duration` downstream, so we don't bother using `serde_with`
110    #[serde(default = "default_poll_secs")]
111    #[derivative(Default(value = "default_poll_secs()"))]
112    #[configurable(metadata(docs::type_unit = "seconds"))]
113    pub(super) poll_secs: u32,
114
115    /// The visibility timeout to use for messages, in seconds.
116    ///
117    /// This controls how long a message is left unavailable after it is received. If a message is received, and
118    /// takes longer than `visibility_timeout_secs` to process and delete the message from the queue, it is made available again for another consumer.
119    ///
120    /// This can happen if there is an issue between consuming a message and deleting it.
121    // NOTE: We restrict this to u32 for safe conversion to i32 later.
122    // NOTE: This value isn't used as a `Duration` downstream, so we don't bother using `serde_with`
123    #[serde(default = "default_visibility_timeout_secs")]
124    #[derivative(Default(value = "default_visibility_timeout_secs()"))]
125    #[configurable(metadata(docs::type_unit = "seconds"))]
126    #[configurable(metadata(docs::human_name = "Visibility Timeout"))]
127    pub(super) visibility_timeout_secs: u32,
128
129    /// Whether to delete the message once it is processed.
130    ///
131    /// It can be useful to set this to `false` for debugging or during the initial setup.
132    #[serde(default = "default_true")]
133    #[derivative(Default(value = "default_true()"))]
134    pub(super) delete_message: bool,
135
136    /// Whether to delete non-retryable messages.
137    ///
138    /// If a message is rejected by the sink and not retryable, it is deleted from the queue.
139    #[serde(default = "default_true")]
140    #[derivative(Default(value = "default_true()"))]
141    pub(super) delete_failed_message: bool,
142
143    /// Number of concurrent tasks to create for polling the queue for messages.
144    ///
145    /// Defaults to the number of available CPUs on the system.
146    ///
147    /// Should not typically need to be changed, but it can sometimes be beneficial to raise this
148    /// value when there is a high rate of messages being pushed into the queue and the objects
149    /// being fetched are small. In these cases, system resources may not be fully utilized without
150    /// fetching more messages per second, as the SQS message consumption rate affects the S3 object
151    /// retrieval rate.
152    #[configurable(metadata(docs::type_unit = "tasks"))]
153    #[configurable(metadata(docs::examples = 5))]
154    pub(super) client_concurrency: Option<NonZeroUsize>,
155
156    /// Maximum number of messages to poll from SQS in a batch
157    ///
158    /// Defaults to 10
159    ///
160    /// Should be set to a smaller value when the files are large to help prevent the ingestion of
161    /// one file from causing the other files to exceed the visibility_timeout. Valid values are 1 - 10
162    // NOTE: We restrict this to u32 for safe conversion to i32 later.
163    #[serde(default = "default_max_number_of_messages")]
164    #[derivative(Default(value = "default_max_number_of_messages()"))]
165    #[configurable(metadata(docs::human_name = "Max Messages"))]
166    #[configurable(metadata(docs::examples = 1))]
167    pub(super) max_number_of_messages: u32,
168
169    #[configurable(derived)]
170    #[serde(default)]
171    #[derivative(Default)]
172    pub(super) tls_options: Option<TlsConfig>,
173
174    // Client timeout configuration for SQS operations. Take care when configuring these settings
175    // to allow enough time for the polling interval configured in `poll_secs`.
176    #[configurable(derived)]
177    #[derivative(Default)]
178    #[serde(default)]
179    #[serde(flatten)]
180    pub(super) timeout: Option<AwsTimeout>,
181
182    /// Configuration for deferring events to another queue based on their age.
183    #[configurable(derived)]
184    pub(super) deferred: Option<DeferredConfig>,
185}
186
187pub(super) struct S3Options {
188    pub(super) compression: super::Compression,
189    pub(super) request_payer: Option<super::S3RequestPayer>,
190}
191
192const fn default_poll_secs() -> u32 {
193    15
194}
195
196const fn default_visibility_timeout_secs() -> u32 {
197    300
198}
199
200const fn default_max_number_of_messages() -> u32 {
201    10
202}
203
204const fn default_true() -> bool {
205    true
206}
207
208#[derive(Debug, Snafu)]
209pub(super) enum IngestorNewError {
210    #[snafu(display("Invalid value for max_number_of_messages {}", messages))]
211    InvalidNumberOfMessages { messages: u32 },
212}
213
214#[allow(clippy::large_enum_variant)]
215#[derive(Debug, Snafu)]
216pub enum ProcessingError {
217    #[snafu(display(
218        "Could not parse SQS message with id {} as S3 notification: {}",
219        message_id,
220        source
221    ))]
222    InvalidSqsMessage {
223        source: serde_json::Error,
224        message_id: String,
225    },
226    #[snafu(display("Failed to fetch s3://{}/{}: {}", bucket, key, source))]
227    GetObject {
228        source: SdkError<GetObjectError, HttpResponse>,
229        bucket: String,
230        key: String,
231    },
232    #[snafu(display("Failed to read all of s3://{}/{}: {}", bucket, key, source))]
233    ReadObject {
234        source: Box<dyn FramingError>,
235        bucket: String,
236        key: String,
237    },
238    #[snafu(display("Failed to flush all of s3://{}/{}: {}", bucket, key, source))]
239    PipelineSend {
240        source: vector_lib::source_sender::SendError,
241        bucket: String,
242        key: String,
243    },
244    #[snafu(display(
245        "Object notification for s3://{}/{} is a bucket in another region: {}",
246        bucket,
247        key,
248        region
249    ))]
250    WrongRegion {
251        region: String,
252        bucket: String,
253        key: String,
254    },
255    #[snafu(display("Unsupported S3 event version: {}.", version,))]
256    UnsupportedS3EventVersion { version: semver::Version },
257    #[snafu(display(
258        "Sink reported an error sending events for an s3 object in region {}: s3://{}/{}",
259        region,
260        bucket,
261        key
262    ))]
263    ErrorAcknowledgement {
264        region: String,
265        bucket: String,
266        key: String,
267    },
268    #[snafu(display(
269        "File s3://{}/{} too old.  Forwarded to deferred queue {}",
270        bucket,
271        key,
272        deferred_queue
273    ))]
274    FileTooOld {
275        bucket: String,
276        key: String,
277        deferred_queue: String,
278    },
279}
280
281pub struct State {
282    region: Region,
283
284    s3_client: S3Client,
285    s3_options: S3Options,
286    sqs_client: SqsClient,
287
288    multiline: Option<line_agg::Config>,
289
290    queue_url: String,
291    poll_secs: i32,
292    max_number_of_messages: i32,
293    client_concurrency: usize,
294    visibility_timeout_secs: i32,
295    delete_message: bool,
296    delete_failed_message: bool,
297    decoder: Decoder,
298
299    deferred: Option<DeferredConfig>,
300}
301
302pub(super) struct Ingestor {
303    state: Arc<State>,
304}
305
306impl Ingestor {
307    pub(super) async fn new(
308        region: Region,
309        sqs_client: SqsClient,
310        s3_client: S3Client,
311        config: Config,
312        s3_options: S3Options,
313        multiline: Option<line_agg::Config>,
314        decoder: Decoder,
315    ) -> Result<Ingestor, IngestorNewError> {
316        if config.max_number_of_messages < 1 || config.max_number_of_messages > 10 {
317            return Err(IngestorNewError::InvalidNumberOfMessages {
318                messages: config.max_number_of_messages,
319            });
320        }
321        let state = Arc::new(State {
322            region,
323
324            s3_client,
325            s3_options,
326            sqs_client,
327
328            multiline,
329
330            queue_url: config.queue_url,
331            poll_secs: config.poll_secs as i32,
332            max_number_of_messages: config.max_number_of_messages as i32,
333            client_concurrency: config
334                .client_concurrency
335                .map(|n| n.get())
336                .unwrap_or_else(crate::num_threads),
337            visibility_timeout_secs: config.visibility_timeout_secs as i32,
338            delete_message: config.delete_message,
339            delete_failed_message: config.delete_failed_message,
340            decoder,
341
342            deferred: config.deferred,
343        });
344
345        Ok(Ingestor { state })
346    }
347
348    pub(super) async fn run(
349        self,
350        cx: SourceContext,
351        acknowledgements: SourceAcknowledgementsConfig,
352        log_namespace: LogNamespace,
353    ) -> Result<(), ()> {
354        let acknowledgements = cx.do_acknowledgements(acknowledgements);
355        let mut handles = Vec::new();
356        for _ in 0..self.state.client_concurrency {
357            let process = IngestorProcess::new(
358                Arc::clone(&self.state),
359                cx.out.clone(),
360                cx.shutdown.clone(),
361                log_namespace,
362                acknowledgements,
363            );
364            let fut = process.run();
365            let handle = crate::spawn_in_current_span(fut);
366            handles.push(handle);
367        }
368
369        // Wait for all of the processes to finish.  If any one of them panics, we resume
370        // that panic here to properly shutdown Vector.
371        for handle in handles.drain(..) {
372            if let Err(e) = handle.await
373                && e.is_panic()
374            {
375                panic::resume_unwind(e.into_panic());
376            }
377        }
378
379        Ok(())
380    }
381}
382
383pub struct IngestorProcess {
384    state: Arc<State>,
385    out: SourceSender,
386    shutdown: ShutdownSignal,
387    acknowledgements: bool,
388    log_namespace: LogNamespace,
389    bytes_received: Registered<BytesReceived>,
390    events_received: Registered<EventsReceived>,
391    backoff: ExponentialBackoff,
392}
393
394impl IngestorProcess {
395    pub fn new(
396        state: Arc<State>,
397        out: SourceSender,
398        shutdown: ShutdownSignal,
399        log_namespace: LogNamespace,
400        acknowledgements: bool,
401    ) -> Self {
402        Self {
403            state,
404            out,
405            shutdown,
406            acknowledgements,
407            log_namespace,
408            bytes_received: register!(BytesReceived::from(Protocol::HTTP)),
409            events_received: register!(EventsReceived),
410            backoff: ExponentialBackoff::default().max_delay(Duration::from_secs(30)),
411        }
412    }
413
414    async fn run(mut self) {
415        let shutdown = self.shutdown.clone().fuse();
416        pin!(shutdown);
417
418        loop {
419            select! {
420                _ = &mut shutdown => break,
421                result = self.run_once() => {
422                    match result {
423                        Ok(()) => {
424                            // Reset backoff on successful receive
425                            self.backoff.reset();
426                        }
427                        Err(_) => {
428                            let delay = self.backoff.next().expect("backoff never ends");
429                            trace!(
430                                delay_ms = delay.as_millis(),
431                                "`run_once` failed, will retry after delay.",
432                            );
433                            tokio::time::sleep(delay).await;
434                        }
435                    }
436                },
437            }
438        }
439    }
440
441    async fn run_once(&mut self) -> Result<(), ()> {
442        let messages = match self.receive_messages().await {
443            Ok(messages) => {
444                emit!(SqsMessageReceiveSucceeded {
445                    count: messages.len(),
446                });
447                messages
448            }
449            Err(err) => {
450                emit!(SqsMessageReceiveError { error: &err });
451                return Err(());
452            }
453        };
454
455        let mut delete_entries = Vec::new();
456        let mut deferred_entries = Vec::new();
457        for message in messages {
458            let receipt_handle = match message.receipt_handle {
459                None => {
460                    // I don't think this will ever actually happen, but is just an artifact of the
461                    // AWS's API predilection for returning nullable values for all response
462                    // attributes
463                    warn!(message = "Refusing to process message with no receipt_handle.", ?message.message_id);
464                    continue;
465                }
466                Some(ref handle) => handle.to_owned(),
467            };
468
469            let message_id = message
470                .message_id
471                .clone()
472                .unwrap_or_else(|| "<unknown>".to_owned());
473            match self.handle_sqs_message(message.clone()).await {
474                Ok(()) => {
475                    emit!(SqsMessageProcessingSucceeded {
476                        message_id: &message_id
477                    });
478                    if self.state.delete_message {
479                        trace!(
480                            message = "Queued SQS message for deletion.",
481                            id = message_id,
482                            receipt_handle = receipt_handle,
483                        );
484                        delete_entries.push(
485                            DeleteMessageBatchRequestEntry::builder()
486                                .id(message_id.clone())
487                                .receipt_handle(receipt_handle)
488                                .build()
489                                .expect("all required builder params specified"),
490                        );
491                    }
492                }
493                Err(err) => {
494                    match err {
495                        ProcessingError::FileTooOld { .. } => {
496                            emit!(SqsMessageProcessingSucceeded {
497                                message_id: &message_id
498                            });
499                            if let Some(deferred) = &self.state.deferred {
500                                trace!(
501                                    message = "Forwarding message to deferred queue.",
502                                    id = message_id,
503                                    receipt_handle = receipt_handle,
504                                    deferred_queue = deferred.queue_url,
505                                );
506
507                                deferred_entries.push(
508                                    SendMessageBatchRequestEntry::builder()
509                                        .id(message_id.clone())
510                                        .message_body(message.body.unwrap_or_default())
511                                        .build()
512                                        .expect("all required builder params specified"),
513                                );
514                            }
515                            //  maybe delete the message from current queue since we have processed it
516                            if self.state.delete_message {
517                                trace!(
518                                    message = "Queued SQS message for deletion.",
519                                    id = message_id,
520                                    receipt_handle = receipt_handle,
521                                );
522                                delete_entries.push(
523                                    DeleteMessageBatchRequestEntry::builder()
524                                        .id(message_id)
525                                        .receipt_handle(receipt_handle)
526                                        .build()
527                                        .expect("all required builder params specified"),
528                                );
529                            }
530                        }
531                        _ => {
532                            emit!(SqsMessageProcessingError {
533                                message_id: &message_id,
534                                error: &err,
535                            });
536                        }
537                    }
538                }
539            }
540        }
541
542        // Should consider removing failed deferrals from the delete_entries
543        if !deferred_entries.is_empty() {
544            let Some(deferred) = &self.state.deferred else {
545                warn!("Deferred queue not configured, but received deferred entries.");
546                return Ok(());
547            };
548            let cloned_entries = deferred_entries.clone();
549            match self
550                .send_messages(deferred_entries, deferred.queue_url.clone())
551                .await
552            {
553                Ok(result) => {
554                    if !result.successful.is_empty() {
555                        emit!(SqsMessageSentSucceeded {
556                            message_ids: result.successful,
557                        })
558                    }
559
560                    if !result.failed.is_empty() {
561                        emit!(SqsMessageSentPartialError {
562                            entries: result.failed
563                        })
564                    }
565                }
566                Err(err) => {
567                    emit!(SqsMessageSendBatchError {
568                        entries: cloned_entries,
569                        error: err,
570                    });
571                }
572            }
573        }
574        if !delete_entries.is_empty() {
575            // We need these for a correct error message if the batch fails overall.
576            let cloned_entries = delete_entries.clone();
577            match self.delete_messages(delete_entries).await {
578                Ok(result) => {
579                    // Batch deletes can have partial successes/failures, so we have to check
580                    // for both cases and emit accordingly.
581                    if !result.successful.is_empty() {
582                        emit!(SqsMessageDeleteSucceeded {
583                            message_ids: result.successful,
584                        });
585                    }
586
587                    if !result.failed.is_empty() {
588                        emit!(SqsMessageDeletePartialError {
589                            entries: result.failed
590                        });
591                    }
592                }
593                Err(err) => {
594                    emit!(SqsMessageDeleteBatchError {
595                        entries: cloned_entries,
596                        error: err,
597                    });
598                }
599            }
600        }
601        Ok(())
602    }
603
604    async fn handle_sqs_message(&mut self, message: Message) -> Result<(), ProcessingError> {
605        let sqs_body = message.body.unwrap_or_default();
606        let sqs_body = serde_json::from_str::<SnsNotification>(sqs_body.as_ref())
607            .map(|notification| notification.message)
608            .unwrap_or(sqs_body);
609        let s3_event: SqsEvent =
610            serde_json::from_str(sqs_body.as_ref()).context(InvalidSqsMessageSnafu {
611                message_id: message
612                    .message_id
613                    .clone()
614                    .unwrap_or_else(|| "<empty>".to_owned()),
615            })?;
616
617        match s3_event {
618            SqsEvent::TestEvent(_s3_test_event) => {
619                debug!(?message.message_id, message = "Found S3 Test Event.");
620                Ok(())
621            }
622            SqsEvent::Event(s3_event) => self.handle_s3_event(s3_event).await,
623        }
624    }
625
626    async fn handle_s3_event(&mut self, s3_event: S3Event) -> Result<(), ProcessingError> {
627        for record in s3_event.records {
628            self.handle_s3_event_record(record, self.log_namespace)
629                .await?
630        }
631        Ok(())
632    }
633
634    async fn handle_s3_event_record(
635        &mut self,
636        s3_event: S3EventRecord,
637        log_namespace: LogNamespace,
638    ) -> Result<(), ProcessingError> {
639        let event_version: semver::Version = s3_event.event_version.clone().into();
640        if !SUPPORTED_S3_EVENT_VERSION.matches(&event_version) {
641            return Err(ProcessingError::UnsupportedS3EventVersion {
642                version: event_version.clone(),
643            });
644        }
645
646        if s3_event.event_name.kind != "ObjectCreated" {
647            emit!(SqsS3EventRecordInvalidEventIgnored {
648                bucket: &s3_event.s3.bucket.name,
649                key: &s3_event.s3.object.key,
650                kind: &s3_event.event_name.kind,
651                name: &s3_event.event_name.name,
652            });
653            return Ok(());
654        }
655
656        // S3 has to send notifications to a queue in the same region so I don't think this will
657        // actually ever be hit unless messages are being forwarded from one queue to another
658        if self.state.region.as_ref() != s3_event.aws_region.as_str() {
659            return Err(ProcessingError::WrongRegion {
660                bucket: s3_event.s3.bucket.name.clone(),
661                key: s3_event.s3.object.key.clone(),
662                region: s3_event.aws_region,
663            });
664        }
665
666        if let Some(deferred) = &self.state.deferred {
667            let delta = Utc::now() - s3_event.event_time;
668            if delta.num_seconds() > deferred.max_age_secs as i64 {
669                return Err(ProcessingError::FileTooOld {
670                    bucket: s3_event.s3.bucket.name.clone(),
671                    key: s3_event.s3.object.key.clone(),
672                    deferred_queue: deferred.queue_url.clone(),
673                });
674            }
675        }
676
677        let download_start = Instant::now();
678
679        let object_result = self
680            .state
681            .s3_client
682            .get_object()
683            .bucket(s3_event.s3.bucket.name.clone())
684            .key(s3_event.s3.object.key.clone())
685            .set_request_payer(self.state.s3_options.request_payer.map(Into::into))
686            .send()
687            .await
688            .context(GetObjectSnafu {
689                bucket: s3_event.s3.bucket.name.clone(),
690                key: s3_event.s3.object.key.clone(),
691            });
692
693        let object = object_result?;
694
695        debug!(
696            message = "Got S3 object from SQS notification.",
697            bucket = s3_event.s3.bucket.name,
698            key = s3_event.s3.object.key,
699        );
700
701        let metadata = object.metadata;
702
703        let timestamp = object.last_modified.map(|ts| {
704            Utc.timestamp_opt(ts.secs(), ts.subsec_nanos())
705                .single()
706                .expect("invalid timestamp")
707        });
708
709        let (batch, receiver) = BatchNotifier::maybe_new_with_receiver(self.acknowledgements);
710        let object_reader = super::s3_object_decoder(
711            self.state.s3_options.compression,
712            &s3_event.s3.object.key,
713            object.content_encoding.as_deref(),
714            object.content_type.as_deref(),
715            object.body,
716        )
717        .await;
718
719        // Record the read error seen to propagate up later so we avoid ack'ing the SQS
720        // message
721        //
722        // String is used as we cannot clone std::io::Error to take ownership in closure
723        //
724        // FramedRead likely stops when it gets an i/o error but I found it more clear to
725        // show that we `take_while` there hasn't been an error
726        //
727        // This can result in objects being partially processed before an error, but we
728        // prefer duplicate lines over message loss. Future work could include recording
729        // the offset of the object that has been read, but this would only be relevant in
730        // the case that the same vector instance processes the same message.
731        let mut read_error = None;
732        let bytes_received = self.bytes_received.clone();
733        let events_received = self.events_received.clone();
734        let lines: Box<dyn Stream<Item = Bytes> + Send + Unpin> = Box::new(
735            FramedRead::new(object_reader, self.state.decoder.framer.clone())
736                .map(|res| {
737                    res.inspect(|bytes| {
738                        bytes_received.emit(ByteSize(bytes.len()));
739                    })
740                    .map_err(|err| {
741                        read_error = Some(err);
742                    })
743                    .ok()
744                })
745                .take_while(|res| ready(res.is_some()))
746                .map(|r| r.expect("validated by take_while")),
747        );
748
749        let lines: Box<dyn Stream<Item = Bytes> + Send + Unpin> = match &self.state.multiline {
750            Some(config) => Box::new(
751                LineAgg::new(
752                    lines.map(|line| ((), line, ())),
753                    line_agg::Logic::new(config.clone()),
754                )
755                .map(|(_src, line, _context, _lastline_context)| line),
756            ),
757            None => lines,
758        };
759
760        let mut stream = lines.flat_map(|line| {
761            let events = match self.state.decoder.deserializer_parse(line) {
762                Ok((events, _events_size)) => events,
763                Err(_error) => {
764                    // Error is handled by `codecs::Decoder`, no further handling
765                    // is needed here.
766                    SmallVec::new()
767                }
768            };
769
770            let events = events
771                .into_iter()
772                .map(|mut event: Event| {
773                    event = event.with_batch_notifier_option(&batch);
774                    if let Some(log_event) = event.maybe_as_log_mut() {
775                        handle_single_log(
776                            log_event,
777                            log_namespace,
778                            &s3_event,
779                            &metadata,
780                            timestamp,
781                        );
782                    }
783                    events_received.emit(CountByteSize(1, event.estimated_json_encoded_size_of()));
784                    event
785                })
786                .collect::<Vec<Event>>();
787            futures::stream::iter(events)
788        });
789
790        let send_error = match self.out.send_event_stream(&mut stream).await {
791            Ok(_) => None,
792            Err(SendError::Closed) => {
793                let (count, _) = stream.size_hint();
794                emit!(StreamClosedError { count });
795                Some(SendError::Closed)
796            }
797            Err(SendError::Timeout) => unreachable!("No timeout is configured here"),
798        };
799
800        // Up above, `lines` captures `read_error`, and eventually is captured by `stream`,
801        // so we explicitly drop it so that we can again utilize `read_error` below.
802        drop(stream);
803
804        let bucket = &s3_event.s3.bucket.name;
805        let duration = download_start.elapsed();
806
807        if read_error.is_some() {
808            emit!(S3ObjectProcessingFailed { bucket, duration });
809        } else {
810            emit!(S3ObjectProcessingSucceeded { bucket, duration });
811        }
812
813        // The BatchNotifier is cloned for each LogEvent in the batch stream, but the last
814        // reference must be dropped before the status of the batch is sent to the channel.
815        drop(batch);
816
817        if let Some(error) = read_error {
818            Err(ProcessingError::ReadObject {
819                source: error,
820                bucket: s3_event.s3.bucket.name.clone(),
821                key: s3_event.s3.object.key.clone(),
822            })
823        } else if let Some(error) = send_error {
824            Err(ProcessingError::PipelineSend {
825                source: error,
826                bucket: s3_event.s3.bucket.name.clone(),
827                key: s3_event.s3.object.key.clone(),
828            })
829        } else {
830            match receiver {
831                None => Ok(()),
832                Some(receiver) => {
833                    let result = receiver.await;
834                    match result {
835                        BatchStatus::Delivered => {
836                            debug!(
837                                message = "S3 object from SQS delivered.",
838                                bucket = s3_event.s3.bucket.name,
839                                key = s3_event.s3.object.key,
840                            );
841                            Ok(())
842                        }
843                        BatchStatus::Errored => Err(ProcessingError::ErrorAcknowledgement {
844                            bucket: s3_event.s3.bucket.name,
845                            key: s3_event.s3.object.key,
846                            region: s3_event.aws_region,
847                        }),
848                        BatchStatus::Rejected => {
849                            if self.state.delete_failed_message {
850                                warn!(
851                                    message =
852                                        "S3 object from SQS was rejected. Deleting failed message.",
853                                    bucket = s3_event.s3.bucket.name,
854                                    key = s3_event.s3.object.key,
855                                );
856                                Ok(())
857                            } else {
858                                Err(ProcessingError::ErrorAcknowledgement {
859                                    bucket: s3_event.s3.bucket.name,
860                                    key: s3_event.s3.object.key,
861                                    region: s3_event.aws_region,
862                                })
863                            }
864                        }
865                    }
866                }
867            }
868        }
869    }
870
871    async fn receive_messages(
872        &mut self,
873    ) -> Result<Vec<Message>, SdkError<ReceiveMessageError, HttpResponse>> {
874        self.state
875            .sqs_client
876            .receive_message()
877            .queue_url(self.state.queue_url.clone())
878            .max_number_of_messages(self.state.max_number_of_messages)
879            .visibility_timeout(self.state.visibility_timeout_secs)
880            .wait_time_seconds(self.state.poll_secs)
881            .send()
882            .map_ok(|res| res.messages.unwrap_or_default())
883            .await
884    }
885
886    async fn delete_messages(
887        &mut self,
888        entries: Vec<DeleteMessageBatchRequestEntry>,
889    ) -> Result<DeleteMessageBatchOutput, SdkError<DeleteMessageBatchError, HttpResponse>> {
890        self.state
891            .sqs_client
892            .delete_message_batch()
893            .queue_url(self.state.queue_url.clone())
894            .set_entries(Some(entries))
895            .send()
896            .await
897    }
898
899    async fn send_messages(
900        &mut self,
901        entries: Vec<SendMessageBatchRequestEntry>,
902        queue_url: String,
903    ) -> Result<SendMessageBatchOutput, SdkError<SendMessageBatchError, HttpResponse>> {
904        self.state
905            .sqs_client
906            .send_message_batch()
907            .queue_url(queue_url.clone())
908            .set_entries(Some(entries))
909            .send()
910            .await
911    }
912}
913
914fn handle_single_log(
915    log: &mut LogEvent,
916    log_namespace: LogNamespace,
917    s3_event: &S3EventRecord,
918    metadata: &Option<HashMap<String, String>>,
919    timestamp: Option<DateTime<Utc>>,
920) {
921    log_namespace.insert_source_metadata(
922        AwsS3Config::NAME,
923        log,
924        Some(LegacyKey::Overwrite(path!("bucket"))),
925        path!("bucket"),
926        Bytes::from(s3_event.s3.bucket.name.as_bytes().to_vec()),
927    );
928
929    log_namespace.insert_source_metadata(
930        AwsS3Config::NAME,
931        log,
932        Some(LegacyKey::Overwrite(path!("object"))),
933        path!("object"),
934        Bytes::from(s3_event.s3.object.key.as_bytes().to_vec()),
935    );
936    log_namespace.insert_source_metadata(
937        AwsS3Config::NAME,
938        log,
939        Some(LegacyKey::Overwrite(path!("region"))),
940        path!("region"),
941        Bytes::from(s3_event.aws_region.as_bytes().to_vec()),
942    );
943
944    if let Some(metadata) = metadata {
945        for (key, value) in metadata {
946            log_namespace.insert_source_metadata(
947                AwsS3Config::NAME,
948                log,
949                Some(LegacyKey::Overwrite(path!(key))),
950                path!("metadata", key.as_str()),
951                value.clone(),
952            );
953        }
954    }
955
956    log_namespace.insert_vector_metadata(
957        log,
958        log_schema().source_type_key(),
959        path!("source_type"),
960        Bytes::from_static(AwsS3Config::NAME.as_bytes()),
961    );
962
963    // This handles the transition from the original timestamp logic. Originally the
964    // `timestamp_key` was populated by the `last_modified` time on the object, falling
965    // back to calling `now()`.
966    match log_namespace {
967        LogNamespace::Vector => {
968            if let Some(timestamp) = timestamp {
969                log.insert(metadata_path!(AwsS3Config::NAME, "timestamp"), timestamp);
970            }
971
972            log.insert(metadata_path!("vector", "ingest_timestamp"), Utc::now());
973        }
974        LogNamespace::Legacy => {
975            if let Some(timestamp_key) = log_schema().timestamp_key() {
976                log.try_insert(
977                    (PathPrefix::Event, timestamp_key),
978                    timestamp.unwrap_or_else(Utc::now),
979                );
980            }
981        }
982    };
983}
984
985// https://docs.aws.amazon.com/sns/latest/dg/sns-sqs-as-subscriber.html
986#[derive(Clone, Debug, Deserialize)]
987#[serde(rename_all = "PascalCase")]
988pub struct SnsNotification {
989    pub message: String,
990    pub timestamp: DateTime<Utc>,
991}
992
993// https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-enable-disable-notification-intro.html
994#[derive(Clone, Debug, Deserialize)]
995#[serde(untagged)]
996enum SqsEvent {
997    Event(S3Event),
998    TestEvent(S3TestEvent),
999}
1000
1001#[derive(Clone, Debug, Deserialize)]
1002#[serde(rename_all = "PascalCase")]
1003pub struct S3TestEvent {
1004    pub service: String,
1005    pub event: S3EventName,
1006    pub bucket: String,
1007}
1008
1009// https://docs.aws.amazon.com/AmazonS3/latest/dev/notification-content-structure.html
1010#[derive(Clone, Debug, Deserialize, Serialize)]
1011#[serde(rename_all = "PascalCase")]
1012pub struct S3Event {
1013    pub records: Vec<S3EventRecord>,
1014}
1015
1016#[derive(Clone, Debug, Deserialize, Serialize)]
1017#[serde(rename_all = "camelCase")]
1018pub struct S3EventRecord {
1019    pub event_version: S3EventVersion,
1020    pub event_source: String,
1021    pub aws_region: String,
1022    pub event_name: S3EventName,
1023    pub event_time: DateTime<Utc>,
1024
1025    pub s3: S3Message,
1026}
1027
1028#[derive(Clone, Debug)]
1029pub struct S3EventVersion {
1030    pub major: u64,
1031    pub minor: u64,
1032}
1033
1034impl From<S3EventVersion> for semver::Version {
1035    fn from(v: S3EventVersion) -> semver::Version {
1036        semver::Version::new(v.major, v.minor, 0)
1037    }
1038}
1039
1040// https://docs.aws.amazon.com/AmazonS3/latest/dev/notification-content-structure.html
1041// <major>.<minor>
1042impl<'de> Deserialize<'de> for S3EventVersion {
1043    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1044    where
1045        D: Deserializer<'de>,
1046    {
1047        use serde::de::Error;
1048
1049        let s = String::deserialize(deserializer)?;
1050
1051        let mut parts = s.splitn(2, '.');
1052
1053        let major = parts
1054            .next()
1055            .ok_or_else(|| D::Error::custom("Missing major version number"))?
1056            .parse::<u64>()
1057            .map_err(D::Error::custom)?;
1058
1059        let minor = parts
1060            .next()
1061            .ok_or_else(|| D::Error::custom("Missing minor version number"))?
1062            .parse::<u64>()
1063            .map_err(D::Error::custom)?;
1064
1065        Ok(S3EventVersion { major, minor })
1066    }
1067}
1068
1069impl Serialize for S3EventVersion {
1070    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1071    where
1072        S: Serializer,
1073    {
1074        serializer.serialize_str(&format!("{}.{}", self.major, self.minor))
1075    }
1076}
1077
1078#[derive(Clone, Debug)]
1079pub struct S3EventName {
1080    pub kind: String,
1081    pub name: String,
1082}
1083
1084// https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#supported-notification-event-types
1085//
1086// we could use enums here, but that seems overly brittle as deserialization would break if they
1087// add new event types or names
1088impl<'de> Deserialize<'de> for S3EventName {
1089    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1090    where
1091        D: Deserializer<'de>,
1092    {
1093        use serde::de::Error;
1094
1095        let s = String::deserialize(deserializer)?;
1096
1097        let mut parts = s.splitn(2, ':');
1098
1099        let kind = parts
1100            .next()
1101            .ok_or_else(|| D::Error::custom("Missing event kind"))?
1102            .parse::<String>()
1103            .map_err(D::Error::custom)?;
1104
1105        let name = parts
1106            .next()
1107            .ok_or_else(|| D::Error::custom("Missing event name"))?
1108            .parse::<String>()
1109            .map_err(D::Error::custom)?;
1110
1111        Ok(S3EventName { kind, name })
1112    }
1113}
1114
1115impl Serialize for S3EventName {
1116    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1117    where
1118        S: Serializer,
1119    {
1120        serializer.serialize_str(&format!("{}:{}", self.kind, self.name))
1121    }
1122}
1123
1124#[derive(Clone, Debug, Deserialize, Serialize)]
1125#[serde(rename_all = "camelCase")]
1126pub struct S3Message {
1127    pub bucket: S3Bucket,
1128    pub object: S3Object,
1129}
1130
1131#[derive(Clone, Debug, Deserialize, Serialize)]
1132#[serde(rename_all = "camelCase")]
1133pub struct S3Bucket {
1134    pub name: String,
1135}
1136
1137#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
1138#[serde(rename_all = "camelCase")]
1139pub struct S3Object {
1140    // S3ObjectKeys are URL encoded
1141    // https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-content-structure.html
1142    #[serde(with = "urlencoded_string")]
1143    pub key: String,
1144}
1145
1146mod urlencoded_string {
1147    use percent_encoding::{percent_decode, utf8_percent_encode};
1148
1149    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
1150    where
1151        D: serde::de::Deserializer<'de>,
1152    {
1153        use serde::de::Error;
1154
1155        serde::de::Deserialize::deserialize(deserializer).and_then(|s: &[u8]| {
1156            let decoded = if s.contains(&b'+') {
1157                // AWS encodes spaces as `+` rather than `%20`, so we first need to handle this.
1158                let s = s
1159                    .iter()
1160                    .map(|c| if *c == b'+' { b' ' } else { *c })
1161                    .collect::<Vec<_>>();
1162                percent_decode(&s).decode_utf8().map(Into::into)
1163            } else {
1164                percent_decode(s).decode_utf8().map(Into::into)
1165            };
1166
1167            decoded
1168                .map_err(|err| D::Error::custom(format!("error url decoding S3 object key: {err}")))
1169        })
1170    }
1171
1172    pub fn serialize<S>(s: &str, serializer: S) -> Result<S::Ok, S::Error>
1173    where
1174        S: serde::ser::Serializer,
1175    {
1176        serializer.serialize_str(
1177            &utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).collect::<String>(),
1178        )
1179    }
1180}
1181
1182#[test]
1183fn test_key_deserialize() {
1184    let value = serde_json::from_str(r#"{"key": "noog+nork"}"#).unwrap();
1185    assert_eq!(
1186        S3Object {
1187            key: "noog nork".to_string(),
1188        },
1189        value
1190    );
1191
1192    let value = serde_json::from_str(r#"{"key": "noog%2bnork"}"#).unwrap();
1193    assert_eq!(
1194        S3Object {
1195            key: "noog+nork".to_string(),
1196        },
1197        value
1198    );
1199}
1200
1201#[test]
1202fn test_s3_testevent() {
1203    let value: S3TestEvent = serde_json::from_str(
1204        r#"{
1205        "Service":"Amazon S3",
1206        "Event":"s3:TestEvent",
1207        "Time":"2014-10-13T15:57:02.089Z",
1208        "Bucket":"bucketname",
1209        "RequestId":"5582815E1AEA5ADF",
1210        "HostId":"8cLeGAmw098X5cv4Zkwcmo8vvZa3eH3eKxsPzbB9wrR+YstdA6Knx4Ip8EXAMPLE"
1211     }"#,
1212    )
1213    .unwrap();
1214
1215    assert_eq!(value.service, "Amazon S3".to_string());
1216    assert_eq!(value.bucket, "bucketname".to_string());
1217    assert_eq!(value.event.kind, "s3".to_string());
1218    assert_eq!(value.event.name, "TestEvent".to_string());
1219}
1220
1221#[test]
1222fn test_s3_sns_testevent() {
1223    let sns_value: SnsNotification = serde_json::from_str(
1224        r#"{
1225        "Type" : "Notification",
1226        "MessageId" : "63a3f6b6-d533-4a47-aef9-fcf5cf758c76",
1227        "TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
1228        "Subject" : "Testing publish to subscribed queues",
1229        "Message" : "{\"Bucket\":\"bucketname\",\"Event\":\"s3:TestEvent\",\"HostId\":\"8cLeGAmw098X5cv4Zkwcmo8vvZa3eH3eKxsPzbB9wrR+YstdA6Knx4Ip8EXAMPLE\",\"RequestId\":\"5582815E1AEA5ADF\",\"Service\":\"Amazon S3\",\"Time\":\"2014-10-13T15:57:02.089Z\"}",
1230        "Timestamp" : "2012-03-29T05:12:16.901Z",
1231        "SignatureVersion" : "1",
1232        "Signature" : "EXAMPLEnTrFPa3...",
1233        "SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
1234        "UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:123456789012:MyTopic:c7fe3a54-ab0e-4ec2-88e0-db410a0f2bee"
1235     }"#,
1236    ).unwrap();
1237
1238    assert_eq!(
1239        sns_value.timestamp,
1240        DateTime::parse_from_rfc3339("2012-03-29T05:12:16.901Z")
1241            .unwrap()
1242            .to_utc()
1243    );
1244
1245    let value: S3TestEvent = serde_json::from_str(sns_value.message.as_ref()).unwrap();
1246
1247    assert_eq!(value.service, "Amazon S3".to_string());
1248    assert_eq!(value.bucket, "bucketname".to_string());
1249    assert_eq!(value.event.kind, "s3".to_string());
1250    assert_eq!(value.event.name, "TestEvent".to_string());
1251}
1252
1253#[test]
1254fn parse_sqs_config() {
1255    let config: Config = serde_yaml::from_str(
1256        r#"queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1257"#,
1258    )
1259    .unwrap();
1260    assert_eq!(
1261        config.queue_url,
1262        "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1263    );
1264    assert!(config.deferred.is_none());
1265
1266    let config: Config = serde_yaml::from_str(indoc::indoc! {r#"
1267        queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1268        deferred:
1269          queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyDeferredQueue"
1270          max_age_secs: 3600
1271    "#})
1272    .unwrap();
1273    assert_eq!(
1274        config.queue_url,
1275        "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1276    );
1277    let Some(deferred) = config.deferred else {
1278        panic!("Expected deferred config");
1279    };
1280    assert_eq!(
1281        deferred.queue_url,
1282        "https://sqs.us-east-1.amazonaws.com/123456789012/MyDeferredQueue"
1283    );
1284    assert_eq!(deferred.max_age_secs, 3600);
1285
1286    let test: Result<Config, serde_yaml::Error> = serde_yaml::from_str(indoc::indoc! {r#"
1287        queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1288        deferred:
1289          max_age_secs: 3600
1290    "#});
1291    assert!(test.is_err());
1292
1293    let test: Result<Config, serde_yaml::Error> = serde_yaml::from_str(indoc::indoc! {r#"
1294        queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue"
1295        deferred:
1296          queue_url: "https://sqs.us-east-1.amazonaws.com/123456789012/MyDeferredQueue"
1297    "#});
1298    assert!(test.is_err());
1299}