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(&self, _figment: &figment::Figment) -> Result<(), figment::Error> {
186 if let Some(sample_rate) = self.sentry.sample_rate {
187 if !(0.0..=1.0).contains(&sample_rate) {
188 return Err(figment::error::Error::custom(
189 "Sentry sample rate must be between 0.0 and 1.0",
190 )
191 .with_path("sentry.sample_rate"));
192 }
193 }
194
195 if let Some(sample_rate) = self.sentry.traces_sample_rate {
196 if !(0.0..=1.0).contains(&sample_rate) {
197 return Err(figment::error::Error::custom(
198 "Sentry sample rate must be between 0.0 and 1.0",
199 )
200 .with_path("sentry.traces_sample_rate"));
201 }
202 }
203
204 if let Some(sample_rate) = self.tracing.sample_rate {
205 if !(0.0..=1.0).contains(&sample_rate) {
206 return Err(figment::error::Error::custom(
207 "Tracing sample rate must be between 0.0 and 1.0",
208 )
209 .with_path("tracing.sample_rate"));
210 }
211 }
212
213 Ok(())
214 }
215}