Skip to main content

camel_core/shared/observability/domain/
config.rs

1use std::fmt;
2use std::sync::Arc;
3
4use serde::Deserialize;
5
6use camel_api::metrics::MetricsCollector;
7
8/// Configuration for the Tracer EIP (Enterprise Integration Pattern).
9///
10/// This struct defines how message tracing should be performed throughout
11/// Camel routes. Use `CamelContext::set_tracer_config` to apply configuration
12/// programmatically, or configure via `Camel.toml` as shown in the module documentation.
13#[derive(Clone, Deserialize, Default)]
14pub struct TracerConfig {
15    #[serde(default)]
16    pub enabled: bool,
17
18    #[serde(default = "default_detail_level")]
19    pub detail_level: DetailLevel,
20
21    #[serde(default)]
22    pub outputs: TracerOutputs,
23
24    /// Metrics collector for recording route-level metrics.
25    /// Not serializable - injected at runtime.
26    #[serde(skip)]
27    pub metrics_collector: Option<Arc<dyn MetricsCollector>>,
28}
29
30impl fmt::Debug for TracerConfig {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.debug_struct("TracerConfig")
33            .field("enabled", &self.enabled)
34            .field("detail_level", &self.detail_level)
35            .field("outputs", &self.outputs)
36            .field(
37                "metrics_collector",
38                &self.metrics_collector.as_ref().map(|_| "MetricsCollector"),
39            )
40            .finish()
41    }
42}
43
44#[derive(Debug, Clone, Deserialize, Default)]
45pub struct TracerOutputs {
46    #[serde(default)]
47    pub stdout: StdoutOutput,
48
49    #[serde(default)]
50    pub file: Option<FileOutput>,
51}
52
53#[derive(Debug, Clone, Deserialize)]
54pub struct StdoutOutput {
55    #[serde(default = "default_true")]
56    pub enabled: bool,
57
58    #[serde(default = "default_format")]
59    pub format: OutputFormat,
60}
61
62impl Default for StdoutOutput {
63    fn default() -> Self {
64        Self {
65            enabled: true,
66            format: OutputFormat::Json,
67        }
68    }
69}
70
71#[derive(Debug, Clone, Deserialize)]
72pub struct FileOutput {
73    pub enabled: bool,
74    pub path: String,
75    #[serde(default = "default_format")]
76    pub format: OutputFormat,
77}
78
79/// Controls the level of detail captured in trace spans.
80///
81/// Each variant progressively adds more fields to the trace output:
82///
83/// - `Minimal`: Includes the core span attributes (correlation_id, route_id,
84///   and step_index). `duration_ms` is a `camel_tracer` log field at
85///   every detail level, not a span attribute.
86/// - `Medium`: Includes Minimal fields plus headers_count, body_type, has_error,
87///   and output_body_type
88/// - `Full`: Includes all fields from Minimal and Medium plus up to 3 message headers
89#[derive(Debug, Clone, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
90#[serde(rename_all = "lowercase")]
91pub enum DetailLevel {
92    #[default]
93    Minimal,
94    Medium,
95    Full,
96}
97
98#[derive(Debug, Clone, Deserialize, Default)]
99#[serde(rename_all = "lowercase")]
100pub enum OutputFormat {
101    #[default]
102    Json,
103    Plain,
104}
105
106fn default_detail_level() -> DetailLevel {
107    DetailLevel::Minimal
108}
109fn default_format() -> OutputFormat {
110    OutputFormat::Json
111}
112fn default_true() -> bool {
113    true
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn tracer_config_defaults_are_stable() {
122        let cfg = TracerConfig::default();
123        assert!(!cfg.enabled);
124        assert_eq!(cfg.detail_level, DetailLevel::Minimal);
125        assert!(cfg.outputs.stdout.enabled);
126        assert!(matches!(cfg.outputs.stdout.format, OutputFormat::Json));
127        assert!(cfg.outputs.file.is_none());
128        assert!(cfg.metrics_collector.is_none());
129    }
130
131    #[test]
132    fn tracer_config_deserializes_lowercase_enums() {
133        let cfg: TracerConfig = serde_json::from_str(
134            r#"{
135  "enabled": true,
136  "detail_level": "full",
137  "outputs": {
138    "stdout": { "enabled": false, "format": "plain" },
139    "file": { "enabled": true, "path": "/tmp/trace.log", "format": "json" }
140  }
141}"#,
142        )
143        .unwrap();
144
145        assert!(cfg.enabled);
146        assert_eq!(cfg.detail_level, DetailLevel::Full);
147        assert!(!cfg.outputs.stdout.enabled);
148        assert!(matches!(cfg.outputs.stdout.format, OutputFormat::Plain));
149        assert_eq!(cfg.outputs.file.as_ref().unwrap().path, "/tmp/trace.log");
150    }
151
152    #[test]
153    fn tracer_config_debug_redacts_metrics_collector() {
154        let dbg = format!("{:?}", TracerConfig::default());
155        assert!(dbg.contains("TracerConfig"));
156        assert!(dbg.contains("metrics_collector"));
157    }
158}