provide_telemetry/
runtime.rs1use std::sync::{OnceLock, RwLock};
7
8use crate::config::TelemetryConfig;
9use crate::errors::TelemetryError;
10use crate::otel::otel_installed;
11use crate::RuntimeOverrides;
12
13static ACTIVE_CONFIG: OnceLock<RwLock<Option<TelemetryConfig>>> = OnceLock::new();
14
15fn active_config() -> &'static RwLock<Option<TelemetryConfig>> {
16 ACTIVE_CONFIG.get_or_init(|| RwLock::new(None))
17}
18
19pub(crate) fn set_active_config(config: Option<TelemetryConfig>) {
20 *active_config()
21 .write()
22 .expect("runtime config lock poisoned") = config;
23}
24
25pub(crate) fn provider_config_changed(current: &TelemetryConfig, target: &TelemetryConfig) -> bool {
26 current.service_name != target.service_name
27 || current.environment != target.environment
28 || current.version != target.version
29 || current.logging.otlp_headers != target.logging.otlp_headers
30 || current.tracing != target.tracing
31 || current.metrics != target.metrics
32}
33
34pub fn get_runtime_config() -> Option<TelemetryConfig> {
35 active_config()
36 .read()
37 .expect("runtime config lock poisoned")
38 .clone()
39}
40
41pub fn update_runtime_config(
42 overrides: RuntimeOverrides,
43) -> Result<TelemetryConfig, TelemetryError> {
44 let mut guard = active_config()
45 .write()
46 .expect("runtime config lock poisoned");
47 let current = guard
48 .as_ref()
49 .cloned()
50 .ok_or_else(|| TelemetryError::new("telemetry not set up: call setup_telemetry first"))?;
51
52 let mut next = current;
53 if let Some(sampling) = overrides.sampling {
54 next.sampling = sampling;
55 }
56 if let Some(backpressure) = overrides.backpressure {
57 next.backpressure = backpressure;
58 }
59 if let Some(exporter) = overrides.exporter {
60 next.exporter = exporter;
61 }
62 if let Some(security) = overrides.security {
63 next.security = security;
64 }
65 if let Some(slo) = overrides.slo {
66 next.slo = slo;
67 }
68 if let Some(pii_max_depth) = overrides.pii_max_depth {
69 next.pii_max_depth = pii_max_depth;
70 }
71 if let Some(strict_schema) = overrides.strict_schema {
72 next.strict_schema = strict_schema;
73 }
74
75 *guard = Some(next.clone());
76 Ok(next)
77}
78
79pub fn reload_runtime_from_env() -> Result<TelemetryConfig, TelemetryError> {
80 let fresh = TelemetryConfig::from_env().map_err(|err| TelemetryError::new(err.message))?;
81 let current = get_runtime_config()
82 .ok_or_else(|| TelemetryError::new("telemetry not set up: call setup_telemetry first"))?;
83
84 let overrides = RuntimeOverrides {
85 sampling: Some(fresh.sampling),
86 backpressure: Some(fresh.backpressure),
87 exporter: Some(fresh.exporter),
88 security: Some(fresh.security),
89 slo: Some(fresh.slo),
90 pii_max_depth: Some(fresh.pii_max_depth),
91 strict_schema: Some(fresh.strict_schema),
92 };
93
94 let mut next = update_runtime_config(overrides)?;
95 next.service_name = current.service_name;
96 next.environment = current.environment;
97 next.version = current.version;
98 next.logging.level = current.logging.level;
99 next.logging.fmt = current.logging.fmt;
100 next.logging.otlp_headers = current.logging.otlp_headers;
101 next.tracing.enabled = current.tracing.enabled;
102 next.tracing.otlp_headers = current.tracing.otlp_headers;
103 next.metrics.enabled = current.metrics.enabled;
104 next.metrics.otlp_headers = current.metrics.otlp_headers;
105
106 set_active_config(Some(next.clone()));
107 Ok(next)
108}
109
110pub fn reconfigure_telemetry(
111 config: Option<TelemetryConfig>,
112) -> Result<TelemetryConfig, TelemetryError> {
113 let target = match config {
114 Some(config) => config,
115 None => TelemetryConfig::from_env().map_err(|err| TelemetryError::new(err.message))?,
116 };
117
118 if let Some(current) = get_runtime_config() {
119 if otel_installed() && provider_config_changed(¤t, &target) {
120 return Err(TelemetryError::new(
121 "OpenTelemetry providers already installed; restart the process for provider-changing config",
122 ));
123 }
124 }
125
126 set_active_config(Some(target.clone()));
127 Ok(target)
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn runtime_test_provider_config_changed_detects_each_provider_field() {
136 let current = TelemetryConfig::default();
137
138 let mut changed = current.clone();
139 changed.service_name = "svc-2".to_string();
140 assert!(provider_config_changed(¤t, &changed));
141
142 let mut changed = current.clone();
143 changed.environment = "prod".to_string();
144 assert!(provider_config_changed(¤t, &changed));
145
146 let mut changed = current.clone();
147 changed.version = "1.2.3".to_string();
148 assert!(provider_config_changed(¤t, &changed));
149
150 let mut changed = current.clone();
151 changed.logging.otlp_headers.insert("x".into(), "1".into());
152 assert!(provider_config_changed(¤t, &changed));
153
154 let mut changed = current.clone();
155 changed.tracing.enabled = !changed.tracing.enabled;
156 assert!(provider_config_changed(¤t, &changed));
157
158 let mut changed = current.clone();
159 changed.metrics.enabled = !changed.metrics.enabled;
160 assert!(provider_config_changed(¤t, &changed));
161 }
162
163 #[test]
164 fn runtime_test_provider_config_changed_is_false_for_hot_only_changes() {
165 let current = TelemetryConfig::default();
166 let mut changed = current.clone();
167 changed.sampling.logs_rate = 0.25;
168 changed.backpressure.logs_maxsize = 9;
169 changed.exporter.logs_retries = 2;
170 changed.security.max_attr_count = 99;
171 changed.slo.enable_red_metrics = !changed.slo.enable_red_metrics;
172 changed.pii_max_depth = 3;
173 changed.strict_schema = true;
174
175 assert!(!provider_config_changed(¤t, &changed));
176 }
177}