#[cfg(debug_assertions)]
use crate::__private::{AutometricsTracker, TrackMetrics, FUNCTION_DESCRIPTIONS};
use crate::settings::{get_settings, AutometricsSettings};
use http::{header::CONTENT_TYPE, Response};
#[cfg(metrics)]
use metrics_exporter_prometheus::{BuildError, PrometheusBuilder, PrometheusHandle};
use once_cell::sync::OnceCell;
#[cfg(any(opentelemetry, prometheus))]
use prometheus::TextEncoder;
use thiserror::Error;
#[cfg(not(exemplars))]
const RESPONSE_CONTENT_TYPE: &str = "text/plain; version=0.0.4";
#[cfg(exemplars)]
const RESPONSE_CONTENT_TYPE: &str = "application/openmetrics-text; version=1.0.0; charset=utf-8";
static GLOBAL_EXPORTER: OnceCell<GlobalPrometheus> = OnceCell::new();
pub type PrometheusResponse = Response<String>;
#[derive(Debug, Error)]
pub enum EncodingError {
#[cfg(any(prometheus, opentelemetry))]
#[error(transparent)]
Prometheus(#[from] prometheus::Error),
#[cfg(prometheus_client)]
#[error(transparent)]
Format(#[from] std::fmt::Error),
#[error(transparent)]
Initialization(#[from] ExporterInitializationError),
}
#[derive(Debug, Error)]
pub enum ExporterInitializationError {
#[error("Prometheus exporter has already been initialized")]
AlreadyInitialized,
#[cfg(metrics)]
#[error(transparent)]
MetricsExporter(#[from] BuildError),
}
pub fn try_init() -> Result<(), ExporterInitializationError> {
let mut newly_initialized = false;
GLOBAL_EXPORTER.get_or_try_init(|| {
newly_initialized = true;
initialize_prometheus_exporter()
})?;
if !newly_initialized {
return Err(ExporterInitializationError::AlreadyInitialized);
}
#[cfg(debug_assertions)]
AutometricsTracker::intitialize_metrics(&FUNCTION_DESCRIPTIONS);
Ok(())
}
pub fn init() {
try_init().unwrap();
}
pub fn encode_to_string() -> Result<String, EncodingError> {
GLOBAL_EXPORTER
.get_or_try_init(initialize_prometheus_exporter)?
.encode_metrics()
}
pub fn encode_http_response() -> PrometheusResponse {
match encode_to_string() {
Ok(metrics) => http::Response::builder()
.status(200)
.header(CONTENT_TYPE, RESPONSE_CONTENT_TYPE)
.body(metrics)
.expect("Error building response"),
Err(err) => http::Response::builder()
.status(500)
.body(format!("{:?}", err))
.expect("Error building response"),
}
}
#[derive(Clone)]
#[doc(hidden)]
struct GlobalPrometheus {
#[allow(dead_code)]
settings: &'static AutometricsSettings,
#[cfg(metrics)]
metrics_exporter: PrometheusHandle,
}
impl GlobalPrometheus {
fn encode_metrics(&self) -> Result<String, EncodingError> {
let mut output = String::new();
#[cfg(metrics)]
output.push_str(&self.metrics_exporter.render());
#[cfg(any(prometheus, opentelemetry))]
TextEncoder::new().encode_utf8(&self.settings.prometheus_registry.gather(), &mut output)?;
#[cfg(prometheus_client)]
prometheus_client::encoding::text::encode(
&mut output,
&self.settings.prometheus_client_registry,
)?;
Ok(output)
}
}
fn initialize_prometheus_exporter() -> Result<GlobalPrometheus, ExporterInitializationError> {
let settings = get_settings();
Ok(GlobalPrometheus {
#[cfg(metrics)]
metrics_exporter: PrometheusBuilder::new()
.set_buckets(&settings.histogram_buckets)?
.install_recorder()?,
settings,
})
}