use std::time::Duration; use opentelemetry::{ KeyValue, metrics::{Counter, Histogram, Meter, UpDownCounter}, }; #[derive(Clone)] pub(crate) struct Metrics { requests: Counter, request_duration: Histogram, active_requests: UpDownCounter, upstream_duration: Histogram, errors: Counter, active_tunnels: UpDownCounter, tunnel_duration: Histogram, tunnel_bytes: Counter, acl_decisions: Counter, } impl Metrics { pub fn new(meter: Meter) -> Self { Self { requests: meter .u64_counter("hermes.proxy.requests") .with_description("Number of HTTP requests and CONNECT handshakes handled") .with_unit("{request}") .build(), request_duration: meter .f64_histogram("http.server.request.duration") .with_description("Duration of downstream HTTP request handling") .with_unit("s") .build(), active_requests: meter .i64_up_down_counter("http.server.active_requests") .with_description("Number of HTTP requests currently being handled") .with_unit("{request}") .build(), upstream_duration: meter .f64_histogram("http.client.request.duration") .with_description("Duration of upstream HTTP requests through response headers") .with_unit("s") .build(), errors: meter .u64_counter("hermes.proxy.errors") .with_description("Number of proxy failures") .with_unit("{error}") .build(), active_tunnels: meter .i64_up_down_counter("hermes.proxy.tunnels.active") .with_description("Number of active CONNECT tunnels") .with_unit("{tunnel}") .build(), tunnel_duration: meter .f64_histogram("hermes.proxy.tunnel.duration") .with_description("Duration of completed CONNECT tunnels") .with_unit("s") .build(), tunnel_bytes: meter .u64_counter("hermes.proxy.tunnel.bytes") .with_description("Bytes relayed through completed CONNECT tunnels") .with_unit("By") .build(), acl_decisions: meter .u64_counter("hermes.proxy.acl.decisions") .with_description("Number of ACL decisions by outcome and reason") .with_unit("{decision}") .build(), } } pub fn request_started(&self) -> ActiveRequest { self.active_requests.add(1, &[]); ActiveRequest { metrics: self.clone(), } } pub fn request_finished( &self, method: &str, status: u16, error_type: Option<&str>, duration: Duration, ) { let mut attributes = vec![ KeyValue::new("http.request.method", method.to_owned()), KeyValue::new("http.response.status_code", i64::from(status)), ]; if let Some(error_type) = error_type { attributes.push(KeyValue::new("error.type", error_type.to_owned())); } self.requests.add(1, &attributes); self.request_duration .record(duration.as_secs_f64(), &attributes); } pub fn upstream_finished( &self, method: &str, status: Option, error_type: Option<&str>, duration: Duration, ) { let mut attributes = vec![KeyValue::new("http.request.method", method.to_owned())]; if let Some(status) = status { attributes.push(KeyValue::new( "http.response.status_code", i64::from(status), )); } if let Some(error_type) = error_type { attributes.push(KeyValue::new("error.type", error_type.to_owned())); } self.upstream_duration .record(duration.as_secs_f64(), &attributes); } pub fn error(&self, phase: &str, error_type: &str) { self.errors.add( 1, &[ KeyValue::new("proxy.phase", phase.to_owned()), KeyValue::new("error.type", error_type.to_owned()), ], ); } pub fn acl_decision(&self, protocol: &str, decision: &str, reason: &str) { self.acl_decisions.add( 1, &[ KeyValue::new("proxy.protocol", protocol.to_owned()), KeyValue::new("acl.decision", decision.to_owned()), KeyValue::new("acl.reason", reason.to_owned()), ], ); } pub fn tunnel_started(&self, protocol: &'static str) -> ActiveTunnel { self.active_tunnels .add(1, &[KeyValue::new("proxy.protocol", protocol)]); ActiveTunnel { metrics: self.clone(), protocol, } } pub fn tunnel_finished( &self, protocol: &str, duration: Duration, client_to_upstream: u64, upstream_to_client: u64, error_type: Option<&str>, ) { let mut attributes = vec![KeyValue::new("proxy.protocol", protocol.to_owned())]; if let Some(error_type) = error_type { attributes.push(KeyValue::new("error.type", error_type.to_owned())); } self.tunnel_duration .record(duration.as_secs_f64(), &attributes); self.tunnel_bytes.add( client_to_upstream, &[ KeyValue::new("proxy.protocol", protocol.to_owned()), KeyValue::new("direction", "client_to_upstream"), ], ); self.tunnel_bytes.add( upstream_to_client, &[ KeyValue::new("proxy.protocol", protocol.to_owned()), KeyValue::new("direction", "upstream_to_client"), ], ); } } pub(crate) struct ActiveRequest { metrics: Metrics, } impl Drop for ActiveRequest { fn drop(&mut self) { self.metrics.active_requests.add(-1, &[]); } } pub(crate) struct ActiveTunnel { metrics: Metrics, protocol: &'static str, } impl Drop for ActiveTunnel { fn drop(&mut self) { self.metrics .active_tunnels .add(-1, &[KeyValue::new("proxy.protocol", self.protocol)]); } }