1#[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#[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 #[cfg(any(prometheus, opentelemetry))]
60 pub fn prometheus_registry(&self) -> &prometheus::Registry {
61 &self.prometheus_registry
62 }
63
64 #[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 #[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 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 #[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 #[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 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 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}