Skip to main content

slog_cloud/
drain.rs

1use std::{
2    future::Future,
3    pin::Pin,
4    task::{Context, Poll},
5    time::Duration,
6};
7
8use futures::FutureExt;
9use slog::{Drain, OwnedKVList, Record};
10
11use crate::{
12    batch::BatchBuilder,
13    client::Client,
14    queue::{BatchMessageQueue, MessageQueue, MessageQueueSender},
15    sender::SenderTask,
16    serializer::LogMessageSerializer,
17};
18
19/// Cloud drain builder.
20pub struct CloudDrainBuilder {
21    queue_capacity: Option<usize>,
22    max_concurrency: usize,
23    initial_retry_delay: Duration,
24    max_retry_delay: Duration,
25    max_retries: Option<u32>,
26    debug: bool,
27}
28
29impl CloudDrainBuilder {
30    /// Create a new cloud drain builder.
31    #[inline]
32    pub const fn new() -> Self {
33        Self {
34            queue_capacity: None,
35            max_concurrency: 16,
36            initial_retry_delay: Duration::from_secs(1),
37            max_retry_delay: Duration::from_secs(60),
38            max_retries: None,
39            debug: false,
40        }
41    }
42
43    /// Set the log message queue capacity or `None` for unlimited (the default
44    /// is `None`).
45    #[inline]
46    pub fn queue_capacity(mut self, queue_capacity: Option<usize>) -> Self {
47        self.queue_capacity = queue_capacity;
48        self
49    }
50
51    /// Set the maximum number of concurrent message sends (the default is 16).
52    #[inline]
53    pub fn max_concurrency(mut self, max_concurrency: usize) -> Self {
54        assert!(max_concurrency > 0);
55
56        self.max_concurrency = max_concurrency;
57        self
58    }
59
60    /// Set the initial retry delay (the default is 1 second).
61    #[inline]
62    pub fn initial_retry_delay(mut self, initial_retry_delay: Duration) -> Self {
63        self.initial_retry_delay = initial_retry_delay;
64        self
65    }
66
67    /// Set the maximum retry delay (the default is 60 seconds).
68    #[inline]
69    pub fn max_retry_delay(mut self, max_retry_delay: Duration) -> Self {
70        self.max_retry_delay = max_retry_delay;
71        self
72    }
73
74    /// Set the maximum number of retries or `None` for unlimited (the default
75    /// is unlimited).
76    #[inline]
77    pub fn max_retries(mut self, max_retries: Option<u32>) -> Self {
78        self.max_retries = max_retries;
79        self
80    }
81
82    /// Enable or disable debug mode (the default is disabled).
83    ///
84    /// In the debug mode you'll be able to see some runtime info on stderr
85    /// that will help you with setting up the drain (e.g. failed requests).
86    /// With debug mode disabled, all errors will be silently ignored.
87    #[inline]
88    pub fn debug(mut self, debug: bool) -> Self {
89        self.debug = debug;
90        self
91    }
92
93    /// Build the cloud drain.
94    ///
95    /// # Arguments
96    /// * `client` - a cloud service client
97    /// * `serializer` - log message serializer
98    /// * `batch_builder` - log message batch builder
99    pub fn build<C, S, M, B, BB>(
100        self,
101        client: C,
102        serializer: S,
103        batch_builder: BB,
104    ) -> (CloudDrain<S, M>, CloudDrainTask, CloudDrainHandle<M>)
105    where
106        C: Client<Message = B> + Send + Sync + 'static,
107        B: Clone + Send + Sync + 'static,
108        BB: BatchBuilder<Item = M, Batch = B> + Send + 'static,
109        M: Send + 'static,
110    {
111        let (messages, message_sender) = MessageQueue::new(self.queue_capacity);
112
113        let messages = messages.batch(batch_builder);
114
115        let sender_task = SenderTask::builder()
116            .max_concurrency(self.max_concurrency)
117            .initial_retry_delay(self.initial_retry_delay)
118            .max_retry_delay(self.max_retry_delay)
119            .max_retries(self.max_retries)
120            .build(client, messages);
121
122        let task = CloudDrainTask { inner: sender_task };
123
124        let handle = CloudDrainHandle {
125            sender: message_sender.clone(),
126        };
127
128        let drain = CloudDrain {
129            serializer,
130            sender: message_sender,
131            debug: self.debug,
132        };
133
134        (drain, task, handle)
135    }
136}
137
138impl Default for CloudDrainBuilder {
139    #[inline]
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145/// Generic cloud log drain.
146pub struct CloudDrain<S, M> {
147    serializer: S,
148    sender: MessageQueueSender<M>,
149    debug: bool,
150}
151
152impl<S, M> Drain for CloudDrain<S, M>
153where
154    S: LogMessageSerializer<Serialized = M>,
155{
156    type Ok = ();
157    type Err = ();
158
159    fn log(&self, record: &Record, logger_values: &OwnedKVList) -> Result<(), ()> {
160        match self.serializer.serialize(record, logger_values) {
161            Ok(msg) => {
162                let res = self.sender.send(msg);
163
164                if self.debug && res.is_err() {
165                    eprintln!("unable to send a log message: the background sender is not running");
166                }
167            }
168            Err(err) if self.debug => {
169                eprintln!("unable to serialize a log message: {err}");
170            }
171            Err(_) => (),
172        }
173
174        Ok(())
175    }
176}
177
178/// Task for a cloud log drain.
179///
180/// The task needs to be polled to drive the drain. It can be spawned as a
181/// background task using [`tokio::spawn`].
182pub struct CloudDrainTask {
183    inner: SenderTask,
184}
185
186impl Future for CloudDrainTask {
187    type Output = ();
188
189    #[inline]
190    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
191        self.inner.poll_unpin(cx)
192    }
193}
194
195/// Handle for a cloud log drain.
196pub struct CloudDrainHandle<M> {
197    sender: MessageQueueSender<M>,
198}
199
200impl<M> CloudDrainHandle<M> {
201    /// Flush any pending log messages.
202    pub async fn flush(&self) {
203        self.sender.flush().await
204    }
205
206    /// Flush any pending log messages.
207    ///
208    /// # Panics
209    /// The method panics if called from an asynchronous execution context.
210    pub fn blocking_flush(&self) {
211        self.sender.blocking_flush();
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use std::{future::Future, time::Duration};
218
219    use bytes::Bytes;
220    use futures::poll;
221    use slog::{Drain, Logger, OwnedKVList, Record, info, o};
222    use tokio::sync::{mpsc, oneshot};
223
224    use crate::{
225        batch::BatchBuilder,
226        client::{Client, HttpClientError, SendError, StatusCode},
227        serializer::LogMessageSerializer,
228    };
229
230    use super::{CloudDrainBuilder, CloudDrainHandle, CloudDrainTask};
231
232    /// Serializer producing just the log message text.
233    struct MessageSerializer;
234
235    impl LogMessageSerializer for MessageSerializer {
236        type Serialized = String;
237
238        fn serialize(&self, record: &Record, _: &OwnedKVList) -> slog::Result<String> {
239            Ok(record.msg().to_string())
240        }
241    }
242
243    /// Batch builder grouping up to a given number of messages.
244    struct VecBatchBuilder {
245        batch: Vec<String>,
246        max_len: usize,
247    }
248
249    impl BatchBuilder for VecBatchBuilder {
250        type Item = String;
251        type Batch = Vec<String>;
252
253        fn push(&mut self, item: String) -> Option<Vec<String>> {
254            let res = if self.batch.len() < self.max_len {
255                None
256            } else {
257                Some(std::mem::take(&mut self.batch))
258            };
259
260            self.batch.push(item);
261
262            res
263        }
264
265        fn flush(&mut self) -> Option<Vec<String>> {
266            if self.batch.is_empty() {
267                None
268            } else {
269                Some(std::mem::take(&mut self.batch))
270            }
271        }
272    }
273
274    /// Send request intercepted by the mock client.
275    struct Request {
276        batch: Vec<String>,
277        response: oneshot::Sender<Result<(), SendError>>,
278    }
279
280    impl Request {
281        /// Respond with `Ok(())`.
282        fn respond_ok(self) {
283            let _ = self.response.send(Ok(()));
284        }
285
286        /// Response with unexpected status code.
287        fn respond_status(self, status: StatusCode) {
288            let err = HttpClientError::UnexpectedStatusCode(status, Bytes::new());
289
290            let _ = self.response.send(Err(err.into()));
291        }
292    }
293
294    /// Client passing all send requests to the test.
295    struct MockClient {
296        requests: mpsc::UnboundedSender<Request>,
297    }
298
299    impl Client for MockClient {
300        type Message = Vec<String>;
301
302        async fn send(&self, batch: Vec<String>) -> Result<(), SendError> {
303            let (tx, rx) = oneshot::channel();
304
305            let _ = self.requests.send(Request {
306                batch,
307                response: tx,
308            });
309
310            // treat requests left behind by a finished test as delivered
311            rx.await.unwrap_or(Ok(()))
312        }
313    }
314
315    /// Helper struct.
316    struct TestDrain {
317        logger: Logger,
318        task: CloudDrainTask,
319        handle: CloudDrainHandle<String>,
320        requests: mpsc::UnboundedReceiver<Request>,
321    }
322
323    /// Get a drain builder with negligible retry delays.
324    fn builder() -> CloudDrainBuilder {
325        CloudDrainBuilder::new()
326            .initial_retry_delay(Duration::from_millis(1))
327            .max_retry_delay(Duration::from_millis(1))
328    }
329
330    /// Build a test drain with a given maximum batch length.
331    fn build(builder: CloudDrainBuilder, max_batch_len: usize) -> TestDrain {
332        let (tx, rx) = mpsc::unbounded_channel();
333
334        let client = MockClient { requests: tx };
335
336        let batch_builder = VecBatchBuilder {
337            batch: Vec::new(),
338            max_len: max_batch_len,
339        };
340
341        let (drain, task, handle) = builder.build(client, MessageSerializer, batch_builder);
342
343        TestDrain {
344            logger: Logger::root(drain.fuse(), o!()),
345            task,
346            handle,
347            requests: rx,
348        }
349    }
350
351    /// Panic if the future takes more than 5 seconds to complete.
352    async fn within<F>(fut: F) -> F::Output
353    where
354        F: Future,
355    {
356        tokio::time::timeout(Duration::from_secs(5), fut)
357            .await
358            .expect("timed out")
359    }
360
361    /// Get the next request or panic.
362    async fn next_request(requests: &mut mpsc::UnboundedReceiver<Request>) -> Request {
363        within(requests.recv())
364            .await
365            .expect("request channel closed")
366    }
367
368    /// Wait 50 milliseconds for a request and panic if there is one.
369    async fn assert_no_request(requests: &mut mpsc::UnboundedReceiver<Request>) {
370        tokio::time::sleep(Duration::from_millis(50)).await;
371
372        assert!(requests.try_recv().is_err(), "unexpected request");
373    }
374
375    #[tokio::test]
376    async fn batches_queued_messages() {
377        let TestDrain {
378            logger,
379            task,
380            handle: _handle,
381            mut requests,
382        } = build(builder(), 2);
383
384        for msg in ["a", "b", "c", "d", "e"] {
385            info!(logger, "{}", msg);
386        }
387
388        tokio::spawn(task);
389
390        let mut batches = Vec::new();
391
392        for _ in 0..3 {
393            let req = next_request(&mut requests).await;
394
395            batches.push(req.batch.clone());
396
397            req.respond_ok();
398        }
399
400        // the batches are sent concurrently
401        batches.sort();
402
403        assert_eq!(batches, [vec!["a", "b"], vec!["c", "d"], vec!["e"]]);
404    }
405
406    #[tokio::test]
407    async fn sends_partial_batch_without_waiting() {
408        let TestDrain {
409            logger,
410            task,
411            handle: _handle,
412            mut requests,
413        } = build(builder(), 10);
414
415        tokio::spawn(task);
416
417        info!(logger, "a");
418
419        assert_eq!(next_request(&mut requests).await.batch, ["a"]);
420    }
421
422    #[tokio::test]
423    async fn flush_waits_for_message_delivery() {
424        let TestDrain {
425            logger,
426            task,
427            handle,
428            mut requests,
429        } = build(builder(), 10);
430
431        tokio::spawn(task);
432
433        info!(logger, "a");
434
435        let mut flush = std::pin::pin!(handle.flush());
436
437        assert!(poll!(flush.as_mut()).is_pending());
438
439        let req = next_request(&mut requests).await;
440
441        tokio::time::sleep(Duration::from_millis(50)).await;
442
443        assert!(poll!(flush.as_mut()).is_pending());
444
445        req.respond_ok();
446
447        within(flush).await;
448    }
449
450    #[tokio::test]
451    async fn flush_does_not_wait_for_messages_logged_after_it() {
452        let TestDrain {
453            logger,
454            task,
455            handle,
456            mut requests,
457        } = build(builder(), 10);
458
459        tokio::spawn(task);
460
461        info!(logger, "a");
462
463        let mut flush = std::pin::pin!(handle.flush());
464
465        assert!(poll!(flush.as_mut()).is_pending());
466
467        info!(logger, "b");
468
469        let req = next_request(&mut requests).await;
470
471        assert_eq!(req.batch, ["a"]);
472
473        req.respond_ok();
474
475        let req = next_request(&mut requests).await;
476
477        assert_eq!(req.batch, ["b"]);
478
479        // the flush completes while "b" is still in flight
480        within(flush).await;
481    }
482
483    #[tokio::test]
484    async fn retries_failed_sends() {
485        let TestDrain {
486            logger,
487            task,
488            handle,
489            mut requests,
490        } = build(builder(), 10);
491
492        tokio::spawn(task);
493
494        info!(logger, "a");
495
496        next_request(&mut requests)
497            .await
498            .respond_status(StatusCode::SERVICE_UNAVAILABLE);
499
500        next_request(&mut requests)
501            .await
502            .respond_status(StatusCode::TOO_MANY_REQUESTS);
503
504        let req = next_request(&mut requests).await;
505
506        assert_eq!(req.batch, ["a"]);
507
508        req.respond_ok();
509
510        within(handle.flush()).await;
511
512        assert_no_request(&mut requests).await;
513    }
514
515    #[tokio::test]
516    async fn gives_up_after_max_retries() {
517        let TestDrain {
518            logger,
519            task,
520            handle,
521            mut requests,
522        } = build(builder().max_retries(Some(2)), 10);
523
524        tokio::spawn(task);
525
526        info!(logger, "a");
527
528        for _ in 0..3 {
529            next_request(&mut requests)
530                .await
531                .respond_status(StatusCode::SERVICE_UNAVAILABLE);
532        }
533
534        within(handle.flush()).await;
535
536        assert_no_request(&mut requests).await;
537    }
538
539    #[tokio::test]
540    async fn does_not_retry_non_retryable_errors() {
541        let TestDrain {
542            logger,
543            task,
544            handle,
545            mut requests,
546        } = build(builder(), 10);
547
548        tokio::spawn(task);
549
550        info!(logger, "a");
551
552        next_request(&mut requests)
553            .await
554            .respond_status(StatusCode::BAD_REQUEST);
555
556        within(handle.flush()).await;
557
558        assert_no_request(&mut requests).await;
559    }
560
561    #[tokio::test]
562    async fn limits_concurrent_sends() {
563        let TestDrain {
564            logger,
565            task,
566            handle: _handle,
567            mut requests,
568        } = build(builder().max_concurrency(2), 1);
569
570        for msg in ["a", "b", "c"] {
571            info!(logger, "{}", msg);
572        }
573
574        tokio::spawn(task);
575
576        let first = next_request(&mut requests).await;
577        let second = next_request(&mut requests).await;
578
579        assert_no_request(&mut requests).await;
580
581        let mut batches = vec![first.batch.clone(), second.batch.clone()];
582
583        first.respond_ok();
584
585        batches.push(next_request(&mut requests).await.batch);
586        batches.sort();
587
588        assert_eq!(batches, [["a"], ["b"], ["c"]]);
589    }
590
591    #[tokio::test]
592    async fn bounded_queue_drops_oldest_messages_but_keeps_flushes() {
593        let TestDrain {
594            logger,
595            task,
596            handle,
597            mut requests,
598        } = build(builder().queue_capacity(Some(1)), 10);
599
600        let mut flush = std::pin::pin!(handle.flush());
601
602        assert!(poll!(flush.as_mut()).is_pending());
603
604        // the flush stays in front of "b" even though "a" gets dropped
605        info!(logger, "a");
606        info!(logger, "b");
607
608        tokio::spawn(task);
609
610        let req = next_request(&mut requests).await;
611
612        assert_eq!(req.batch, ["b"]);
613
614        // the flush completes while "b" is still in flight
615        within(flush).await;
616    }
617
618    #[tokio::test]
619    async fn flush_completes_when_task_is_dropped() {
620        let TestDrain {
621            logger,
622            task,
623            handle,
624            requests: _requests,
625        } = build(builder(), 10);
626
627        info!(logger, "a");
628
629        let mut flush = std::pin::pin!(handle.flush());
630
631        assert!(poll!(flush.as_mut()).is_pending());
632
633        std::mem::drop(task);
634
635        within(flush).await;
636        within(handle.flush()).await;
637    }
638
639    #[tokio::test]
640    async fn task_delivers_pending_messages_before_finishing() {
641        let TestDrain {
642            logger,
643            task,
644            handle,
645            mut requests,
646        } = build(builder(), 10);
647
648        info!(logger, "a");
649
650        std::mem::drop(logger);
651        std::mem::drop(handle);
652
653        let task = tokio::spawn(task);
654
655        let req = next_request(&mut requests).await;
656
657        assert_eq!(req.batch, ["a"]);
658
659        tokio::time::sleep(Duration::from_millis(50)).await;
660
661        assert!(!task.is_finished());
662
663        req.respond_ok();
664
665        within(task).await.unwrap();
666    }
667}