1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! An HTTP endpoint for metrics collection.

use std::net::SocketAddr;

use abscissa_core::{Component, FrameworkError};
use serde::{Deserialize, Serialize};

/// Abscissa component which runs a metrics endpoint.
#[derive(Debug, Component)]
pub struct MetricsEndpoint {}

impl MetricsEndpoint {
    /// Create the component.
    #[cfg(feature = "prometheus")]
    pub fn new(config: &Config) -> Result<Self, FrameworkError> {
        if let Some(addr) = config.endpoint_addr {
            info!("Trying to open metrics endpoint at {}...", addr);

            let endpoint_result = metrics_exporter_prometheus::PrometheusBuilder::new()
                .with_http_listener(addr)
                .install();

            match endpoint_result {
                Ok(()) => {
                    info!("Opened metrics endpoint at {}", addr);

                    // Expose binary metadata to metrics, using a single time series with
                    // value 1:
                    //     https://www.robustperception.io/exposing-the-software-version-to-prometheus
                    metrics::counter!(
                        format!("{}.build.info", env!("CARGO_PKG_NAME")),
                        "version" => env!("CARGO_PKG_VERSION")
                    )
                    .increment(1);
                }
                Err(e) => panic!(
                    "Opening metrics endpoint listener {addr:?} failed: {e:?}. \
                     Hint: Check if another zebrad or zcashd process is running. \
                     Try changing the metrics endpoint_addr in the Zebra config.",
                ),
            }
        }

        Ok(Self {})
    }

    /// Create the component.
    #[cfg(not(feature = "prometheus"))]
    pub fn new(config: &Config) -> Result<Self, FrameworkError> {
        if let Some(addr) = config.endpoint_addr {
            warn!(
                ?addr,
                "unable to activate configured metrics endpoint: \
                 enable the 'prometheus' feature when compiling zebrad",
            );
        }

        Ok(Self {})
    }
}

/// Metrics configuration section.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
    /// The address used for the Prometheus metrics endpoint.
    ///
    /// Install Zebra using `cargo install --features=prometheus` to enable this config.
    ///
    /// The endpoint is disabled if this is set to `None`.
    pub endpoint_addr: Option<SocketAddr>,
}

// we like our default configs to be explicit
#[allow(unknown_lints)]
#[allow(clippy::derivable_impls)]
impl Default for Config {
    fn default() -> Self {
        Self {
            endpoint_addr: None,
        }
    }
}