use std::collections::HashMap;
use async_trait::async_trait;
use bytes::Bytes;
use serde::Serialize;
use serde_json::Value;
use tokio::sync::RwLock;
use crate::{
networking::{NetworkClient, RequestArgs},
statsig_metadata::StatsigMetadata,
OpsStatsEventObserver,
};
use super::ops_stats::OpsStatsEvent;
static SDK_EXCEPTION_ENDPOINT: &str = "https://statsigapi.net/v1/sdk_exception";
#[derive(Clone, Serialize)]
pub struct ErrorBoundaryEvent {
pub tag: String,
pub exception: String,
}
pub struct SDKErrorsObserver {
errors_aggregator: RwLock<HashMap<String, u32>>,
network_client: NetworkClient,
statsig_options_logging_copy: String,
}
impl SDKErrorsObserver {
pub fn new(sdk_key: &str, options_logging_copy: String) -> Self {
SDKErrorsObserver {
network_client: NetworkClient::new(
sdk_key,
Some(StatsigMetadata::get_constant_request_headers(sdk_key)),
),
errors_aggregator: RwLock::new(HashMap::new()),
statsig_options_logging_copy: options_logging_copy,
}
}
async fn handle_eb_event(&self, eb_event: ErrorBoundaryEvent) {
let key = format!("{}:{}", eb_event.tag, eb_event.exception);
let mut write_guard = self.errors_aggregator.write().await;
let count = write_guard.entry(key).or_default();
*count += 1;
if *count > 1 {
return;
}
self.log_exception(eb_event).await;
}
async fn log_exception(&self, e: ErrorBoundaryEvent) {
let mut body_obj = serde_json::to_value(e).unwrap_or_default();
if let Value::Object(ref mut map) = body_obj {
map.insert(
"statsigOptions".to_string(),
Value::String(self.statsig_options_logging_copy.clone()),
);
}
let body = serde_json::to_string_pretty(&body_obj).unwrap_or_default();
let request_args = RequestArgs {
url: SDK_EXCEPTION_ENDPOINT.to_owned(),
retries: 0,
..RequestArgs::new()
};
let _ = self
.network_client
.post(request_args, Some(Bytes::from(body)))
.await;
}
}
#[async_trait]
impl OpsStatsEventObserver for SDKErrorsObserver {
async fn handle_event(&self, event: OpsStatsEvent) {
if let OpsStatsEvent::SDKErrorEvent(e) = event {
self.handle_eb_event(e).await
}
}
}