mas_config/sections/
telemetry.rs1use schemars::JsonSchema;
9use serde::{Deserialize, Serialize, de::Error as _};
10use serde_with::skip_serializing_none;
11use url::Url;
12
13use super::ConfigurationSection;
14
15#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
17#[serde(rename_all = "lowercase")]
18pub enum Propagator {
19 TraceContext,
21
22 Baggage,
24
25 Jaeger,
27}
28
29#[expect(clippy::unnecessary_wraps)]
30fn otlp_endpoint_default() -> Option<String> {
31 Some("https://localhost:4318".to_owned())
32}
33
34#[skip_serializing_none]
36#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)]
37#[serde(rename_all = "lowercase")]
38pub enum TracingExporterKind {
39 #[default]
41 None,
42
43 Stdout,
45
46 Otlp,
48}
49
50#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
52pub struct TracingConfig {
53 #[serde(default)]
55 pub exporter: TracingExporterKind,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
59 #[schemars(url, default = "otlp_endpoint_default")]
60 pub endpoint: Option<Url>,
61
62 #[serde(default)]
64 pub propagators: Vec<Propagator>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
70 #[schemars(example = 0.5, range(min = 0.0, max = 1.0))]
71 pub sample_rate: Option<f64>,
72}
73
74impl TracingConfig {
75 fn is_default(&self) -> bool {
77 matches!(self.exporter, TracingExporterKind::None)
78 && self.endpoint.is_none()
79 && self.propagators.is_empty()
80 }
81}
82
83#[skip_serializing_none]
85#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)]
86#[serde(rename_all = "lowercase")]
87pub enum MetricsExporterKind {
88 #[default]
90 None,
91
92 Stdout,
94
95 Otlp,
97
98 Prometheus,
101}
102
103#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
105pub struct MetricsConfig {
106 #[serde(default)]
108 pub exporter: MetricsExporterKind,
109
110 #[serde(skip_serializing_if = "Option::is_none")]
112 #[schemars(url, default = "otlp_endpoint_default")]
113 pub endpoint: Option<Url>,
114}
115
116impl MetricsConfig {
117 fn is_default(&self) -> bool {
119 matches!(self.exporter, MetricsExporterKind::None) && self.endpoint.is_none()
120 }
121}
122
123#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
125pub struct SentryConfig {
126 #[schemars(url, example = &"https://public@host:port/1")]
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub dsn: Option<String>,
130
131 #[schemars(example = &"production")]
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub environment: Option<String>,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
142 #[schemars(example = 0.5, range(min = 0.0, max = 1.0))]
143 pub sample_rate: Option<f32>,
144
145 #[serde(skip_serializing_if = "Option::is_none")]
150 #[schemars(example = 0.5, range(min = 0.0, max = 1.0))]
151 pub traces_sample_rate: Option<f32>,
152}
153
154impl SentryConfig {
155 fn is_default(&self) -> bool {
157 self.dsn.is_none()
158 }
159}
160
161#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
163pub struct TelemetryConfig {
164 #[serde(default, skip_serializing_if = "TracingConfig::is_default")]
166 pub tracing: TracingConfig,
167
168 #[serde(default, skip_serializing_if = "MetricsConfig::is_default")]
170 pub metrics: MetricsConfig,
171
172 #[serde(default, skip_serializing_if = "SentryConfig::is_default")]
174 pub sentry: SentryConfig,
175}
176
177impl TelemetryConfig {
178 pub(crate) fn is_default(&self) -> bool {
180 self.tracing.is_default() && self.metrics.is_default() && self.sentry.is_default()
181 }
182}
183
184impl ConfigurationSection for TelemetryConfig {
185 const PATH: Option<&'static str> = Some("telemetry");
186
187 fn validate(
188 &self,
189 _figment: &figment::Figment,
190 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
191 if let Some(sample_rate) = self.sentry.sample_rate
192 && !(0.0..=1.0).contains(&sample_rate)
193 {
194 return Err(figment::error::Error::custom(
195 "Sentry sample rate must be between 0.0 and 1.0",
196 )
197 .with_path("sentry.sample_rate")
198 .into());
199 }
200
201 if let Some(sample_rate) = self.sentry.traces_sample_rate
202 && !(0.0..=1.0).contains(&sample_rate)
203 {
204 return Err(figment::error::Error::custom(
205 "Sentry sample rate must be between 0.0 and 1.0",
206 )
207 .with_path("sentry.traces_sample_rate")
208 .into());
209 }
210
211 if let Some(sample_rate) = self.tracing.sample_rate
212 && !(0.0..=1.0).contains(&sample_rate)
213 {
214 return Err(figment::error::Error::custom(
215 "Tracing sample rate must be between 0.0 and 1.0",
216 )
217 .with_path("tracing.sample_rate")
218 .into());
219 }
220
221 Ok(())
222 }
223}