autometrics/
settings.rs

1//! Customize the global settings for Autometrics.
2//!
3//! ```rust
4//! use autometrics::settings::AutometricsSettings;
5//!
6//! AutometricsSettings::builder()
7//!    .service_name("test_service")
8//!   .init();
9//! ```
10//!
11//! See [`AutometricsSettingsBuilder`] for more details on the available options.
12
13#[cfg(prometheus_exporter)]
14use crate::prometheus_exporter::{self, ExporterInitializationError};
15use once_cell::sync::OnceCell;
16use std::env;
17use thiserror::Error;
18
19pub(crate) static AUTOMETRICS_SETTINGS: OnceCell<AutometricsSettings> = OnceCell::new();
20#[cfg(any(prometheus_exporter, prometheus, prometheus_client))]
21const DEFAULT_HISTOGRAM_BUCKETS: [f64; 14] = [
22    0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0,
23];
24
25/// Load the settings configured by the user or use the defaults.
26///
27/// Note that attempting to set the settings after this function is called will panic.
28#[allow(dead_code)]
29pub(crate) fn get_settings() -> &'static AutometricsSettings {
30    AUTOMETRICS_SETTINGS.get_or_init(|| AutometricsSettingsBuilder::default().build())
31}
32
33pub struct AutometricsSettings {
34    #[cfg(any(prometheus_exporter, prometheus, prometheus_client))]
35    pub(crate) histogram_buckets: Vec<f64>,
36    pub(crate) service_name: String,
37    pub(crate) repo_url: String,
38    pub(crate) repo_provider: String,
39    #[cfg(any(prometheus, opentelemetry))]
40    pub(crate) prometheus_registry: prometheus::Registry,
41    #[cfg(prometheus_client)]
42    pub(crate) prometheus_client_registry: prometheus_client::registry::Registry,
43    #[cfg(prometheus_client)]
44    pub(crate) prometheus_client_metrics: crate::tracker::prometheus_client::Metrics,
45}
46
47impl AutometricsSettings {
48    pub fn builder() -> AutometricsSettingsBuilder {
49        AutometricsSettingsBuilder::default()
50    }
51
52    /// Access the [`Registry`] where Autometrics metrics are collected.
53    ///
54    /// You can use this to encode the metrics using the functionality provided by the [`prometheus`] crate
55    /// if you do not want to use the provided [`prometheus_exporter`].
56    ///
57    /// [`Registry`]: prometheus::Registry
58    /// [`prometheus_exporter`]: crate::prometheus_exporter
59    #[cfg(any(prometheus, opentelemetry))]
60    pub fn prometheus_registry(&self) -> &prometheus::Registry {
61        &self.prometheus_registry
62    }
63
64    /// Access the [`Registry`] where Autometrics metrics are collected.
65    ///
66    /// You can use this to encode the metrics using the functionality provided by the [`prometheus_client`] crate
67    /// if you do not want to use the provided [`prometheus_exporter`].
68    ///
69    /// [`Registry`]: prometheus_client::registry::Registry
70    /// [`prometheus_exporter`]: crate::prometheus_exporter
71    #[cfg(prometheus_client)]
72    pub fn prometheus_client_registry(&self) -> &prometheus_client::registry::Registry {
73        &self.prometheus_client_registry
74    }
75}
76
77#[derive(Debug, Default)]
78pub struct AutometricsSettingsBuilder {
79    pub(crate) service_name: Option<String>,
80    pub(crate) repo_url: Option<String>,
81    pub(crate) repo_provider: Option<String>,
82    #[cfg(any(prometheus_exporter, prometheus, prometheus_client))]
83    pub(crate) histogram_buckets: Option<Vec<f64>>,
84    #[cfg(any(prometheus, opentelemetry))]
85    pub(crate) prometheus_registry: Option<prometheus::Registry>,
86    #[cfg(prometheus_client)]
87    pub(crate) prometheus_client_registry: Option<prometheus_client::registry::Registry>,
88}
89
90impl AutometricsSettingsBuilder {
91    /// Set the buckets, represented in seconds, used for the function latency histograms.
92    ///
93    /// If this is not set, the buckets recommended by the [OpenTelemetry specification] are used.
94    ///
95    /// [OpenTelemetry specification]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#explicit-bucket-histogram-aggregation
96    #[cfg(any(prometheus_exporter, prometheus, prometheus_client))]
97    pub fn histogram_buckets(mut self, histogram_buckets: impl Into<Vec<f64>>) -> Self {
98        self.histogram_buckets = Some(histogram_buckets.into());
99        self
100    }
101
102    /// All metrics produced by Autometrics have a label called `service.name`
103    /// (or `service_name` when exported to Prometheus) attached to
104    /// identify the logical service they are part of.
105    ///
106    /// You can set this here or via environment variables.
107    ///
108    /// The priority for where the service name is loaded from is:
109    /// 1. This method
110    /// 2. `AUTOMETRICS_SERVICE_NAME` (at runtime)
111    /// 3. `OTEL_SERVICE_NAME` (at runtime)
112    /// 4. `CARGO_PKG_NAME` (at compile time), which is the name of the crate defined in the `Cargo.toml` file.
113    pub fn service_name(mut self, service_name: impl Into<String>) -> Self {
114        self.service_name = Some(service_name.into());
115        self
116    }
117
118    pub fn repo_url(mut self, repo_url: impl Into<String>) -> Self {
119        self.repo_url = Some(repo_url.into());
120        self
121    }
122
123    pub fn repo_provider(mut self, repo_provider: impl Into<String>) -> Self {
124        self.repo_provider = Some(repo_provider.into());
125        self
126    }
127
128    /// Configure the [`prometheus::Registry`] that will be used to collect metrics when using
129    /// either the `prometheus` or `opentelemetry` backends. If none is set, it will use
130    /// the [`prometheus::default_registry`].
131    ///
132    /// This is mainly useful if you want to add custom metrics to the same registry, or if you want to
133    /// add a custom prefix or custom labels to all of the metrics.
134    ///
135    /// If you are not using the provided [`prometheus_exporter`] to export metrics and want to encode
136    /// the metrics from the `Registry`, you can simply `clone` the `Registry` before passing it in here
137    /// and use the original one for encoding.
138    #[cfg(any(prometheus, opentelemetry))]
139    pub fn prometheus_registry(mut self, registry: prometheus::Registry) -> Self {
140        self.prometheus_registry = Some(registry);
141        self
142    }
143
144    /// Configure the [`prometheus_client::registry::Registry`] that will be used to collect metrics.
145    ///
146    /// This is mainly useful if you want to add custom metrics to the same registry.
147    ///
148    /// If you are not using the provided [`prometheus_exporter`] to export metrics and want to access
149    /// the `Registry` again to encode the metrics, you can access it again via [`AutometricsSettings::prometheus_client_registry`].
150    #[cfg(prometheus_client)]
151    pub fn prometheus_client_registry(
152        mut self,
153        registry: prometheus_client::registry::Registry,
154    ) -> Self {
155        self.prometheus_client_registry = Some(registry);
156        self
157    }
158
159    /// Set the global settings for Autometrics. This returns an error if the
160    /// settings have already been initialized.
161    ///
162    /// Note: this function should only be called once and MUST be called before
163    /// the settings are used by any other Autometrics functions.
164    ///
165    /// If the Prometheus exporter is enabled, this will also initialize it.
166    pub fn try_init(self) -> Result<&'static AutometricsSettings, SettingsInitializationError> {
167        let settings = self.build();
168
169        let settings = AUTOMETRICS_SETTINGS
170            .try_insert(settings)
171            .map_err(|_| SettingsInitializationError::AlreadyInitialized)?;
172
173        #[cfg(prometheus_exporter)]
174        prometheus_exporter::try_init()?;
175
176        Ok(settings)
177    }
178
179    /// Set the global settings for Autometrics.
180    ///
181    /// Note: this function can only be called once and MUST be called before
182    /// the settings are used by any other Autometrics functions.
183    ///
184    /// If the Prometheus exporter is enabled, this will also initialize it.
185    ///
186    /// ## Panics
187    ///
188    /// This function will panic if the settings have already been initialized.
189    pub fn init(self) -> &'static AutometricsSettings {
190        self.try_init().unwrap()
191    }
192
193    fn build(self) -> AutometricsSettings {
194        #[cfg(prometheus_client)]
195        let (prometheus_client_registry, prometheus_client_metrics) =
196            crate::tracker::prometheus_client::initialize_registry(
197                self.prometheus_client_registry
198                    .unwrap_or_else(<prometheus_client::registry::Registry>::default),
199            );
200
201        let repo_url = self
202            .repo_url
203            .or_else(|| env::var("AUTOMETRICS_REPOSITORY_URL").ok())
204            .unwrap_or_else(|| env!("CARGO_PKG_REPOSITORY").to_string());
205
206        AutometricsSettings {
207            #[cfg(any(prometheus_exporter, prometheus, prometheus_client))]
208            histogram_buckets: self
209                .histogram_buckets
210                .unwrap_or_else(|| DEFAULT_HISTOGRAM_BUCKETS.to_vec()),
211            service_name: self
212                .service_name
213                .or_else(|| env::var("AUTOMETRICS_SERVICE_NAME").ok())
214                .or_else(|| env::var("OTEL_SERVICE_NAME").ok())
215                .unwrap_or_else(|| env!("CARGO_PKG_NAME").to_string()),
216            repo_provider: self
217                .repo_provider
218                .or_else(|| env::var("AUTOMETRICS_REPOSITORY_PROVIDER").ok())
219                .or_else(|| {
220                    AutometricsSettingsBuilder::determinate_repo_provider_from_url(Some(&repo_url))
221                        .map(|s| s.to_string())
222                })
223                .unwrap_or_default(),
224            repo_url,
225            #[cfg(prometheus_client)]
226            prometheus_client_registry,
227            #[cfg(prometheus_client)]
228            prometheus_client_metrics,
229            #[cfg(any(prometheus, opentelemetry))]
230            prometheus_registry: self
231                .prometheus_registry
232                .unwrap_or_else(|| prometheus::default_registry().clone()),
233        }
234    }
235
236    fn determinate_repo_provider_from_url(url: Option<&str>) -> Option<&'static str> {
237        url.and_then(|url| {
238            let lowered = url.to_lowercase();
239
240            if lowered.contains("github.com") {
241                Some("github")
242            } else if lowered.contains("gitlab.com") {
243                Some("gitlab")
244            } else if lowered.contains("bitbucket.org") {
245                Some("bitbucket")
246            } else {
247                None
248            }
249        })
250    }
251}
252
253#[derive(Debug, Error)]
254pub enum SettingsInitializationError {
255    #[error("Autometrics settings have already been initialized")]
256    AlreadyInitialized,
257
258    #[cfg(prometheus_exporter)]
259    #[error(transparent)]
260    PrometheusExporter(#[from] ExporterInitializationError),
261}