mas_config/sections/
telemetry.rs1use schemars::JsonSchema;
8use serde::{Deserialize, Serialize, de::Error as _};
9use serde_with::skip_serializing_none;
10use url::Url;
11
12use super::ConfigurationSection;
13
14fn sample_rate_example() -> f64 {
15 0.5
16}
17
18#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
20#[serde(rename_all = "lowercase")]
21pub enum Propagator {
22 TraceContext,
24
25 Baggage,
27
28 Jaeger,
30}
31
32#[allow(clippy::unnecessary_wraps)]
33fn otlp_endpoint_default() -> Option<String> {
34 Some("https://localhost:4318".to_owned())
35}
36
37#[skip_serializing_none]
39#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)]
40#[serde(rename_all = "lowercase")]
41pub enum TracingExporterKind {
42 #[default]
44 None,
45
46 Stdout,
48
49 Otlp,
51}
52
53#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
55pub struct TracingConfig {
56 #[serde(default)]
58 pub exporter: TracingExporterKind,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 #[schemars(url, default = "otlp_endpoint_default")]
63 pub endpoint: Option<Url>,
64
65 #[serde(default)]
67 pub propagators: Vec<Propagator>,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
73 #[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))]
74 pub sample_rate: Option<f64>,
75}
76
77impl TracingConfig {
78 fn is_default(&self) -> bool {
80 matches!(self.exporter, TracingExporterKind::None)
81 && self.endpoint.is_none()
82 && self.propagators.is_empty()
83 }
84}
85
86#[skip_serializing_none]
88#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)]
89#[serde(rename_all = "lowercase")]
90pub enum MetricsExporterKind {
91 #[default]
93 None,
94
95 Stdout,
97
98 Otlp,
100
101 Prometheus,
104}
105
106#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
108pub struct MetricsConfig {
109 #[serde(default)]
111 pub exporter: MetricsExporterKind,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 #[schemars(url, default = "otlp_endpoint_default")]
116 pub endpoint: Option<Url>,
117}
118
119impl MetricsConfig {
120 fn is_default(&self) -> bool {
122 matches!(self.exporter, MetricsExporterKind::None) && self.endpoint.is_none()
123 }
124}
125
126fn sentry_dsn_example() -> &'static str {
127 "https://public@host:port/1"
128}
129
130fn sentry_environment_example() -> &'static str {
131 "production"
132}
133
134#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
136pub struct SentryConfig {
137 #[schemars(url, example = "sentry_dsn_example")]
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub dsn: Option<String>,
141
142 #[schemars(example = "sentry_environment_example")]
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub environment: Option<String>,
148
149 #[serde(skip_serializing_if = "Option::is_none")]
153 #[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))]
154 pub sample_rate: Option<f32>,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
160 #[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))]
161 pub traces_sample_rate: Option<f32>,
162}
163
164impl SentryConfig {
165 fn is_default(&self) -> bool {
167 self.dsn.is_none()
168 }
169}
170
171#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
173pub struct TelemetryConfig {
174 #[serde(default, skip_serializing_if = "TracingConfig::is_default")]
176 pub tracing: TracingConfig,
177
178 #[serde(default, skip_serializing_if = "MetricsConfig::is_default")]
180 pub metrics: MetricsConfig,
181
182 #[serde(default, skip_serializing_if = "SentryConfig::is_default")]
184 pub sentry: SentryConfig,
185}
186
187impl TelemetryConfig {
188 pub(crate) fn is_default(&self) -> bool {
190 self.tracing.is_default() && self.metrics.is_default() && self.sentry.is_default()
191 }
192}
193
194impl ConfigurationSection for TelemetryConfig {
195 const PATH: Option<&'static str> = Some("telemetry");
196
197 fn validate(&self, _figment: &figment::Figment) -> Result<(), figment::Error> {
198 if let Some(sample_rate) = self.sentry.sample_rate {
199 if !(0.0..=1.0).contains(&sample_rate) {
200 return Err(figment::error::Error::custom(
201 "Sentry sample rate must be between 0.0 and 1.0",
202 )
203 .with_path("sentry.sample_rate"));
204 }
205 }
206
207 if let Some(sample_rate) = self.sentry.traces_sample_rate {
208 if !(0.0..=1.0).contains(&sample_rate) {
209 return Err(figment::error::Error::custom(
210 "Sentry sample rate must be between 0.0 and 1.0",
211 )
212 .with_path("sentry.traces_sample_rate"));
213 }
214 }
215
216 if let Some(sample_rate) = self.tracing.sample_rate {
217 if !(0.0..=1.0).contains(&sample_rate) {
218 return Err(figment::error::Error::custom(
219 "Tracing sample rate must be between 0.0 and 1.0",
220 )
221 .with_path("tracing.sample_rate"));
222 }
223 }
224
225 Ok(())
226 }
227}