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
14#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
16#[serde(rename_all = "lowercase")]
17pub enum Propagator {
18 TraceContext,
20
21 Baggage,
23
24 Jaeger,
26}
27
28#[allow(clippy::unnecessary_wraps)]
29fn otlp_endpoint_default() -> Option<String> {
30 Some("https://localhost:4318".to_owned())
31}
32
33#[skip_serializing_none]
35#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)]
36#[serde(rename_all = "lowercase")]
37pub enum TracingExporterKind {
38 #[default]
40 None,
41
42 Stdout,
44
45 Otlp,
47}
48
49#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
51pub struct TracingConfig {
52 #[serde(default)]
54 pub exporter: TracingExporterKind,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 #[schemars(url, default = "otlp_endpoint_default")]
59 pub endpoint: Option<Url>,
60
61 #[serde(default)]
63 pub propagators: Vec<Propagator>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
69 #[schemars(example = 0.5, range(min = 0.0, max = 1.0))]
70 pub sample_rate: Option<f64>,
71}
72
73impl TracingConfig {
74 fn is_default(&self) -> bool {
76 matches!(self.exporter, TracingExporterKind::None)
77 && self.endpoint.is_none()
78 && self.propagators.is_empty()
79 }
80}
81
82#[skip_serializing_none]
84#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default)]
85#[serde(rename_all = "lowercase")]
86pub enum MetricsExporterKind {
87 #[default]
89 None,
90
91 Stdout,
93
94 Otlp,
96
97 Prometheus,
100}
101
102#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
104pub struct MetricsConfig {
105 #[serde(default)]
107 pub exporter: MetricsExporterKind,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 #[schemars(url, default = "otlp_endpoint_default")]
112 pub endpoint: Option<Url>,
113}
114
115impl MetricsConfig {
116 fn is_default(&self) -> bool {
118 matches!(self.exporter, MetricsExporterKind::None) && self.endpoint.is_none()
119 }
120}
121
122#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
124pub struct SentryConfig {
125 #[schemars(url, example = &"https://public@host:port/1")]
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub dsn: Option<String>,
129
130 #[schemars(example = &"production")]
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub environment: Option<String>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
141 #[schemars(example = 0.5, range(min = 0.0, max = 1.0))]
142 pub sample_rate: Option<f32>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
148 #[schemars(example = 0.5, range(min = 0.0, max = 1.0))]
149 pub traces_sample_rate: Option<f32>,
150}
151
152impl SentryConfig {
153 fn is_default(&self) -> bool {
155 self.dsn.is_none()
156 }
157}
158
159#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
161pub struct TelemetryConfig {
162 #[serde(default, skip_serializing_if = "TracingConfig::is_default")]
164 pub tracing: TracingConfig,
165
166 #[serde(default, skip_serializing_if = "MetricsConfig::is_default")]
168 pub metrics: MetricsConfig,
169
170 #[serde(default, skip_serializing_if = "SentryConfig::is_default")]
172 pub sentry: SentryConfig,
173}
174
175impl TelemetryConfig {
176 pub(crate) fn is_default(&self) -> bool {
178 self.tracing.is_default() && self.metrics.is_default() && self.sentry.is_default()
179 }
180}
181
182impl ConfigurationSection for TelemetryConfig {
183 const PATH: Option<&'static str> = Some("telemetry");
184
185 fn validate(
186 &self,
187 _figment: &figment::Figment,
188 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
189 if let Some(sample_rate) = self.sentry.sample_rate
190 && !(0.0..=1.0).contains(&sample_rate)
191 {
192 return Err(figment::error::Error::custom(
193 "Sentry sample rate must be between 0.0 and 1.0",
194 )
195 .with_path("sentry.sample_rate")
196 .into());
197 }
198
199 if let Some(sample_rate) = self.sentry.traces_sample_rate
200 && !(0.0..=1.0).contains(&sample_rate)
201 {
202 return Err(figment::error::Error::custom(
203 "Sentry sample rate must be between 0.0 and 1.0",
204 )
205 .with_path("sentry.traces_sample_rate")
206 .into());
207 }
208
209 if let Some(sample_rate) = self.tracing.sample_rate
210 && !(0.0..=1.0).contains(&sample_rate)
211 {
212 return Err(figment::error::Error::custom(
213 "Tracing sample rate must be between 0.0 and 1.0",
214 )
215 .with_path("tracing.sample_rate")
216 .into());
217 }
218
219 Ok(())
220 }
221}