provide_telemetry/
setup.rs1use std::sync::{Mutex, OnceLock};
7
8use crate::config::TelemetryConfig;
9use crate::errors::TelemetryError;
10use crate::otel::{flush_otel, setup_otel, shutdown_otel};
11use crate::policies::apply_policies;
12use crate::runtime::{get_runtime_config, set_active_config};
13
14#[derive(Clone, Copy, Debug, Default)]
15struct SetupState {
16 done: bool,
17}
18
19static SETUP_STATE: OnceLock<Mutex<SetupState>> = OnceLock::new();
20
21#[cfg_attr(test, mutants::skip)] fn default_setup_state_mutex() -> Mutex<SetupState> {
23 Mutex::new(SetupState::default())
24}
25
26fn setup_state() -> &'static Mutex<SetupState> {
27 SETUP_STATE.get_or_init(default_setup_state_mutex)
28}
29
30pub fn setup_telemetry() -> Result<TelemetryConfig, TelemetryError> {
31 let mut state = crate::_lock::lock(setup_state());
32 if state.done {
33 return get_runtime_config()
34 .ok_or_else(|| TelemetryError::new("telemetry setup state is inconsistent"));
35 }
36
37 let config = TelemetryConfig::from_env().map_err(|err| TelemetryError::new(err.message))?;
38 setup_otel(&config)?;
39 apply_policies(&config);
40 set_active_config(Some(config.clone()));
41 state.done = true;
42 Ok(config)
43}
44
45pub fn flush_telemetry() -> Result<(), TelemetryError> {
58 if flush_otel() {
59 return Ok(());
60 }
61 Err(TelemetryError::new(
62 "telemetry flush exceeded its deadline; records may not have been exported",
63 ))
64}
65
66pub fn shutdown_telemetry() -> Result<(), TelemetryError> {
67 {
68 let mut state = crate::_lock::lock(setup_state());
69 state.done = false;
70 }
71 shutdown_otel();
72 set_active_config(None);
73 Ok(())
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 use crate::testing::acquire_test_state_lock;
81
82 #[test]
83 fn flush_is_ok_when_nothing_is_installed() {
84 let _guard = acquire_test_state_lock();
85 shutdown_telemetry().expect("pre-test shutdown should succeed");
86
87 flush_telemetry().expect("flush with no providers should succeed");
90 }
91
92 #[test]
93 fn flush_leaves_telemetry_set_up_and_repeatable() {
94 let _guard = acquire_test_state_lock();
95 shutdown_telemetry().expect("pre-test shutdown should succeed");
96 let config = setup_telemetry().expect("setup should succeed");
97
98 flush_telemetry().expect("first flush should succeed");
99 flush_telemetry().expect("second flush should succeed");
100
101 assert_eq!(
103 get_runtime_config().expect("runtime config should survive a flush"),
104 config
105 );
106 shutdown_telemetry().expect("shutdown should succeed");
107 }
108
109 #[test]
110 fn setup_test_round_trip_sets_and_clears_runtime_state() {
111 let _guard = acquire_test_state_lock();
112 shutdown_telemetry().expect("pre-test shutdown should succeed");
113
114 let config = setup_telemetry().expect("setup should succeed");
115 assert_eq!(
116 get_runtime_config().expect("runtime config should exist"),
117 config
118 );
119 assert!(crate::_lock::lock(setup_state()).done);
120
121 shutdown_telemetry().expect("shutdown should succeed");
122 assert!(get_runtime_config().is_none());
123 assert!(!crate::_lock::lock(setup_state()).done);
124 }
125
126 #[test]
127 fn setup_test_repeated_setup_returns_existing_runtime_config() {
128 let _guard = acquire_test_state_lock();
129 shutdown_telemetry().expect("pre-test shutdown should succeed");
130
131 let first = setup_telemetry().expect("first setup should succeed");
132 let second = setup_telemetry().expect("second setup should return existing config");
133
134 assert_eq!(first, second);
135 shutdown_telemetry().expect("shutdown should succeed");
136 }
137
138 #[test]
139 fn setup_test_inconsistent_done_state_returns_error() {
140 let _guard = acquire_test_state_lock();
141 shutdown_telemetry().expect("pre-test shutdown should succeed");
142 set_active_config(None);
143 crate::_lock::lock(setup_state()).done = true;
144
145 let err = setup_telemetry().expect_err("inconsistent state must fail");
146 assert!(
147 err.message.contains("inconsistent"),
148 "unexpected error: {}",
149 err.message
150 );
151
152 crate::_lock::lock(setup_state()).done = false;
153 }
154
155 #[test]
156 fn setup_test_invalid_env_surfaces_parse_error() {
157 let _guard = acquire_test_state_lock();
158 shutdown_telemetry().expect("pre-test shutdown should succeed");
159 std::env::set_var("PROVIDE_LOG_INCLUDE_TIMESTAMP", "not-a-bool");
160
161 let err = setup_telemetry().expect_err("invalid env must fail setup");
162 assert!(err.message.contains("PROVIDE_LOG_INCLUDE_TIMESTAMP"));
163
164 std::env::remove_var("PROVIDE_LOG_INCLUDE_TIMESTAMP");
165 }
166
167 #[cfg(feature = "otel")]
168 #[test]
169 fn setup_test_invalid_otel_endpoint_surfaces_setup_error() {
170 let _guard = acquire_test_state_lock();
171 shutdown_telemetry().expect("pre-test shutdown should succeed");
172 std::env::set_var("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "ftp://collector:4318");
173 std::env::set_var("PROVIDE_EXPORTER_LOGS_FAIL_OPEN", "false");
174
175 let err = setup_telemetry().expect_err("invalid OTEL endpoint must fail setup");
176 assert!(err.message.contains("scheme"));
177
178 std::env::remove_var("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT");
179 std::env::remove_var("PROVIDE_EXPORTER_LOGS_FAIL_OPEN");
180 }
181}