use reqwest::Client;
use std::time::Duration;
use tokio::time::sleep;
use crate::config::BetterStackConfig;
use crate::error::Result;
use crate::log_event::LogEvent;
lazy_static::lazy_static! {
static ref HTTP_CLIENT: Client = Client::builder()
.timeout(Duration::from_secs(30))
.build()
.expect("Failed to create HTTP client");
}
pub(crate) async fn send_batch(batch: Vec<LogEvent>, config: &BetterStackConfig) -> Result<()> {
let mut retries = 0;
let mut delay = config.initial_retry_delay;
loop {
match send_request(&batch, config).await {
Ok(_) => return Ok(()),
Err(e) => {
if retries >= config.max_retries {
return Err(e);
}
eprintln!(
"Failed to send logs to Better Stack (attempt {}/{}): {}. Retrying in {:?}",
retries + 1,
config.max_retries,
e,
delay
);
sleep(delay).await;
delay = std::cmp::min(delay * 2, config.max_retry_delay);
retries += 1;
}
}
}
}
async fn send_request(batch: &[LogEvent], config: &BetterStackConfig) -> Result<()> {
let (body, content_type) = {
#[cfg(feature = "json")]
{
let body = serde_json::to_vec(&batch)?;
(body, "application/json")
}
#[cfg(feature = "message_pack")]
{
let body = rmp_serde::to_vec_named(&batch)?;
(body, "application/msgpack")
}
};
let response = HTTP_CLIENT
.post(config.ingestion_url())
.bearer_auth(&config.source_token)
.header("Content-Type", content_type)
.body(body)
.send()
.await?;
if !response.status().is_success() {
let status = response.status();
let body = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(crate::error::BetterStackError::RuntimeError(format!(
"Better Stack API error: {} - {}",
status, body
)));
}
Ok(())
}