/ src / metrics.rs
metrics.rs
  1  mod service;
  2  
  3  use std::{sync::Arc, time::UNIX_EPOCH};
  4  
  5  use http::StatusCode;
  6  use prometheus_client::{
  7      encoding::{EncodeLabelKey, EncodeLabelSet, EncodeLabelValue, LabelSetEncoder},
  8      metrics::{counter::Counter, family::Family, gauge::ConstGauge},
  9      registry::Registry,
 10  };
 11  use reqwest::Url;
 12  use strum::EnumIter;
 13  
 14  pub use service::MetricsService;
 15  
 16  #[derive(Clone, Debug, EnumIter, Eq, thiserror::Error, Hash, strum::IntoStaticStr, PartialEq)]
 17  pub enum PushError {
 18      #[error("Error encoding metrics: {source}")]
 19      Encode { source: std::fmt::Error },
 20      #[error("Error pushing metrics: {error}")]
 21      Push { error: String },
 22      #[error("Pushgateway response received {status:?} from {url:?}")]
 23      PushResponse {
 24          status: Option<StatusCode>,
 25          url: Option<Url>,
 26      },
 27  }
 28  
 29  impl EncodeLabelValue for PushError {
 30      fn encode(
 31          &self,
 32          encoder: &mut prometheus_client::encoding::LabelValueEncoder,
 33      ) -> Result<(), std::fmt::Error> {
 34          let value: &'static str = self.into();
 35  
 36          EncodeLabelValue::encode(&value, encoder)
 37      }
 38  }
 39  
 40  impl EncodeLabelSet for PushError {
 41      fn encode(&self, mut encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> {
 42          let mut label_encoder = encoder.encode_label();
 43          let mut label_key_encoder = label_encoder.encode_label_key()?;
 44          EncodeLabelKey::encode(&"error", &mut label_key_encoder)?;
 45  
 46          let mut label_value_encoder = label_key_encoder.encode_label_value()?;
 47          EncodeLabelValue::encode(self, &mut label_value_encoder)?;
 48  
 49          label_value_encoder.finish()?;
 50  
 51          Ok(())
 52      }
 53  }
 54  
 55  impl From<reqwest::Error> for PushError {
 56      fn from(source: reqwest::Error) -> Self {
 57          let url = source.url().cloned();
 58          let status = source.status();
 59  
 60          Self::PushResponse { status, url }
 61      }
 62  }
 63  
 64  impl From<reqwest_middleware::Error> for PushError {
 65      fn from(source: reqwest_middleware::Error) -> Self {
 66          match source {
 67              reqwest_middleware::Error::Middleware(source) => Self::Push {
 68                  error: source.to_string(),
 69              },
 70              reqwest_middleware::Error::Reqwest(source) => source.into(),
 71          }
 72      }
 73  }
 74  
 75  #[derive(Clone, Debug, EncodeLabelSet, Eq, Hash, PartialEq)]
 76  pub struct RequestLabels {
 77      route: String,
 78      status_code: u16,
 79  }
 80  
 81  impl RequestLabels {
 82      pub(crate) fn new(route: String, status_code: http::StatusCode) -> Self {
 83          Self {
 84              route,
 85              status_code: status_code.as_u16(),
 86          }
 87      }
 88  }
 89  
 90  pub type RequestsCounter = Family<RequestLabels, Counter>;
 91  
 92  pub struct Metrics {
 93      pub registry: Arc<Registry>,
 94      pub metrics_push: Counter,
 95      pub metrics_push_errors: Family<PushError, Counter>,
 96      pub requests: RequestsCounter,
 97  }
 98  
 99  impl Metrics {
100      pub fn new() -> Self {
101          let mut registry = Registry::default();
102  
103          let start_time = UNIX_EPOCH
104              .elapsed()
105              .expect("cannot determine time")
106              .as_secs_f64();
107          let start_time = ConstGauge::new(start_time);
108          registry.register(
109              "process_start_time_seconds",
110              "Process start time",
111              start_time,
112          );
113  
114          let app_registry = registry.sub_registry_with_prefix("ambient_weather_local");
115  
116          let metrics_push = Counter::default();
117          metrics_push.inc_by(0u64);
118          app_registry.register("metrics_push", "Metrics push count", metrics_push.clone());
119  
120          let metrics_push_errors =
121              Family::<PushError, Counter>::new_with_constructor(Counter::default);
122          app_registry.register(
123              "metrics_push_errors",
124              "Metrics push errors",
125              metrics_push_errors.clone(),
126          );
127  
128          let requests = Family::<RequestLabels, Counter>::new_with_constructor(Counter::default);
129          app_registry.register("http_requests", "HTTP requests received", requests.clone());
130  
131          Self {
132              registry: registry.into(),
133  
134              metrics_push,
135              metrics_push_errors,
136              requests,
137          }
138      }
139  
140      pub fn service(&self) -> MetricsService {
141          MetricsService::new(self.registry.clone())
142      }
143  }
144  
145  impl Default for Metrics {
146      fn default() -> Self {
147          Self::new()
148      }
149  }