#[cfg(feature = "metrics")]
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(feature = "metrics")]
use std::time::Duration;
#[cfg(feature = "logging")]
use tracing_subscriber::EnvFilter;
pub const SPAN_PROVIDE: &str = "fluxdi.provide";
pub const SPAN_RESOLVE: &str = "fluxdi.resolve";
pub const SPAN_FACTORY_EXECUTE: &str = "fluxdi.factory.execute";
pub const EVENT_CIRCULAR_DEPENDENCY: &str = "fluxdi.circular_dependency";
#[cfg(feature = "metrics")]
pub const METRIC_PROVIDE_ATTEMPTS_TOTAL: &str = "fluxdi_provide_attempts_total";
#[cfg(feature = "metrics")]
pub const METRIC_PROVIDE_SUCCESS_TOTAL: &str = "fluxdi_provide_success_total";
#[cfg(feature = "metrics")]
pub const METRIC_PROVIDE_FAILURES_TOTAL: &str = "fluxdi_provide_failures_total";
#[cfg(feature = "metrics")]
pub const METRIC_RESOLVE_ATTEMPTS_TOTAL: &str = "fluxdi_resolve_attempts_total";
#[cfg(feature = "metrics")]
pub const METRIC_RESOLVE_SUCCESS_TOTAL: &str = "fluxdi_resolve_success_total";
#[cfg(feature = "metrics")]
pub const METRIC_RESOLVE_FAILURES_TOTAL: &str = "fluxdi_resolve_failures_total";
#[cfg(feature = "metrics")]
pub const METRIC_RESOLVE_CACHE_HITS_TOTAL: &str = "fluxdi_resolve_cache_hits_total";
#[cfg(feature = "metrics")]
pub const METRIC_RESOLVE_CACHE_MISSES_TOTAL: &str = "fluxdi_resolve_cache_misses_total";
#[cfg(feature = "metrics")]
pub const METRIC_FACTORY_EXECUTIONS_TOTAL: &str = "fluxdi_factory_executions_total";
#[cfg(feature = "metrics")]
pub const METRIC_RESOLVE_DURATION_SECONDS_TOTAL: &str = "fluxdi_resolve_duration_seconds_total";
#[cfg(feature = "metrics")]
pub const METRIC_RESOLVE_DURATION_SAMPLES_TOTAL: &str = "fluxdi_resolve_duration_samples_total";
#[cfg(feature = "metrics")]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct MetricsSnapshot {
pub provide_attempts_total: u64,
pub provide_success_total: u64,
pub provide_failures_total: u64,
pub resolve_attempts_total: u64,
pub resolve_success_total: u64,
pub resolve_failures_total: u64,
pub resolve_cache_hits_total: u64,
pub resolve_cache_misses_total: u64,
pub factory_executions_total: u64,
pub resolve_duration_ns_total: u64,
pub resolve_duration_samples_total: u64,
}
#[cfg(feature = "metrics")]
impl MetricsSnapshot {
#[cfg(feature = "prometheus")]
pub fn to_prometheus(self) -> String {
let mut output = String::new();
append_counter(
&mut output,
METRIC_PROVIDE_ATTEMPTS_TOTAL,
"Total number of provider registration attempts.",
self.provide_attempts_total,
);
append_counter(
&mut output,
METRIC_PROVIDE_SUCCESS_TOTAL,
"Total number of successful provider registrations.",
self.provide_success_total,
);
append_counter(
&mut output,
METRIC_PROVIDE_FAILURES_TOTAL,
"Total number of failed provider registrations.",
self.provide_failures_total,
);
append_counter(
&mut output,
METRIC_RESOLVE_ATTEMPTS_TOTAL,
"Total number of resolve attempts.",
self.resolve_attempts_total,
);
append_counter(
&mut output,
METRIC_RESOLVE_SUCCESS_TOTAL,
"Total number of successful resolves.",
self.resolve_success_total,
);
append_counter(
&mut output,
METRIC_RESOLVE_FAILURES_TOTAL,
"Total number of failed resolves.",
self.resolve_failures_total,
);
append_counter(
&mut output,
METRIC_RESOLVE_CACHE_HITS_TOTAL,
"Total number of resolve cache hits.",
self.resolve_cache_hits_total,
);
append_counter(
&mut output,
METRIC_RESOLVE_CACHE_MISSES_TOTAL,
"Total number of resolve cache misses.",
self.resolve_cache_misses_total,
);
append_counter(
&mut output,
METRIC_FACTORY_EXECUTIONS_TOTAL,
"Total number of provider factory executions.",
self.factory_executions_total,
);
let resolve_seconds_total = self.resolve_duration_ns_total as f64 / 1_000_000_000.0;
append_counter_float(
&mut output,
METRIC_RESOLVE_DURATION_SECONDS_TOTAL,
"Total time spent resolving services in seconds.",
resolve_seconds_total,
);
append_counter(
&mut output,
METRIC_RESOLVE_DURATION_SAMPLES_TOTAL,
"Total number of resolve duration samples recorded.",
self.resolve_duration_samples_total,
);
output
}
}
#[cfg(all(feature = "metrics", feature = "prometheus"))]
fn append_counter(output: &mut String, metric_name: &str, help: &str, value: u64) {
output.push_str("# HELP ");
output.push_str(metric_name);
output.push(' ');
output.push_str(help);
output.push('\n');
output.push_str("# TYPE ");
output.push_str(metric_name);
output.push_str(" counter\n");
output.push_str(metric_name);
output.push(' ');
output.push_str(&value.to_string());
output.push('\n');
}
#[cfg(all(feature = "metrics", feature = "prometheus"))]
fn append_counter_float(output: &mut String, metric_name: &str, help: &str, value: f64) {
output.push_str("# HELP ");
output.push_str(metric_name);
output.push(' ');
output.push_str(help);
output.push('\n');
output.push_str("# TYPE ");
output.push_str(metric_name);
output.push_str(" counter\n");
output.push_str(metric_name);
output.push(' ');
output.push_str(&value.to_string());
output.push('\n');
}
#[cfg(feature = "metrics")]
#[derive(Debug, Default)]
pub(crate) struct MetricsState {
provide_attempts_total: AtomicU64,
provide_success_total: AtomicU64,
provide_failures_total: AtomicU64,
resolve_attempts_total: AtomicU64,
resolve_success_total: AtomicU64,
resolve_failures_total: AtomicU64,
resolve_cache_hits_total: AtomicU64,
resolve_cache_misses_total: AtomicU64,
factory_executions_total: AtomicU64,
resolve_duration_ns_total: AtomicU64,
resolve_duration_samples_total: AtomicU64,
}
#[cfg(feature = "metrics")]
impl MetricsState {
pub(crate) fn record_provide_attempt(&self) {
self.provide_attempts_total.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_provide_success(&self) {
self.provide_success_total.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_provide_failure(&self) {
self.provide_failures_total.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_resolve_attempt(&self) {
self.resolve_attempts_total.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_resolve_success(&self, elapsed: Duration) {
self.resolve_success_total.fetch_add(1, Ordering::Relaxed);
self.resolve_duration_ns_total
.fetch_add(duration_to_nanos_u64(elapsed), Ordering::Relaxed);
self.resolve_duration_samples_total
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_resolve_failure(&self, elapsed: Duration) {
self.resolve_failures_total.fetch_add(1, Ordering::Relaxed);
self.resolve_duration_ns_total
.fetch_add(duration_to_nanos_u64(elapsed), Ordering::Relaxed);
self.resolve_duration_samples_total
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_resolve_cache_hit(&self) {
self.resolve_cache_hits_total
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_resolve_cache_miss(&self) {
self.resolve_cache_misses_total
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_factory_execution(&self) {
self.factory_executions_total
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn snapshot(&self) -> MetricsSnapshot {
MetricsSnapshot {
provide_attempts_total: self.provide_attempts_total.load(Ordering::Relaxed),
provide_success_total: self.provide_success_total.load(Ordering::Relaxed),
provide_failures_total: self.provide_failures_total.load(Ordering::Relaxed),
resolve_attempts_total: self.resolve_attempts_total.load(Ordering::Relaxed),
resolve_success_total: self.resolve_success_total.load(Ordering::Relaxed),
resolve_failures_total: self.resolve_failures_total.load(Ordering::Relaxed),
resolve_cache_hits_total: self.resolve_cache_hits_total.load(Ordering::Relaxed),
resolve_cache_misses_total: self.resolve_cache_misses_total.load(Ordering::Relaxed),
factory_executions_total: self.factory_executions_total.load(Ordering::Relaxed),
resolve_duration_ns_total: self.resolve_duration_ns_total.load(Ordering::Relaxed),
resolve_duration_samples_total: self
.resolve_duration_samples_total
.load(Ordering::Relaxed),
}
}
#[cfg(feature = "prometheus")]
pub(crate) fn to_prometheus(&self) -> String {
self.snapshot().to_prometheus()
}
}
#[cfg(feature = "metrics")]
fn duration_to_nanos_u64(duration: Duration) -> u64 {
duration.as_nanos().min(u64::MAX as u128) as u64
}
#[cfg(feature = "logging")]
fn default_env_filter() -> EnvFilter {
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))
}
#[cfg(feature = "logging")]
pub fn try_init_logging() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
tracing_subscriber::fmt()
.with_env_filter(default_env_filter())
.try_init()
}
#[cfg(feature = "logging")]
pub fn init_logging() {
let _ = try_init_logging();
}
#[cfg(feature = "opentelemetry")]
mod opentelemetry;
#[cfg(feature = "opentelemetry")]
pub use opentelemetry::{opentelemetry_layer, try_init_opentelemetry};