Skip to main content

opendal_layer_prometheus/
lib.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, doc(auto_cfg))]
21#![deny(missing_docs)]
22use opendal_core::raw::*;
23use opendal_core::*;
24use opendal_layer_observe_metrics_common as observe;
25use prometheus::HistogramVec;
26use prometheus::Registry;
27use prometheus::core::AtomicI64;
28use prometheus::core::AtomicU64;
29use prometheus::core::GenericCounterVec;
30use prometheus::core::GenericGaugeVec;
31use prometheus::register_histogram_vec_with_registry;
32use prometheus::register_int_counter_vec_with_registry;
33use prometheus::register_int_gauge_vec_with_registry;
34
35/// `PrometheusLayer` records OpenDAL operation and HTTP fetch metrics with
36/// [prometheus](https://docs.rs/prometheus).
37///
38/// # Prometheus Metrics
39///
40/// This layer registers operation metrics and HTTP fetch metrics. Please see the documentation of [`observe`] module for metric details.
41/// For a more detailed explanation of these metrics and how they are used, please refer to the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/).
42///
43/// # Examples
44///
45/// ## Basic Usage
46///
47/// ```no_run
48/// # use log::info;
49/// # use opendal_core::services;
50/// # use opendal_core::Operator;
51/// # use opendal_core::Result;
52/// # use opendal_layer_prometheus::PrometheusLayer;
53/// # use prometheus::Encoder;
54/// #
55/// # #[tokio::main]
56/// # async fn main() -> Result<()> {
57/// let registry = prometheus::default_registry();
58///
59/// let op = Operator::new(services::Memory::default())?
60///     .layer(
61///         PrometheusLayer::builder()
62///             .register(registry)
63///             .expect("register metrics successfully"),
64///     );
65///
66/// // Write data into object test.
67/// op.write("test", "Hello, World!").await?;
68/// // Read data from the object.
69/// let bs = op.read("test").await?;
70/// info!("content: {}", String::from_utf8_lossy(&bs.to_bytes()));
71///
72/// // Get object metadata.
73/// let meta = op.stat("test").await?;
74/// info!("meta: {:?}", meta);
75///
76/// // Export prometheus metrics.
77/// let mut buffer = Vec::<u8>::new();
78/// let encoder = prometheus::TextEncoder::new();
79/// encoder.encode(&prometheus::gather(), &mut buffer).unwrap();
80/// println!("## Prometheus Metrics");
81/// println!("{}", String::from_utf8(buffer.clone()).unwrap());
82/// # Ok(())
83/// # }
84/// ```
85///
86/// ## Global Instance
87///
88/// `PrometheusLayer` needs to be registered before instantiation.
89///
90/// If there are multiple operators in an application that need the `PrometheusLayer`, we could
91/// instantiate it once and pass it to multiple operators. But we cannot directly call
92/// `.layer(PrometheusLayer::builder().register(&registry)?)` for different services, because
93/// registering the same metrics multiple times to the same registry will cause register errors.
94/// Therefore, we can provide a global instance for the `PrometheusLayer`.
95///
96/// ```no_run
97/// # use std::sync::OnceLock;
98/// #
99/// # use log::info;
100/// # use opendal_core::services;
101/// # use opendal_core::Operator;
102/// # use opendal_core::Result;
103/// # use opendal_layer_prometheus::PrometheusLayer;
104/// # use prometheus::Encoder;
105/// #
106/// fn global_prometheus_layer() -> &'static PrometheusLayer {
107///     static GLOBAL: OnceLock<PrometheusLayer> = OnceLock::new();
108///     GLOBAL.get_or_init(|| {
109///         PrometheusLayer::builder()
110///             .register_default()
111///             .expect("Failed to register with the global registry")
112///     })
113/// }
114///
115/// # #[tokio::main]
116/// # async fn main() -> Result<()> {
117/// let op = Operator::new(services::Memory::default())?
118///     .layer(global_prometheus_layer().clone());
119///
120/// // Write data into object test.
121/// op.write("test", "Hello, World!").await?;
122///
123/// // Read data from the object.
124/// let bs = op.read("test").await?;
125/// info!("content: {}", String::from_utf8_lossy(&bs.to_bytes()));
126///
127/// // Get object metadata.
128/// let meta = op.stat("test").await?;
129/// info!("meta: {:?}", meta);
130///
131/// // Export prometheus metrics.
132/// let mut buffer = Vec::<u8>::new();
133/// let encoder = prometheus::TextEncoder::new();
134/// encoder.encode(&prometheus::gather(), &mut buffer).unwrap();
135/// println!("## Prometheus Metrics");
136/// println!("{}", String::from_utf8(buffer.clone()).unwrap());
137/// # Ok(())
138/// # }
139/// ```
140#[derive(Clone, Debug)]
141pub struct PrometheusLayer {
142    interceptor: PrometheusInterceptor,
143}
144
145impl PrometheusLayer {
146    /// Create a [`PrometheusLayerBuilder`] to set the configuration of metrics.
147    ///
148    /// # Example
149    ///
150    /// ```no_run
151    /// # use opendal_core::services;
152    /// # use opendal_core::Operator;
153    /// # use opendal_core::Result;
154    /// # use opendal_layer_prometheus::PrometheusLayer;
155    /// #
156    /// # #[tokio::main]
157    /// # async fn main() -> Result<()> {
158    /// // Pick a builder and configure it.
159    /// let builder = services::Memory::default();
160    /// let registry = prometheus::default_registry();
161    ///
162    /// let duration_seconds_buckets = prometheus::exponential_buckets(0.01, 2.0, 16).unwrap();
163    /// let bytes_buckets = prometheus::exponential_buckets(1.0, 2.0, 16).unwrap();
164    /// let _ = Operator::new(builder)?
165    ///     .layer(
166    ///         PrometheusLayer::builder()
167    ///             .duration_seconds_buckets(duration_seconds_buckets)
168    ///             .bytes_buckets(bytes_buckets)
169    ///             .register(registry)
170    ///             .expect("register metrics successfully"),
171    ///     );
172    /// # Ok(())
173    /// # }
174    /// ```
175    pub fn builder() -> PrometheusLayerBuilder {
176        PrometheusLayerBuilder::default()
177    }
178}
179
180impl Layer for PrometheusLayer {
181    fn apply_service(&self, inner: Servicer) -> Servicer {
182        observe::MetricsLayer::new(self.interceptor.clone()).apply_service(inner)
183    }
184
185    // Reuse the same interceptor because this layer registers both operation and HTTP metrics.
186    fn apply_context(&self, srv: Servicer, inner: OperationContext) -> OperationContext {
187        observe::MetricsLayer::new(self.interceptor.clone()).apply_context(srv, inner)
188    }
189}
190
191/// [`PrometheusLayerBuilder`] is a config builder to build a [`PrometheusLayer`].
192pub struct PrometheusLayerBuilder {
193    bytes_buckets: Vec<f64>,
194    bytes_rate_buckets: Vec<f64>,
195    entries_buckets: Vec<f64>,
196    entries_rate_buckets: Vec<f64>,
197    duration_seconds_buckets: Vec<f64>,
198    ttfb_buckets: Vec<f64>,
199}
200
201impl Default for PrometheusLayerBuilder {
202    fn default() -> Self {
203        Self {
204            bytes_buckets: observe::DEFAULT_BYTES_BUCKETS.to_vec(),
205            bytes_rate_buckets: observe::DEFAULT_BYTES_RATE_BUCKETS.to_vec(),
206            entries_buckets: observe::DEFAULT_ENTRIES_BUCKETS.to_vec(),
207            entries_rate_buckets: observe::DEFAULT_ENTRIES_RATE_BUCKETS.to_vec(),
208            duration_seconds_buckets: observe::DEFAULT_DURATION_SECONDS_BUCKETS.to_vec(),
209            ttfb_buckets: observe::DEFAULT_TTFB_BUCKETS.to_vec(),
210        }
211    }
212}
213
214impl PrometheusLayerBuilder {
215    /// Set buckets for bytes related histogram like `operation_bytes`.
216    pub fn bytes_buckets(mut self, buckets: Vec<f64>) -> Self {
217        if !buckets.is_empty() {
218            self.bytes_buckets = buckets;
219        }
220        self
221    }
222
223    /// Set buckets for bytes rate related histogram like `operation_bytes_rate`.
224    pub fn bytes_rate_buckets(mut self, buckets: Vec<f64>) -> Self {
225        if !buckets.is_empty() {
226            self.bytes_rate_buckets = buckets;
227        }
228        self
229    }
230
231    /// Set buckets for entries related histogram like `operation_entries`.
232    pub fn entries_buckets(mut self, buckets: Vec<f64>) -> Self {
233        if !buckets.is_empty() {
234            self.entries_buckets = buckets;
235        }
236        self
237    }
238
239    /// Set buckets for entries rate related histogram like `operation_entries_rate`.
240    pub fn entries_rate_buckets(mut self, buckets: Vec<f64>) -> Self {
241        if !buckets.is_empty() {
242            self.entries_rate_buckets = buckets;
243        }
244        self
245    }
246
247    /// Set buckets for duration seconds related histogram like `operation_duration_seconds`.
248    pub fn duration_seconds_buckets(mut self, buckets: Vec<f64>) -> Self {
249        if !buckets.is_empty() {
250            self.duration_seconds_buckets = buckets;
251        }
252        self
253    }
254
255    /// Set buckets for ttfb related histogram like `operation_ttfb_seconds`.
256    pub fn ttfb_buckets(mut self, buckets: Vec<f64>) -> Self {
257        if !buckets.is_empty() {
258            self.ttfb_buckets = buckets;
259        }
260        self
261    }
262
263    /// Register the metrics into the given registry and return a [`PrometheusLayer`].
264    ///
265    /// # Example
266    ///
267    /// ```no_run
268    /// # use opendal_core::services;
269    /// # use opendal_core::Operator;
270    /// # use opendal_core::Result;
271    /// # use opendal_layer_prometheus::PrometheusLayer;
272    /// #
273    /// # #[tokio::main]
274    /// # async fn main() -> Result<()> {
275    /// // Pick a builder and configure it.
276    /// let builder = services::Memory::default();
277    /// let _ = Operator::new(builder)?
278    ///     .layer(
279    ///         PrometheusLayer::builder()
280    ///             .register(prometheus::default_registry())
281    ///             .expect("register metrics successfully"),
282    ///     );
283    /// # Ok(())
284    /// # }
285    /// ```
286    pub fn register(self, registry: &Registry) -> Result<PrometheusLayer> {
287        let labels = OperationLabels::names();
288        let operation_bytes = {
289            let metric = observe::MetricValue::OperationBytes(0);
290            register_histogram_vec_with_registry!(
291                metric.name(),
292                metric.help(),
293                labels.as_ref(),
294                self.bytes_buckets.clone(),
295                registry
296            )
297            .map_err(parse_prometheus_error)?
298        };
299        let operation_bytes_rate = {
300            let metric = observe::MetricValue::OperationBytesRate(0.0);
301            register_histogram_vec_with_registry!(
302                metric.name(),
303                metric.help(),
304                labels.as_ref(),
305                self.bytes_rate_buckets.clone(),
306                registry
307            )
308            .map_err(parse_prometheus_error)?
309        };
310        let operation_entries = {
311            let metric = observe::MetricValue::OperationEntries(0);
312            register_histogram_vec_with_registry!(
313                metric.name(),
314                metric.help(),
315                labels.as_ref(),
316                self.entries_buckets,
317                registry
318            )
319            .map_err(parse_prometheus_error)?
320        };
321        let operation_entries_rate = {
322            let metric = observe::MetricValue::OperationEntriesRate(0.0);
323            register_histogram_vec_with_registry!(
324                metric.name(),
325                metric.help(),
326                labels.as_ref(),
327                self.entries_rate_buckets,
328                registry
329            )
330            .map_err(parse_prometheus_error)?
331        };
332        let operation_duration_seconds = {
333            let metric = observe::MetricValue::OperationDurationSeconds(Duration::default());
334            register_histogram_vec_with_registry!(
335                metric.name(),
336                metric.help(),
337                labels.as_ref(),
338                self.duration_seconds_buckets.clone(),
339                registry
340            )
341            .map_err(parse_prometheus_error)?
342        };
343        let operation_executing = {
344            let metric = observe::MetricValue::OperationExecuting(0);
345            register_int_gauge_vec_with_registry!(
346                metric.name(),
347                metric.help(),
348                labels.as_ref(),
349                registry
350            )
351            .map_err(parse_prometheus_error)?
352        };
353        let operation_ttfb_seconds = {
354            let metric = observe::MetricValue::OperationTtfbSeconds(Duration::default());
355            register_histogram_vec_with_registry!(
356                metric.name(),
357                metric.help(),
358                labels.as_ref(),
359                self.ttfb_buckets.clone(),
360                registry
361            )
362            .map_err(parse_prometheus_error)?
363        };
364
365        let labels_with_error = OperationLabels::names().with_error();
366        let operation_errors_total = {
367            let metric = observe::MetricValue::OperationErrorsTotal;
368            register_int_counter_vec_with_registry!(
369                metric.name(),
370                metric.help(),
371                labels_with_error.as_ref(),
372                registry
373            )
374            .map_err(parse_prometheus_error)?
375        };
376
377        let http_labels = OperationLabels::names().with_service_operation();
378        let http_executing = {
379            let metric = observe::MetricValue::HttpExecuting(0);
380            register_int_gauge_vec_with_registry!(
381                metric.name(),
382                metric.help(),
383                http_labels.as_ref(),
384                registry
385            )
386            .map_err(parse_prometheus_error)?
387        };
388        let http_request_bytes = {
389            let metric = observe::MetricValue::HttpRequestBytes(0);
390            register_histogram_vec_with_registry!(
391                metric.name(),
392                metric.help(),
393                http_labels.as_ref(),
394                self.bytes_buckets.clone(),
395                registry
396            )
397            .map_err(parse_prometheus_error)?
398        };
399        let http_request_bytes_rate = {
400            let metric = observe::MetricValue::HttpRequestBytesRate(0.0);
401            register_histogram_vec_with_registry!(
402                metric.name(),
403                metric.help(),
404                http_labels.as_ref(),
405                self.bytes_rate_buckets.clone(),
406                registry
407            )
408            .map_err(parse_prometheus_error)?
409        };
410        let http_request_duration_seconds = {
411            let metric = observe::MetricValue::HttpRequestDurationSeconds(Duration::default());
412            register_histogram_vec_with_registry!(
413                metric.name(),
414                metric.help(),
415                http_labels.as_ref(),
416                self.duration_seconds_buckets.clone(),
417                registry
418            )
419            .map_err(parse_prometheus_error)?
420        };
421        let http_response_bytes = {
422            let metric = observe::MetricValue::HttpResponseBytes(0);
423            register_histogram_vec_with_registry!(
424                metric.name(),
425                metric.help(),
426                http_labels.as_ref(),
427                self.bytes_buckets,
428                registry
429            )
430            .map_err(parse_prometheus_error)?
431        };
432        let http_response_bytes_rate = {
433            let metric = observe::MetricValue::HttpResponseBytesRate(0.0);
434            register_histogram_vec_with_registry!(
435                metric.name(),
436                metric.help(),
437                http_labels.as_ref(),
438                self.bytes_rate_buckets,
439                registry
440            )
441            .map_err(parse_prometheus_error)?
442        };
443        let http_response_duration_seconds = {
444            let metric = observe::MetricValue::HttpResponseDurationSeconds(Duration::default());
445            register_histogram_vec_with_registry!(
446                metric.name(),
447                metric.help(),
448                http_labels.as_ref(),
449                self.duration_seconds_buckets,
450                registry
451            )
452            .map_err(parse_prometheus_error)?
453        };
454        let http_connection_errors_total = {
455            let metric = observe::MetricValue::HttpConnectionErrorsTotal;
456            register_int_counter_vec_with_registry!(
457                metric.name(),
458                metric.help(),
459                http_labels.as_ref(),
460                registry
461            )
462            .map_err(parse_prometheus_error)?
463        };
464
465        let http_labels_with_status_code = OperationLabels::names()
466            .with_service_operation()
467            .with_status_code();
468        let http_status_errors_total = {
469            let metric = observe::MetricValue::HttpStatusErrorsTotal;
470            register_int_counter_vec_with_registry!(
471                metric.name(),
472                metric.help(),
473                http_labels_with_status_code.as_ref(),
474                registry
475            )
476            .map_err(parse_prometheus_error)?
477        };
478
479        Ok(PrometheusLayer {
480            interceptor: PrometheusInterceptor {
481                operation_bytes,
482                operation_bytes_rate,
483                operation_entries,
484                operation_entries_rate,
485                operation_duration_seconds,
486                operation_errors_total,
487                operation_executing,
488                operation_ttfb_seconds,
489
490                http_executing,
491                http_request_bytes,
492                http_request_bytes_rate,
493                http_request_duration_seconds,
494                http_response_bytes,
495                http_response_bytes_rate,
496                http_response_duration_seconds,
497                http_connection_errors_total,
498                http_status_errors_total,
499            },
500        })
501    }
502
503    /// Register the metrics into the default registry and return a [`PrometheusLayer`].
504    ///
505    /// # Example
506    ///
507    /// ```no_run
508    /// # use opendal_core::services;
509    /// # use opendal_core::Operator;
510    /// # use opendal_core::Result;
511    /// # use opendal_layer_prometheus::PrometheusLayer;
512    /// #
513    /// # #[tokio::main]
514    /// # async fn main() -> Result<()> {
515    /// // Pick a builder and configure it.
516    /// let builder = services::Memory::default();
517    /// let _ = Operator::new(builder)?
518    ///     .layer(
519    ///         PrometheusLayer::builder()
520    ///             .register_default()
521    ///             .expect("register metrics successfully"),
522    ///     );
523    /// # Ok(())
524    /// # }
525    /// ```
526    pub fn register_default(self) -> Result<PrometheusLayer> {
527        let registry = prometheus::default_registry();
528        self.register(registry)
529    }
530}
531
532/// Convert the [`prometheus::Error`] to [`Error`].
533fn parse_prometheus_error(err: prometheus::Error) -> Error {
534    Error::new(ErrorKind::Unexpected, err.to_string()).set_source(err)
535}
536
537#[doc(hidden)]
538#[derive(Clone, Debug)]
539pub struct PrometheusInterceptor {
540    operation_bytes: HistogramVec,
541    operation_bytes_rate: HistogramVec,
542    operation_entries: HistogramVec,
543    operation_entries_rate: HistogramVec,
544    operation_duration_seconds: HistogramVec,
545    operation_errors_total: GenericCounterVec<AtomicU64>,
546    operation_executing: GenericGaugeVec<AtomicI64>,
547    operation_ttfb_seconds: HistogramVec,
548
549    http_executing: GenericGaugeVec<AtomicI64>,
550    http_request_bytes: HistogramVec,
551    http_request_bytes_rate: HistogramVec,
552    http_request_duration_seconds: HistogramVec,
553    http_response_bytes: HistogramVec,
554    http_response_bytes_rate: HistogramVec,
555    http_response_duration_seconds: HistogramVec,
556    http_connection_errors_total: GenericCounterVec<AtomicU64>,
557    http_status_errors_total: GenericCounterVec<AtomicU64>,
558}
559
560impl observe::MetricsIntercept for PrometheusInterceptor {
561    fn observe(&self, labels: observe::MetricLabels, value: observe::MetricValue) {
562        let labels = OperationLabels(labels);
563        match value {
564            observe::MetricValue::OperationBytes(v) => self
565                .operation_bytes
566                .with_label_values(&labels.op_values())
567                .observe(v as f64),
568            observe::MetricValue::OperationBytesRate(v) => self
569                .operation_bytes_rate
570                .with_label_values(&labels.op_values())
571                .observe(v),
572            observe::MetricValue::OperationEntries(v) => self
573                .operation_entries
574                .with_label_values(&labels.op_values())
575                .observe(v as f64),
576            observe::MetricValue::OperationEntriesRate(v) => self
577                .operation_entries_rate
578                .with_label_values(&labels.op_values())
579                .observe(v),
580            observe::MetricValue::OperationDurationSeconds(v) => self
581                .operation_duration_seconds
582                .with_label_values(&labels.op_values())
583                .observe(v.as_secs_f64()),
584            observe::MetricValue::OperationErrorsTotal => self
585                .operation_errors_total
586                .with_label_values(&labels.op_values())
587                .inc(),
588            observe::MetricValue::OperationExecuting(v) => self
589                .operation_executing
590                .with_label_values(&labels.op_values())
591                .add(v as i64),
592            observe::MetricValue::OperationTtfbSeconds(v) => self
593                .operation_ttfb_seconds
594                .with_label_values(&labels.op_values())
595                .observe(v.as_secs_f64()),
596
597            observe::MetricValue::HttpExecuting(v) => self
598                .http_executing
599                .with_label_values(&labels.http_values())
600                .add(v as i64),
601            observe::MetricValue::HttpRequestBytes(v) => self
602                .http_request_bytes
603                .with_label_values(&labels.http_values())
604                .observe(v as f64),
605            observe::MetricValue::HttpRequestBytesRate(v) => self
606                .http_request_bytes_rate
607                .with_label_values(&labels.http_values())
608                .observe(v),
609            observe::MetricValue::HttpRequestDurationSeconds(v) => self
610                .http_request_duration_seconds
611                .with_label_values(&labels.http_values())
612                .observe(v.as_secs_f64()),
613            observe::MetricValue::HttpResponseBytes(v) => self
614                .http_response_bytes
615                .with_label_values(&labels.http_values())
616                .observe(v as f64),
617            observe::MetricValue::HttpResponseBytesRate(v) => self
618                .http_response_bytes_rate
619                .with_label_values(&labels.http_values())
620                .observe(v),
621            observe::MetricValue::HttpResponseDurationSeconds(v) => self
622                .http_response_duration_seconds
623                .with_label_values(&labels.http_values())
624                .observe(v.as_secs_f64()),
625            observe::MetricValue::HttpConnectionErrorsTotal => self
626                .http_connection_errors_total
627                .with_label_values(&labels.http_values())
628                .inc(),
629            observe::MetricValue::HttpStatusErrorsTotal => self
630                .http_status_errors_total
631                .with_label_values(&labels.http_values())
632                .inc(),
633            _ => {}
634        }
635    }
636}
637
638struct OperationLabelNames(Vec<&'static str>);
639
640impl AsRef<[&'static str]> for OperationLabelNames {
641    fn as_ref(&self) -> &[&'static str] {
642        &self.0
643    }
644}
645
646impl OperationLabelNames {
647    fn with_error(mut self) -> Self {
648        self.0.push(observe::LABEL_ERROR);
649        self
650    }
651
652    fn with_status_code(mut self) -> Self {
653        self.0.push(observe::LABEL_STATUS_CODE);
654        self
655    }
656
657    fn with_service_operation(mut self) -> Self {
658        self.0.push(observe::LABEL_SERVICE_OPERATION);
659        self
660    }
661}
662
663#[derive(Clone, Debug, PartialEq, Eq, Hash)]
664struct OperationLabels(observe::MetricLabels);
665
666impl OperationLabels {
667    fn names() -> OperationLabelNames {
668        OperationLabelNames(vec![
669            observe::LABEL_SCHEME,
670            observe::LABEL_NAMESPACE,
671            observe::LABEL_ROOT,
672            observe::LABEL_OPERATION,
673        ])
674    }
675
676    fn op_values(&self) -> Vec<&str> {
677        let mut labels = vec![
678            self.0.scheme,
679            self.0.namespace.as_ref(),
680            self.0.root.as_ref(),
681            self.0.operation,
682        ];
683
684        if let Some(error) = self.0.error {
685            labels.push(error.into_static());
686        }
687
688        labels
689    }
690
691    fn http_values(&self) -> Vec<&str> {
692        let mut labels = vec![
693            self.0.scheme,
694            self.0.namespace.as_ref(),
695            self.0.root.as_ref(),
696            self.0.operation,
697            self.0.service_operation.unwrap_or("unknown"),
698        ];
699
700        if let Some(error) = self.0.error {
701            labels.push(error.into_static());
702        }
703
704        if let Some(status_code) = &self.0.status_code {
705            labels.push(status_code.as_str());
706        }
707
708        labels
709    }
710}