camel_core/shared/observability/domain/
config.rs1use serde::{Deserialize, Deserializer};
2
3#[derive(Clone, Debug, Default)]
9pub struct TracerConfig {
10 pub enabled: bool,
15
16 pub tracing_enabled_explicit: bool,
24
25 pub pipeline_enabled: bool,
33
34 pub detail_level: DetailLevel,
35
36 pub outputs: TracerOutputs,
37
38 pub metrics_levers: MetricsLeversConfig,
43}
44
45impl<'de> Deserialize<'de> for TracerConfig {
53 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54 where
55 D: Deserializer<'de>,
56 {
57 #[derive(Deserialize)]
58 struct Raw {
59 #[serde(default)]
60 enabled: Option<bool>,
61 #[serde(default = "default_detail_level")]
62 detail_level: DetailLevel,
63 #[serde(default)]
64 outputs: TracerOutputs,
65 }
66
67 let raw = Raw::deserialize(deserializer)?;
68 Ok(Self {
69 enabled: raw.enabled.unwrap_or(false),
70 tracing_enabled_explicit: raw.enabled.is_some(),
71 pipeline_enabled: false,
72 detail_level: raw.detail_level,
73 outputs: raw.outputs,
74 metrics_levers: MetricsLeversConfig::default(),
75 })
76 }
77}
78
79#[derive(Debug, Clone, Deserialize, Default)]
80pub struct TracerOutputs {
81 #[serde(default)]
82 pub stdout: StdoutOutput,
83
84 #[serde(default)]
85 pub file: Option<FileOutput>,
86}
87
88#[derive(Debug, Clone, Deserialize)]
89pub struct StdoutOutput {
90 #[serde(default = "default_true")]
91 pub enabled: bool,
92
93 #[serde(default = "default_format")]
94 pub format: OutputFormat,
95}
96
97impl Default for StdoutOutput {
98 fn default() -> Self {
99 Self {
100 enabled: true,
101 format: OutputFormat::Json,
102 }
103 }
104}
105
106#[derive(Debug, Clone, Deserialize)]
107pub struct FileOutput {
108 pub enabled: bool,
109 pub path: String,
110 #[serde(default = "default_format")]
111 pub format: OutputFormat,
112}
113
114#[derive(Debug, Clone, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
125#[serde(rename_all = "lowercase")]
126pub enum DetailLevel {
127 #[default]
128 Minimal,
129 Medium,
130 Full,
131}
132
133#[derive(Debug, Clone, Deserialize, Default)]
134#[serde(rename_all = "lowercase")]
135pub enum OutputFormat {
136 #[default]
137 Json,
138 Plain,
139}
140
141#[derive(Clone, Debug, PartialEq)]
150pub struct MetricsLeversConfig {
151 pub enabled: bool,
152 pub exchange: bool,
153 pub duration: bool,
154 pub components: bool,
155}
156
157impl MetricsLeversConfig {
158 pub fn exchanges_enabled(&self) -> bool {
160 self.enabled && self.exchange
161 }
162
163 pub fn durations_enabled(&self) -> bool {
165 self.enabled && self.duration
166 }
167
168 pub fn components_enabled(&self) -> bool {
172 self.enabled && self.components
173 }
174}
175
176impl Default for MetricsLeversConfig {
177 fn default() -> Self {
178 Self {
179 enabled: true,
180 exchange: true,
181 duration: true,
182 components: false,
183 }
184 }
185}
186
187impl<'de> Deserialize<'de> for MetricsLeversConfig {
192 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
193 where
194 D: Deserializer<'de>,
195 {
196 #[derive(Deserialize)]
197 #[serde(deny_unknown_fields)]
198 struct Raw {
199 #[serde(default)]
200 enabled: Option<bool>,
201 #[serde(default)]
202 exchange: Option<bool>,
203 #[serde(default)]
204 duration: Option<bool>,
205 #[serde(default)]
206 components: Option<bool>,
207 }
208
209 let raw = Raw::deserialize(deserializer)?;
210 Ok(Self {
211 enabled: raw.enabled.unwrap_or(true),
212 exchange: raw.exchange.unwrap_or(true),
213 duration: raw.duration.unwrap_or(true),
214 components: raw.components.unwrap_or(false),
215 })
216 }
217}
218
219fn default_detail_level() -> DetailLevel {
220 DetailLevel::Minimal
221}
222fn default_format() -> OutputFormat {
223 OutputFormat::Json
224}
225fn default_true() -> bool {
226 true
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn tracer_config_defaults_are_stable() {
235 let cfg = TracerConfig::default();
236 assert!(!cfg.enabled);
237 assert_eq!(cfg.detail_level, DetailLevel::Minimal);
238 assert!(cfg.outputs.stdout.enabled);
239 assert!(matches!(cfg.outputs.stdout.format, OutputFormat::Json));
240 assert!(cfg.outputs.file.is_none());
241 }
242
243 #[test]
244 fn tracer_config_deserializes_lowercase_enums() {
245 let cfg: TracerConfig = serde_json::from_str(
246 r#"{
247 "enabled": true,
248 "detail_level": "full",
249 "outputs": {
250 "stdout": { "enabled": false, "format": "plain" },
251 "file": { "enabled": true, "path": "/tmp/trace.log", "format": "json" }
252 }
253}"#,
254 )
255 .unwrap();
256
257 assert!(cfg.enabled);
258 assert_eq!(cfg.detail_level, DetailLevel::Full);
259 assert!(!cfg.outputs.stdout.enabled);
260 assert!(matches!(cfg.outputs.stdout.format, OutputFormat::Plain));
261 assert_eq!(cfg.outputs.file.as_ref().unwrap().path, "/tmp/trace.log");
262 }
263}