diff --git a/src/encoding/text.rs b/src/encoding/text.rs index 754b08a7..221234ec 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -36,6 +36,7 @@ use crate::registry::{Registry, Unit}; use std::borrow::Cow; use std::collections::HashMap; use std::io::Write; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::ops::Deref; /// Encode the metrics registered with the provided [`Registry`] into the @@ -206,6 +207,88 @@ impl Encode for () { } } +/// Warning: Using an IP address as a label is only useful when the number of +/// distinct values is low (i.e. low cardinality). In all other cases you should +/// combine your metrics into a single metric instead. Especially bad examples +/// are: storing separate metrics for each client connecting to your public +/// service or having a large fleet of servers and storing individual binding +/// addresses. +impl Encode for Ipv4Addr { + fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { + writer.write_all(self.to_string().as_bytes())?; + Ok(()) + } +} + +/// Warning: Using an IP address as a label is only useful when the number of +/// distinct values is low (i.e. low cardinality). In all other cases you should +/// combine your metrics into a single metric instead. Especially bad examples +/// are: storing separate metrics for each client connecting to your public +/// service or having a large fleet of servers and storing individual binding +/// addresses. +impl Encode for Ipv6Addr { + fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { + writer.write_all(self.to_string().as_bytes())?; + Ok(()) + } +} + +/// Warning: Using an IP address as a label is only useful when the number of +/// distinct values is low (i.e. low cardinality). In all other cases you should +/// combine your metrics into a single metric instead. Especially bad examples +/// are: storing separate metrics for each client connecting to your public +/// service or having a large fleet of servers and storing individual binding +/// addresses. +impl Encode for IpAddr { + fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { + match self { + IpAddr::V4(v4) => v4.encode(writer), + IpAddr::V6(v6) => v6.encode(writer), + } + } +} + +/// Warning: Using a socket address as a label is only useful when the number of +/// distinct values is low (i.e. low cardinality). In all other cases you should +/// combine your metrics into a single metric instead. Especially bad examples +/// are: storing separate metrics for each client connecting to your public +/// service or having a large fleet of servers and storing individual binding +/// addresses. +impl Encode for SocketAddrV4 { + fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { + writer.write_all(self.to_string().as_bytes())?; + Ok(()) + } +} + +/// Warning: Using a socket address as a label is only useful when the number of +/// distinct values is low (i.e. low cardinality). In all other cases you should +/// combine your metrics into a single metric instead. Especially bad examples +/// are: storing separate metrics for each client connecting to your public +/// service or having a large fleet of servers and storing individual binding +/// addresses. +impl Encode for SocketAddrV6 { + fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { + writer.write_all(self.to_string().as_bytes())?; + Ok(()) + } +} + +/// Warning: Using a socket address as a label is only useful when the number of +/// distinct values is low (i.e. low cardinality). In all other cases you should +/// combine your metrics into a single metric instead. Especially bad examples +/// are: storing separate metrics for each client connecting to your public +/// service or having a large fleet of servers and storing individual binding +/// addresses. +impl Encode for SocketAddr { + fn encode(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> { + match self { + SocketAddr::V4(v4) => v4.encode(writer), + SocketAddr::V6(v6) => v6.encode(writer), + } + } +} + /// Helper type for [`EncodeMetric`], see [`EncodeMetric::encode`]. /// // `Encoder` does not take a trait parameter for `writer` and `labels` because @@ -705,6 +788,48 @@ mod tests { parse_with_python_client(String::from_utf8(encoded).unwrap()); } + #[test] + fn encode_socketaddr() { + let mut registry = Registry::default(); + let family = Family::, Counter>::default(); + registry.register("my_addr", "My socket address", family.clone()); + + let addr: SocketAddr = "127.0.0.1:80".parse().unwrap(); + family.get_or_create(&vec![("address", addr)]).inc(); + + let mut encoded = Vec::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP my_addr My socket address.\n".to_owned() + + "# TYPE my_addr counter\n" + + "my_addr_total{address=\"127.0.0.1:80\"} 1\n" + + "# EOF\n"; + assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + + parse_with_python_client(String::from_utf8(encoded).unwrap()); + } + + #[test] + fn encode_ipaddr() { + let mut registry = Registry::default(); + let family = Family::, Counter>::default(); + registry.register("my_addr", "My IP address", family.clone()); + + let addr: IpAddr = "::1".parse().unwrap(); + family.get_or_create(&vec![("address", addr)]).inc(); + + let mut encoded = Vec::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP my_addr My IP address.\n".to_owned() + + "# TYPE my_addr counter\n" + + "my_addr_total{address=\"::1\"} 1\n" + + "# EOF\n"; + assert_eq!(expected, String::from_utf8(encoded.clone()).unwrap()); + + parse_with_python_client(String::from_utf8(encoded).unwrap()); + } + #[test] fn encode_counter_family_with_prefix_with_label() { let mut registry = Registry::default();