use std::fmt;
use std::time::Duration;
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::logs::{LogBatch, LogExporter};
use opentelemetry_sdk::metrics::data::ResourceMetrics;
use opentelemetry_sdk::metrics::exporter::PushMetricExporter;
use opentelemetry_sdk::metrics::Temporality;
use opentelemetry_sdk::trace::{SpanData, SpanExporter};
use opentelemetry_sdk::Resource;
use crate::resilience::{get_exporter_policy, run_with_resilience_inner, ExporterPolicy};
use crate::sampling::Signal;
async fn run_otel_resilience<F, Fut>(
signal: Signal,
policy: &ExporterPolicy,
make_fut: F,
) -> OTelSdkResult
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = OTelSdkResult> + Send,
{
match run_with_resilience_inner(
signal,
policy,
make_fut,
OTelSdkError::Timeout,
|e| matches!(e, OTelSdkError::Timeout(_)),
|| OTelSdkError::InternalFailure("circuit breaker open".into()),
)
.await
{
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
pub struct ResilientSpanExporter<E: SpanExporter> {
inner: E,
}
impl<E: SpanExporter> ResilientSpanExporter<E> {
pub fn new(inner: E) -> Self {
Self { inner }
}
}
impl<E: SpanExporter> fmt::Debug for ResilientSpanExporter<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResilientSpanExporter").finish()
}
}
impl<E: SpanExporter> SpanExporter for ResilientSpanExporter<E> {
async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
let policy = get_exporter_policy(Signal::Traces).unwrap_or_default();
run_otel_resilience(Signal::Traces, &policy, || self.inner.export(batch.clone())).await
}
fn shutdown(&mut self) -> OTelSdkResult {
self.inner.shutdown()
}
fn shutdown_with_timeout(&mut self, timeout: Duration) -> OTelSdkResult {
self.inner.shutdown_with_timeout(timeout)
}
fn force_flush(&mut self) -> OTelSdkResult {
self.inner.force_flush()
}
fn set_resource(&mut self, resource: &Resource) {
self.inner.set_resource(resource)
}
}
pub struct ResilientLogExporter<E: LogExporter> {
inner: E,
}
impl<E: LogExporter> ResilientLogExporter<E> {
pub fn new(inner: E) -> Self {
Self { inner }
}
}
impl<E: LogExporter> fmt::Debug for ResilientLogExporter<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResilientLogExporter").finish()
}
}
impl<E: LogExporter> LogExporter for ResilientLogExporter<E> {
async fn export(&self, batch: LogBatch<'_>) -> OTelSdkResult {
let owned: Vec<(
opentelemetry_sdk::logs::SdkLogRecord,
opentelemetry::InstrumentationScope,
)> = batch.iter().map(|(r, s)| (r.clone(), s.clone())).collect();
let policy = get_exporter_policy(Signal::Logs).unwrap_or_default();
run_otel_resilience(Signal::Logs, &policy, || async {
let refs: Vec<(
&opentelemetry_sdk::logs::SdkLogRecord,
&opentelemetry::InstrumentationScope,
)> = owned.iter().map(|(r, s)| (r, s)).collect();
let rebatch = LogBatch::new(&refs);
self.inner.export(rebatch).await
})
.await
}
fn shutdown(&self) -> OTelSdkResult {
self.inner.shutdown()
}
fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult {
self.inner.shutdown_with_timeout(timeout)
}
fn set_resource(&mut self, resource: &Resource) {
self.inner.set_resource(resource)
}
}
pub struct ResilientMetricExporter<E: PushMetricExporter> {
inner: E,
}
impl<E: PushMetricExporter> ResilientMetricExporter<E> {
pub fn new(inner: E) -> Self {
Self { inner }
}
}
impl<E: PushMetricExporter> fmt::Debug for ResilientMetricExporter<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResilientMetricExporter").finish()
}
}
impl<E: PushMetricExporter> PushMetricExporter for ResilientMetricExporter<E> {
async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult {
let policy = get_exporter_policy(Signal::Metrics).unwrap_or_default();
run_otel_resilience(Signal::Metrics, &policy, || self.inner.export(metrics)).await
}
fn force_flush(&self) -> OTelSdkResult {
self.inner.force_flush()
}
fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult {
self.inner.shutdown_with_timeout(timeout)
}
fn shutdown(&self) -> OTelSdkResult {
self.inner.shutdown()
}
fn temporality(&self) -> Temporality {
self.inner.temporality()
}
}
#[cfg(test)]
#[path = "resilient_span_tests.rs"]
mod span_tests;
#[cfg(test)]
#[path = "resilient_log_metric_tests.rs"]
mod log_metric_tests;
#[cfg(test)]
#[path = "resilient_health_tests.rs"]
mod health_tests;
#[cfg(test)]
#[path = "resilient_forwarding_tests.rs"]
mod forwarding_tests;