use super::google_logger::{GoogleLogger, LogMapper};
use crate::{GoogleWriterConfig, google_writer::GoogleWriterHandle};
use serde_json::Value;
use std::sync::Arc;
use tokio::{
sync::{RwLock, mpsc, oneshot},
task::JoinHandle,
time::sleep,
};
enum ControlMessage {
Flush(oneshot::Sender<()>),
Shutdown(oneshot::Sender<()>),
}
#[derive(Debug)]
pub struct GoogleWriterRuntime<M: LogMapper> {
handle: GoogleWriterHandle,
control_tx: mpsc::Sender<ControlMessage>,
shutdown_handle: Option<JoinHandle<()>>,
_marker: std::marker::PhantomData<M>,
}
impl<M: LogMapper + Send + Sync + 'static> GoogleWriterRuntime<M> {
pub fn new(google_logger: GoogleLogger<M>, config: GoogleWriterConfig) -> Self {
let (tx, mut rx) = mpsc::unbounded_channel::<Value>();
let (control_tx, mut control_rx) = mpsc::channel::<ControlMessage>(8);
let logger = Arc::new(RwLock::new(google_logger));
let logger_clone = logger.clone();
let handle_task = tokio::spawn(async move {
let mut buffer = Vec::with_capacity(config.max_batch);
loop {
tokio::select! {
maybe_entry = rx.recv() => {
match maybe_entry {
Some(entry) => {
buffer.push(entry);
if buffer.len() >= config.max_batch {
Self::flush_batch(&logger_clone, std::mem::take(&mut buffer)).await;
}
}
None => {
if !buffer.is_empty() {
Self::flush_batch(&logger_clone, buffer).await;
}
break;
}
}
}
Some(ctrl) = control_rx.recv() => {
match ctrl {
ControlMessage::Flush(reply) => {
if !buffer.is_empty() {
Self::flush_batch(&logger_clone, std::mem::take(&mut buffer)).await;
}
let _ = reply.send(());
}
ControlMessage::Shutdown(reply) => {
if !buffer.is_empty() {
Self::flush_batch(&logger_clone, buffer).await;
}
let _ = reply.send(());
break;
}
}
}
_ = sleep(config.max_delay), if !buffer.is_empty() => {
Self::flush_batch(&logger_clone, std::mem::take(&mut buffer)).await;
}
}
}
tracing::debug!("Background task shut down cleanly.");
});
Self {
handle: GoogleWriterHandle { sender: tx },
control_tx,
shutdown_handle: Some(handle_task),
_marker: std::marker::PhantomData,
}
}
pub fn writer(&self) -> GoogleWriterHandle {
self.handle.clone()
}
pub async fn flush_and_wait(&self) {
let (tx, rx) = oneshot::channel();
let _ = self.control_tx.send(ControlMessage::Flush(tx)).await;
let _ = rx.await;
}
pub async fn shutdown(mut self) {
if let Some(handle) = self.shutdown_handle.take() {
let (tx, rx) = oneshot::channel();
let _ = self.control_tx.send(ControlMessage::Shutdown(tx)).await;
let _ = rx.await;
if let Err(err) = handle.await {
tracing::error!("Shutdown task panicked: {:?}", err);
}
}
}
async fn flush_batch(logger: &Arc<RwLock<GoogleLogger<M>>>, batch: Vec<Value>) {
let mut guard = logger.write().await;
if let Err(err) = guard.write_logs(batch).await {
tracing::error!("Failed to write log batch: {err}");
}
}
}