|
1 | 1 | //! Open Metrics text format implementation. |
2 | 2 | //! |
3 | 3 | //! ``` |
4 | | -//! # use prometheus_client::encoding::text::{encode, encode_registry, encode_eof}; |
| 4 | +//! # use prometheus_client::encoding::text::{encode, encode_registry, encode_eof, EncodeError}; |
5 | 5 | //! # use prometheus_client::metrics::counter::Counter; |
6 | 6 | //! # use prometheus_client::registry::Registry; |
7 | 7 | //! # |
@@ -45,9 +45,58 @@ use crate::metrics::MetricType; |
45 | 45 | use crate::registry::{Prefix, Registry, Unit}; |
46 | 46 |
|
47 | 47 | use std::borrow::Cow; |
| 48 | +use std::cell::Cell; |
48 | 49 | use std::collections::HashMap; |
49 | 50 | use std::fmt::Write; |
50 | 51 |
|
| 52 | +thread_local! { |
| 53 | + static UNSUPPORTED_NATIVE_HISTOGRAM: Cell<bool> = Cell::new(false); |
| 54 | +} |
| 55 | + |
| 56 | +/// Error returned by text encoding. |
| 57 | +#[derive(Clone, Copy, Debug)] |
| 58 | +pub enum EncodeError { |
| 59 | + /// Formatting failed while writing text output. |
| 60 | + Fmt(std::fmt::Error), |
| 61 | + /// The OpenMetrics text encoder does not support native-only histograms. |
| 62 | + UnsupportedNativeHistogram, |
| 63 | +} |
| 64 | + |
| 65 | +impl std::fmt::Display for EncodeError { |
| 66 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 67 | + match self { |
| 68 | + EncodeError::Fmt(_) => f.write_str("failed to encode metrics into OpenMetrics text"), |
| 69 | + EncodeError::UnsupportedNativeHistogram => { |
| 70 | + f.write_str("OpenMetrics text encoding does not support native-only histograms") |
| 71 | + } |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +impl std::error::Error for EncodeError { |
| 77 | + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 78 | + match self { |
| 79 | + EncodeError::Fmt(err) => Some(err), |
| 80 | + EncodeError::UnsupportedNativeHistogram => None, |
| 81 | + } |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +impl From<std::fmt::Error> for EncodeError { |
| 86 | + fn from(err: std::fmt::Error) -> Self { |
| 87 | + if UNSUPPORTED_NATIVE_HISTOGRAM.with(|unsupported| unsupported.replace(false)) { |
| 88 | + EncodeError::UnsupportedNativeHistogram |
| 89 | + } else { |
| 90 | + EncodeError::Fmt(err) |
| 91 | + } |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +fn mark_unsupported_native_histogram() -> std::fmt::Error { |
| 96 | + UNSUPPORTED_NATIVE_HISTOGRAM.with(|unsupported| unsupported.set(true)); |
| 97 | + std::fmt::Error |
| 98 | +} |
| 99 | + |
51 | 100 | /// Encode both the metrics registered with the provided [`Registry`] and the |
52 | 101 | /// EOF marker into the provided [`Write`]r using the OpenMetrics text format. |
53 | 102 | /// |
@@ -84,14 +133,14 @@ use std::fmt::Write; |
84 | 133 | /// // Encode the complete OpenMetrics exposition into the buffer |
85 | 134 | /// let mut buffer = String::new(); |
86 | 135 | /// encode(&mut buffer, ®istry)?; |
87 | | -/// # Ok::<(), std::fmt::Error>(()) |
| 136 | +/// # Ok::<(), prometheus_client::encoding::text::EncodeError>(()) |
88 | 137 | /// ``` |
89 | | -pub fn encode<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error> |
| 138 | +pub fn encode<W>(writer: &mut W, registry: &Registry) -> Result<(), EncodeError> |
90 | 139 | where |
91 | 140 | W: Write, |
92 | 141 | { |
93 | 142 | encode_registry(writer, registry)?; |
94 | | - encode_eof(writer) |
| 143 | + encode_eof(writer).map_err(EncodeError::Fmt) |
95 | 144 | } |
96 | 145 |
|
97 | 146 | /// Encode the metrics registered with the provided [`Registry`] into the |
@@ -138,13 +187,16 @@ where |
138 | 187 | /// |
139 | 188 | /// // Encode the gauge registry into the buffer |
140 | 189 | /// encode_registry(&mut buffer, ®_gauge)?; |
141 | | -/// # Ok::<(), std::fmt::Error>(()) |
| 190 | +/// # Ok::<(), prometheus_client::encoding::text::EncodeError>(()) |
142 | 191 | /// ``` |
143 | | -pub fn encode_registry<W>(writer: &mut W, registry: &Registry) -> Result<(), std::fmt::Error> |
| 192 | +pub fn encode_registry<W>(writer: &mut W, registry: &Registry) -> Result<(), EncodeError> |
144 | 193 | where |
145 | 194 | W: Write, |
146 | 195 | { |
147 | | - registry.encode(&mut DescriptorEncoder::new(writer).into()) |
| 196 | + UNSUPPORTED_NATIVE_HISTOGRAM.with(|unsupported| unsupported.set(false)); |
| 197 | + registry |
| 198 | + .encode(&mut DescriptorEncoder::new(writer).into()) |
| 199 | + .map_err(EncodeError::from) |
148 | 200 | } |
149 | 201 |
|
150 | 202 | /// Encode the EOF marker into the provided [`Write`]r using the OpenMetrics |
@@ -175,7 +227,7 @@ where |
175 | 227 | /// |
176 | 228 | /// // Encode EOF marker to complete the message |
177 | 229 | /// encode_eof(&mut buffer)?; |
178 | | -/// # Ok::<(), std::fmt::Error>(()) |
| 230 | +/// # Ok::<(), prometheus_client::encoding::text::EncodeError>(()) |
179 | 231 | /// ``` |
180 | 232 | pub fn encode_eof<W>(writer: &mut W) -> Result<(), std::fmt::Error> |
181 | 233 | where |
@@ -455,7 +507,7 @@ impl MetricEncoder<'_> { |
455 | 507 | _native: NativeHistogram<'_>, |
456 | 508 | ) -> Result<(), std::fmt::Error> { |
457 | 509 | if buckets.is_empty() { |
458 | | - return Err(std::fmt::Error); |
| 510 | + return Err(mark_unsupported_native_histogram()); |
459 | 511 | } |
460 | 512 |
|
461 | 513 | self.encode_histogram(sum, count, buckets, exemplars) |
@@ -990,7 +1042,10 @@ mod tests { |
990 | 1042 | histogram.observe(1.0); |
991 | 1043 |
|
992 | 1044 | let mut encoded = String::new(); |
993 | | - assert!(encode(&mut encoded, ®istry).is_err()); |
| 1045 | + assert!(matches!( |
| 1046 | + encode(&mut encoded, ®istry), |
| 1047 | + Err(EncodeError::UnsupportedNativeHistogram) |
| 1048 | + )); |
994 | 1049 | } |
995 | 1050 |
|
996 | 1051 | #[test] |
|
0 commit comments