use reqwest::Client;
use crate::notifier::{ErrorEvent, ErrorNotifier, NotifyFuture};
#[derive(Debug, Clone)]
pub struct CmdlineConfig {
pub base_url: String,
pub api_key: String,
pub environment: Option<String>,
pub service: Option<String>,
pub release: Option<String>,
}
impl CmdlineConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
base_url: "https://cmdline.io".into(),
api_key: api_key.into(),
environment: None,
service: None,
release: None,
}
}
pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn with_environment(mut self, env: impl Into<String>) -> Self {
self.environment = Some(env.into());
self
}
pub fn with_service(mut self, service: impl Into<String>) -> Self {
self.service = Some(service.into());
self
}
pub fn with_release(mut self, release: impl Into<String>) -> Self {
self.release = Some(release.into());
self
}
}
pub struct CmdlineProvider {
config: CmdlineConfig,
}
impl CmdlineProvider {
pub fn new(config: CmdlineConfig) -> Self {
Self { config }
}
}
impl ErrorNotifier for CmdlineProvider {
fn notify<'a>(&'a self, client: &'a Client, event: &'a ErrorEvent) -> NotifyFuture<'a> {
Box::pin(async move {
let url = format!(
"{}/api/ingest/errors",
self.config.base_url.trim_end_matches('/')
);
let title = format!("{:?}: {}", event.error_code, event.message);
let title = if title.len() > 500 {
format!("{}...", &title[..497])
} else {
title
};
let severity = match event.error_code {
crate::errors::ErrorCode::Database => "fatal",
crate::errors::ErrorCode::Exception => "error",
crate::errors::ErrorCode::Authentication
| crate::errors::ErrorCode::Authorization => "warning",
crate::errors::ErrorCode::BadRequest
| crate::errors::ErrorCode::NotFound
| crate::errors::ErrorCode::Validation => "warning",
};
let error_type = match event.error_code {
crate::errors::ErrorCode::BadRequest
| crate::errors::ErrorCode::NotFound
| crate::errors::ErrorCode::Authentication
| crate::errors::ErrorCode::Authorization => "http_error",
_ => "exception",
};
let mut payload = serde_json::json!({
"title": title,
"severity": severity,
"error_type": error_type,
"service": event.app_name,
});
if let Some(ref source) = event.source_error {
payload["message"] = serde_json::Value::String(source.clone());
}
payload["extra"] = serde_json::json!({
"location": event.location,
});
if let Some(ref env) = self.config.environment {
payload["environment"] = serde_json::Value::String(env.clone());
}
if let Some(ref service) = self.config.service {
payload["service"] = serde_json::Value::String(service.clone());
}
if let Some(ref release) = self.config.release {
payload["release"] = serde_json::Value::String(release.clone());
}
if let Some(ref stacktrace) = event.stacktrace {
payload["stacktrace"] = serde_json::Value::String(stacktrace.clone());
}
if let Some(ref request_url) = event.request_url {
payload["request_url"] = serde_json::Value::String(request_url.clone());
}
if let Some(ref request_method) = event.request_method {
payload["request_method"] = serde_json::Value::String(request_method.clone());
}
if let Some(ref user_id) = event.user_id {
payload["user_id"] = serde_json::Value::String(user_id.clone());
}
if let Some(ref user_email) = event.user_email {
payload["user_email"] = serde_json::Value::String(user_email.clone());
}
if let Some(ref tags) = event.tags {
payload["tags"] = tags.clone();
}
if let Some(ref extra) = event.extra {
if let Some(existing) = payload.get_mut("extra") {
if let (Some(existing_obj), Some(new_obj)) =
(existing.as_object_mut(), extra.as_object())
{
for (k, v) in new_obj {
existing_obj.insert(k.clone(), v.clone());
}
}
}
}
if let Some(ref breadcrumbs) = event.breadcrumbs {
payload["breadcrumbs"] = breadcrumbs.clone();
}
if let Some(ref fingerprint) = event.fingerprint {
payload["fingerprint"] = serde_json::Value::String(fingerprint.clone());
}
client
.post(&url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.json(&payload)
.send()
.await?
.error_for_status()?;
Ok(())
})
}
fn name(&self) -> &'static str {
"cmdline"
}
}