Skip to main content

provide_telemetry/
setup.rs

1// SPDX-FileCopyrightText: Copyright (C) 2026 provide.io llc
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-Comment: Part of provide-telemetry.
4//
5
6use std::sync::{Mutex, OnceLock};
7
8use crate::config::TelemetryConfig;
9use crate::errors::TelemetryError;
10use crate::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)] // Equivalent mutants only swap in Mutex::default().
22fn 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 shutdown_telemetry() -> Result<(), TelemetryError> {
46    {
47        let mut state = crate::_lock::lock(setup_state());
48        state.done = false;
49    }
50    shutdown_otel();
51    set_active_config(None);
52    Ok(())
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    use crate::testing::acquire_test_state_lock;
60
61    #[test]
62    fn setup_test_round_trip_sets_and_clears_runtime_state() {
63        let _guard = acquire_test_state_lock();
64        shutdown_telemetry().expect("pre-test shutdown should succeed");
65
66        let config = setup_telemetry().expect("setup should succeed");
67        assert_eq!(
68            get_runtime_config().expect("runtime config should exist"),
69            config
70        );
71        assert!(crate::_lock::lock(setup_state()).done);
72
73        shutdown_telemetry().expect("shutdown should succeed");
74        assert!(get_runtime_config().is_none());
75        assert!(!crate::_lock::lock(setup_state()).done);
76    }
77
78    #[test]
79    fn setup_test_repeated_setup_returns_existing_runtime_config() {
80        let _guard = acquire_test_state_lock();
81        shutdown_telemetry().expect("pre-test shutdown should succeed");
82
83        let first = setup_telemetry().expect("first setup should succeed");
84        let second = setup_telemetry().expect("second setup should return existing config");
85
86        assert_eq!(first, second);
87        shutdown_telemetry().expect("shutdown should succeed");
88    }
89
90    #[test]
91    fn setup_test_inconsistent_done_state_returns_error() {
92        let _guard = acquire_test_state_lock();
93        shutdown_telemetry().expect("pre-test shutdown should succeed");
94        set_active_config(None);
95        crate::_lock::lock(setup_state()).done = true;
96
97        let err = setup_telemetry().expect_err("inconsistent state must fail");
98        assert!(
99            err.message.contains("inconsistent"),
100            "unexpected error: {}",
101            err.message
102        );
103
104        crate::_lock::lock(setup_state()).done = false;
105    }
106
107    #[test]
108    fn setup_test_invalid_env_surfaces_parse_error() {
109        let _guard = acquire_test_state_lock();
110        shutdown_telemetry().expect("pre-test shutdown should succeed");
111        std::env::set_var("PROVIDE_LOG_INCLUDE_TIMESTAMP", "not-a-bool");
112
113        let err = setup_telemetry().expect_err("invalid env must fail setup");
114        assert!(err.message.contains("PROVIDE_LOG_INCLUDE_TIMESTAMP"));
115
116        std::env::remove_var("PROVIDE_LOG_INCLUDE_TIMESTAMP");
117    }
118
119    #[cfg(feature = "otel")]
120    #[test]
121    fn setup_test_invalid_otel_endpoint_surfaces_setup_error() {
122        let _guard = acquire_test_state_lock();
123        shutdown_telemetry().expect("pre-test shutdown should succeed");
124        std::env::set_var("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "ftp://collector:4318");
125        std::env::set_var("PROVIDE_EXPORTER_LOGS_FAIL_OPEN", "false");
126
127        let err = setup_telemetry().expect_err("invalid OTEL endpoint must fail setup");
128        assert!(err.message.contains("scheme"));
129
130        std::env::remove_var("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT");
131        std::env::remove_var("PROVIDE_EXPORTER_LOGS_FAIL_OPEN");
132    }
133}