#![warn(
clippy::all,
clippy::missing_docs_in_private_items,
clippy::nursery,
clippy::pedantic,
missing_docs
)]
use anyhow::{anyhow, Result};
use parking_lot::{Condvar, Mutex};
use reqwest::Client;
use sentry::{
Dsn, Event, Options, RawEnvelope, Request, Transport as SentryTransport, TransportShutdown,
};
use sentry_contrib_native as sentry;
use std::{convert::TryInto, str::FromStr, sync::Arc, time::Duration};
use tokio::{
sync::mpsc::{self, Sender},
task,
};
async fn send_sentry_request(client: &Client, request: Request) -> Result<()> {
let request = request.map(|body| body.as_bytes().to_vec());
let response = client
.execute(request.try_into()?)
.await
.map_err(|e| anyhow!("Failed to send Sentry request: {}", e))?;
response
.error_for_status()
.map_err(|e| anyhow!("Received error response from Sentry: {}", e))?;
Ok(())
}
struct Transport {
client: Client,
sender: Sender<RawEnvelope>,
shutdown: Arc<(Mutex<()>, Condvar)>,
}
impl Transport {
fn new(client: Client, options: &Options) -> Self {
let (sender, mut receiver) = mpsc::channel::<RawEnvelope>(1024);
let shutdown = Arc::new((Mutex::new(()), Condvar::new()));
let transport = Self {
client,
sender,
shutdown: shutdown.clone(),
};
let client = transport.client.clone();
let dsn = Dsn::from_str(options.dsn().expect("no DSN found")).expect("invalid DSN");
tokio::spawn(async move {
while let Some(envelope) = receiver.recv().await {
let req = envelope.to_request(dsn.clone());
match send_sentry_request(&client, req).await {
Ok(_) => eprintln!("successfully sent sentry envelope"),
Err(err) => eprintln!("failed to send sentry envelope: {}", err),
}
}
let (lock, cvar) = &*shutdown;
let _shutdown_lock = lock.lock();
cvar.notify_one();
});
transport
}
}
impl SentryTransport for Transport {
fn send(&self, envelope: RawEnvelope) {
let sender = self.sender.clone();
task::spawn(async move {
if let Err(err) = sender.send(envelope).await {
eprintln!("failed to send envelope to send queue: {}", err);
}
});
}
fn shutdown(self: Box<Self>, timeout: Duration) -> TransportShutdown {
drop(self.sender);
let (lock, cvar) = &*self.shutdown;
let mut shutdown = lock.lock();
let result = cvar.wait_for(&mut shutdown, timeout);
if result.timed_out() {
TransportShutdown::TimedOut
} else {
TransportShutdown::Success
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
let mut options = sentry::Options::new();
options.set_dsn("https://1234abcd@your.sentry.service.com/1234");
let client = Client::new();
{
let client = client.clone();
options.set_transport(move |options| Ok(Transport::new(client, options)));
}
let _shutdown = options.init().expect("failed to initialize Sentry");
Event::new().capture();
Event::new().capture();
Event::new().capture();
let _builder = client.post("example.com");
Ok(())
}