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 }