Skip to main content

cognee_observability/
settings.rs

1//! Read-only view of the observability-relevant subset of `Settings`.
2//!
3//! Defined here (not in `cognee-lib`) to avoid a hard dependency on the
4//! umbrella crate. `cognee-lib::Settings` implements this trait in a
5//! sibling task so HTTP middleware and other upstream callers can drive
6//! [`crate::init_telemetry`] without going through `cognee-lib`.
7
8/// Borrow-only adapter over the OTEL fields of cognee `Settings`.
9///
10/// All accessors return `&str` / `bool` so implementations can avoid
11/// cloning. `Send + Sync` so the trait object can travel across async
12/// task boundaries.
13pub trait SettingsView: Send + Sync {
14    /// Mirrors `Settings.cognee_tracing_enabled`.
15    fn tracing_enabled(&self) -> bool;
16    /// Mirrors `Settings.otel_service_name`.
17    fn service_name(&self) -> &str;
18    /// Mirrors `Settings.otel_exporter_otlp_endpoint`.
19    fn otlp_endpoint(&self) -> &str;
20    /// Mirrors `Settings.otel_exporter_otlp_headers`.
21    fn otlp_headers(&self) -> &str;
22    /// Mirrors `Settings.otel_exporter_otlp_protocol`.
23    fn otlp_protocol(&self) -> &str;
24    /// Mirrors `Settings.otel_span_processor`.
25    fn span_processor(&self) -> &str;
26    /// Mirrors `Settings.otel_traces_sampler`.
27    fn traces_sampler(&self) -> &str;
28    /// Mirrors `Settings.otel_traces_sampler_arg`.
29    fn traces_sampler_arg(&self) -> &str;
30}
31
32// Defaults mirror `cognee-lib::Settings::default()` (config.rs lines 644-651).
33// Kept here so callers that don't depend on `cognee-lib` (e.g. `cognee-http-server`)
34// still get the same defaults; a unit test below pins the two views together.
35pub(crate) const DEFAULT_TRACING_ENABLED: bool = false;
36pub(crate) const DEFAULT_SERVICE_NAME: &str = "cognee";
37pub(crate) const DEFAULT_OTLP_ENDPOINT: &str = "";
38pub(crate) const DEFAULT_OTLP_HEADERS: &str = "";
39pub(crate) const DEFAULT_OTLP_PROTOCOL: &str = "grpc";
40pub(crate) const DEFAULT_SPAN_PROCESSOR: &str = "batch";
41pub(crate) const DEFAULT_TRACES_SAMPLER: &str = "";
42pub(crate) const DEFAULT_TRACES_SAMPLER_ARG: &str = "";
43
44/// Snapshot of OTEL-relevant env vars usable as a [`SettingsView`].
45///
46/// Lets crates that don't depend on `cognee-lib::Settings` (e.g. the HTTP
47/// server) drive [`crate::init_telemetry`] directly from the environment.
48#[derive(Debug, Clone)]
49pub struct EnvSettingsView {
50    tracing_enabled: bool,
51    service_name: String,
52    otlp_endpoint: String,
53    otlp_headers: String,
54    otlp_protocol: String,
55    span_processor: String,
56    traces_sampler: String,
57    traces_sampler_arg: String,
58}
59
60impl Default for EnvSettingsView {
61    fn default() -> Self {
62        Self {
63            tracing_enabled: DEFAULT_TRACING_ENABLED,
64            service_name: DEFAULT_SERVICE_NAME.to_string(),
65            otlp_endpoint: DEFAULT_OTLP_ENDPOINT.to_string(),
66            otlp_headers: DEFAULT_OTLP_HEADERS.to_string(),
67            otlp_protocol: DEFAULT_OTLP_PROTOCOL.to_string(),
68            span_processor: DEFAULT_SPAN_PROCESSOR.to_string(),
69            traces_sampler: DEFAULT_TRACES_SAMPLER.to_string(),
70            traces_sampler_arg: DEFAULT_TRACES_SAMPLER_ARG.to_string(),
71        }
72    }
73}
74
75impl EnvSettingsView {
76    /// Read all eight OTEL env vars. Missing or empty vars fall back to
77    /// the same defaults as `cognee-lib::Settings::default()`.
78    pub fn from_env() -> Self {
79        let mut view = Self::default();
80
81        if let Some(v) = read_env("COGNEE_TRACING_ENABLED") {
82            view.tracing_enabled = cognee_utils::parse_env_bool(&v);
83        }
84        if let Some(v) = read_env("OTEL_SERVICE_NAME") {
85            view.service_name = v;
86        }
87        if let Some(v) = read_env("OTEL_EXPORTER_OTLP_ENDPOINT") {
88            view.otlp_endpoint = v;
89        }
90        if let Some(v) = read_env("OTEL_EXPORTER_OTLP_HEADERS") {
91            view.otlp_headers = v;
92        }
93        if let Some(v) = read_env("OTEL_EXPORTER_OTLP_PROTOCOL") {
94            view.otlp_protocol = v;
95        }
96        if let Some(v) = read_env("OTEL_SPAN_PROCESSOR") {
97            view.span_processor = v;
98        }
99        if let Some(v) = read_env("OTEL_TRACES_SAMPLER") {
100            view.traces_sampler = v;
101        }
102        if let Some(v) = read_env("OTEL_TRACES_SAMPLER_ARG") {
103            view.traces_sampler_arg = v;
104        }
105
106        view
107    }
108}
109
110// Empty string is treated as "unset" so callers can clear an env var via
111// `KEY=` without flipping the value to a literal empty string mid-process.
112fn read_env(key: &str) -> Option<String> {
113    match std::env::var(key) {
114        Ok(v) if !v.is_empty() => Some(v),
115        _ => None,
116    }
117}
118
119impl SettingsView for EnvSettingsView {
120    fn tracing_enabled(&self) -> bool {
121        self.tracing_enabled
122    }
123    fn service_name(&self) -> &str {
124        &self.service_name
125    }
126    fn otlp_endpoint(&self) -> &str {
127        &self.otlp_endpoint
128    }
129    fn otlp_headers(&self) -> &str {
130        &self.otlp_headers
131    }
132    fn otlp_protocol(&self) -> &str {
133        &self.otlp_protocol
134    }
135    fn span_processor(&self) -> &str {
136        &self.span_processor
137    }
138    fn traces_sampler(&self) -> &str {
139        &self.traces_sampler
140    }
141    fn traces_sampler_arg(&self) -> &str {
142        &self.traces_sampler_arg
143    }
144}
145
146#[cfg(test)]
147#[allow(
148    clippy::expect_used,
149    clippy::unwrap_used,
150    reason = "test code — panics are acceptable failures"
151)]
152mod tests {
153    use super::*;
154    use std::sync::Mutex;
155
156    // Env-mutating tests share process state; serialize them to avoid
157    // cross-test interference.
158    static ENV_LOCK: Mutex<()> = Mutex::new(());
159
160    fn clear_otel_env() {
161        for key in [
162            "COGNEE_TRACING_ENABLED",
163            "OTEL_SERVICE_NAME",
164            "OTEL_EXPORTER_OTLP_ENDPOINT",
165            "OTEL_EXPORTER_OTLP_HEADERS",
166            "OTEL_EXPORTER_OTLP_PROTOCOL",
167            "OTEL_SPAN_PROCESSOR",
168            "OTEL_TRACES_SAMPLER",
169            "OTEL_TRACES_SAMPLER_ARG",
170        ] {
171            // Safety: tests serialize via ENV_LOCK; no other thread reads
172            // the env concurrently here.
173            unsafe { std::env::remove_var(key) };
174        }
175    }
176
177    #[test]
178    fn from_env_empty_matches_defaults() {
179        let _g = ENV_LOCK.lock().expect("env lock poisoned");
180        clear_otel_env();
181
182        let view = EnvSettingsView::from_env();
183        assert!(!view.tracing_enabled());
184        assert_eq!(view.service_name(), DEFAULT_SERVICE_NAME);
185        assert_eq!(view.otlp_endpoint(), DEFAULT_OTLP_ENDPOINT);
186        assert_eq!(view.otlp_headers(), DEFAULT_OTLP_HEADERS);
187        assert_eq!(view.otlp_protocol(), DEFAULT_OTLP_PROTOCOL);
188        assert_eq!(view.span_processor(), DEFAULT_SPAN_PROCESSOR);
189        assert_eq!(view.traces_sampler(), DEFAULT_TRACES_SAMPLER);
190        assert_eq!(view.traces_sampler_arg(), DEFAULT_TRACES_SAMPLER_ARG);
191    }
192
193    #[test]
194    fn tracing_enabled_truthy_values() {
195        let _g = ENV_LOCK.lock().expect("env lock poisoned");
196        for v in ["true", "TRUE", "1", "yes", "YES"] {
197            clear_otel_env();
198            unsafe { std::env::set_var("COGNEE_TRACING_ENABLED", v) };
199            let view = EnvSettingsView::from_env();
200            assert!(view.tracing_enabled(), "{v} should enable tracing");
201        }
202    }
203
204    #[test]
205    fn tracing_enabled_falsy_values() {
206        let _g = ENV_LOCK.lock().expect("env lock poisoned");
207        for v in ["false", "0", "no", "off", "anything-else"] {
208            clear_otel_env();
209            unsafe { std::env::set_var("COGNEE_TRACING_ENABLED", v) };
210            let view = EnvSettingsView::from_env();
211            assert!(!view.tracing_enabled(), "{v} should not enable tracing");
212        }
213    }
214
215    #[test]
216    fn from_env_reads_all_fields() {
217        let _g = ENV_LOCK.lock().expect("env lock poisoned");
218        clear_otel_env();
219        unsafe {
220            std::env::set_var("COGNEE_TRACING_ENABLED", "true");
221            std::env::set_var("OTEL_SERVICE_NAME", "my-svc");
222            std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", "http://collector:4317");
223            std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", "x-key=val");
224            std::env::set_var("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf");
225            std::env::set_var("OTEL_SPAN_PROCESSOR", "simple");
226            std::env::set_var("OTEL_TRACES_SAMPLER", "traceidratio");
227            std::env::set_var("OTEL_TRACES_SAMPLER_ARG", "0.5");
228        }
229
230        let view = EnvSettingsView::from_env();
231        assert!(view.tracing_enabled());
232        assert_eq!(view.service_name(), "my-svc");
233        assert_eq!(view.otlp_endpoint(), "http://collector:4317");
234        assert_eq!(view.otlp_headers(), "x-key=val");
235        assert_eq!(view.otlp_protocol(), "http/protobuf");
236        assert_eq!(view.span_processor(), "simple");
237        assert_eq!(view.traces_sampler(), "traceidratio");
238        assert_eq!(view.traces_sampler_arg(), "0.5");
239
240        clear_otel_env();
241    }
242}