Skip to main content

provide_telemetry/config/
from_env.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
6//! Environment-variable parsing for `TelemetryConfig`. Split out of
7//! `config/mod.rs` so the file stays under the 500-LOC ceiling.
8
9use std::collections::HashMap;
10use std::env;
11
12use super::parse::{
13    env_value, nonempty_env_value, parse_bool, parse_module_levels, parse_non_negative_float,
14    parse_otlp_headers, parse_rate, parse_usize,
15};
16use super::{
17    BackpressureConfig, EventSchemaConfig, ExporterPolicyConfig, LoggingConfig, MetricsConfig,
18    SLOConfig, SamplingConfig, SecurityConfig, TelemetryConfig, TracingConfig,
19};
20use crate::errors::ConfigurationError;
21
22impl TelemetryConfig {
23    pub fn from_env() -> Result<Self, ConfigurationError> {
24        let env_map = env::vars().collect::<HashMap<_, _>>();
25        Self::from_map(&env_map)
26    }
27
28    pub fn from_map(env: &HashMap<String, String>) -> Result<Self, ConfigurationError> {
29        let shared_headers =
30            parse_otlp_headers(env_value(env, &["OTEL_EXPORTER_OTLP_HEADERS"])).unwrap_or_default();
31        let shared_endpoint = nonempty_env_value(env, &["OTEL_EXPORTER_OTLP_ENDPOINT"]);
32        let shared_protocol = env_value(env, &["OTEL_EXPORTER_OTLP_PROTOCOL"]).unwrap_or("");
33        // Per the OTLP/HTTP spec, when falling back to the shared endpoint
34        // the per-signal path must be appended (/v1/traces, /v1/metrics,
35        // /v1/logs). Signal-specific endpoint env vars are used verbatim.
36        let with_signal_path = |signal_path: &str| -> Option<String> {
37            shared_endpoint.map(|base| format!("{}/{}", base.trim_end_matches('/'), signal_path))
38        };
39
40        Ok(Self {
41            service_name: env_value(env, &["PROVIDE_TELEMETRY_SERVICE_NAME"])
42                .unwrap_or("provide-service")
43                .to_string(),
44            environment: env_value(env, &["PROVIDE_TELEMETRY_ENV", "PROVIDE_ENV"])
45                .unwrap_or("dev")
46                .to_string(),
47            version: env_value(env, &["PROVIDE_TELEMETRY_VERSION", "PROVIDE_VERSION"])
48                .unwrap_or("0.0.0")
49                .to_string(),
50            strict_schema: parse_bool(
51                env_value(env, &["PROVIDE_TELEMETRY_STRICT_SCHEMA"]),
52                false,
53                "PROVIDE_TELEMETRY_STRICT_SCHEMA",
54            )?,
55            pii_max_depth: parse_usize(
56                env_value(env, &["PROVIDE_LOG_PII_MAX_DEPTH"]),
57                8,
58                "PROVIDE_LOG_PII_MAX_DEPTH",
59            )?,
60            logging: LoggingConfig {
61                level: env_value(env, &["PROVIDE_LOG_LEVEL"])
62                    .unwrap_or("INFO")
63                    .to_string(),
64                fmt: env_value(env, &["PROVIDE_LOG_FORMAT"])
65                    .unwrap_or("console")
66                    .to_string(),
67                include_timestamp: parse_bool(
68                    env_value(env, &["PROVIDE_LOG_INCLUDE_TIMESTAMP"]),
69                    true,
70                    "PROVIDE_LOG_INCLUDE_TIMESTAMP",
71                )?,
72                otlp_headers: parse_otlp_headers(env_value(
73                    env,
74                    &["OTEL_EXPORTER_OTLP_LOGS_HEADERS"],
75                ))
76                .unwrap_or_else(|| shared_headers.clone()),
77                otlp_endpoint: nonempty_env_value(env, &["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"])
78                    .map(str::to_string)
79                    .or_else(|| with_signal_path("v1/logs")),
80                otlp_protocol: env_value(env, &["OTEL_EXPORTER_OTLP_LOGS_PROTOCOL"])
81                    .unwrap_or(shared_protocol)
82                    .to_string(),
83                module_levels: parse_module_levels(
84                    env_value(env, &["PROVIDE_LOG_MODULE_LEVELS"]).unwrap_or(""),
85                ),
86            },
87            tracing: TracingConfig {
88                enabled: parse_bool(
89                    env_value(env, &["PROVIDE_TRACE_ENABLED"]),
90                    true,
91                    "PROVIDE_TRACE_ENABLED",
92                )?,
93                sample_rate: parse_rate(
94                    env_value(env, &["PROVIDE_TRACE_SAMPLE_RATE"]),
95                    1.0,
96                    "PROVIDE_TRACE_SAMPLE_RATE",
97                )?,
98                otlp_headers: parse_otlp_headers(env_value(
99                    env,
100                    &["OTEL_EXPORTER_OTLP_TRACES_HEADERS"],
101                ))
102                .unwrap_or_else(|| shared_headers.clone()),
103                otlp_endpoint: nonempty_env_value(env, &["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"])
104                    .map(str::to_string)
105                    .or_else(|| with_signal_path("v1/traces")),
106                otlp_protocol: env_value(env, &["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"])
107                    .unwrap_or(shared_protocol)
108                    .to_string(),
109            },
110            metrics: MetricsConfig {
111                enabled: parse_bool(
112                    env_value(env, &["PROVIDE_METRICS_ENABLED"]),
113                    true,
114                    "PROVIDE_METRICS_ENABLED",
115                )?,
116                otlp_headers: parse_otlp_headers(env_value(
117                    env,
118                    &["OTEL_EXPORTER_OTLP_METRICS_HEADERS"],
119                ))
120                .unwrap_or(shared_headers),
121                otlp_endpoint: nonempty_env_value(env, &["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"])
122                    .map(str::to_string)
123                    .or_else(|| with_signal_path("v1/metrics")),
124                otlp_protocol: env_value(env, &["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"])
125                    .unwrap_or(shared_protocol)
126                    .to_string(),
127                metric_export_interval_ms: parse_usize(
128                    env_value(env, &["OTEL_METRIC_EXPORT_INTERVAL"]),
129                    60_000,
130                    "OTEL_METRIC_EXPORT_INTERVAL",
131                )? as u64,
132            },
133            event_schema: EventSchemaConfig {
134                strict_event_name: parse_bool(
135                    env_value(env, &["PROVIDE_TELEMETRY_STRICT_EVENT_NAME"]),
136                    false,
137                    "PROVIDE_TELEMETRY_STRICT_EVENT_NAME",
138                )?,
139                required_keys: env_value(env, &["PROVIDE_TELEMETRY_REQUIRED_KEYS"])
140                    .unwrap_or("")
141                    .split(',')
142                    .map(str::trim)
143                    .filter(|value| !value.is_empty())
144                    .map(str::to_string)
145                    .collect(),
146            },
147            sampling: SamplingConfig {
148                logs_rate: parse_rate(
149                    env_value(env, &["PROVIDE_SAMPLING_LOGS_RATE"]),
150                    1.0,
151                    "PROVIDE_SAMPLING_LOGS_RATE",
152                )?,
153                traces_rate: parse_rate(
154                    env_value(env, &["PROVIDE_SAMPLING_TRACES_RATE"]),
155                    1.0,
156                    "PROVIDE_SAMPLING_TRACES_RATE",
157                )?,
158                metrics_rate: parse_rate(
159                    env_value(env, &["PROVIDE_SAMPLING_METRICS_RATE"]),
160                    1.0,
161                    "PROVIDE_SAMPLING_METRICS_RATE",
162                )?,
163            },
164            backpressure: BackpressureConfig {
165                logs_maxsize: parse_usize(
166                    env_value(env, &["PROVIDE_BACKPRESSURE_LOGS_MAXSIZE"]),
167                    0,
168                    "PROVIDE_BACKPRESSURE_LOGS_MAXSIZE",
169                )?,
170                traces_maxsize: parse_usize(
171                    env_value(env, &["PROVIDE_BACKPRESSURE_TRACES_MAXSIZE"]),
172                    0,
173                    "PROVIDE_BACKPRESSURE_TRACES_MAXSIZE",
174                )?,
175                metrics_maxsize: parse_usize(
176                    env_value(env, &["PROVIDE_BACKPRESSURE_METRICS_MAXSIZE"]),
177                    0,
178                    "PROVIDE_BACKPRESSURE_METRICS_MAXSIZE",
179                )?,
180            },
181            exporter: ExporterPolicyConfig {
182                logs_retries: parse_usize(
183                    env_value(env, &["PROVIDE_EXPORTER_LOGS_RETRIES"]),
184                    0,
185                    "PROVIDE_EXPORTER_LOGS_RETRIES",
186                )?,
187                traces_retries: parse_usize(
188                    env_value(env, &["PROVIDE_EXPORTER_TRACES_RETRIES"]),
189                    0,
190                    "PROVIDE_EXPORTER_TRACES_RETRIES",
191                )?,
192                metrics_retries: parse_usize(
193                    env_value(env, &["PROVIDE_EXPORTER_METRICS_RETRIES"]),
194                    0,
195                    "PROVIDE_EXPORTER_METRICS_RETRIES",
196                )?,
197                logs_backoff_seconds: parse_non_negative_float(
198                    env_value(env, &["PROVIDE_EXPORTER_LOGS_BACKOFF_SECONDS"]),
199                    0.0,
200                    "PROVIDE_EXPORTER_LOGS_BACKOFF_SECONDS",
201                )?,
202                traces_backoff_seconds: parse_non_negative_float(
203                    env_value(env, &["PROVIDE_EXPORTER_TRACES_BACKOFF_SECONDS"]),
204                    0.0,
205                    "PROVIDE_EXPORTER_TRACES_BACKOFF_SECONDS",
206                )?,
207                metrics_backoff_seconds: parse_non_negative_float(
208                    env_value(env, &["PROVIDE_EXPORTER_METRICS_BACKOFF_SECONDS"]),
209                    0.0,
210                    "PROVIDE_EXPORTER_METRICS_BACKOFF_SECONDS",
211                )?,
212                logs_timeout_seconds: parse_non_negative_float(
213                    env_value(env, &["PROVIDE_EXPORTER_LOGS_TIMEOUT_SECONDS"]),
214                    10.0,
215                    "PROVIDE_EXPORTER_LOGS_TIMEOUT_SECONDS",
216                )?,
217                traces_timeout_seconds: parse_non_negative_float(
218                    env_value(env, &["PROVIDE_EXPORTER_TRACES_TIMEOUT_SECONDS"]),
219                    10.0,
220                    "PROVIDE_EXPORTER_TRACES_TIMEOUT_SECONDS",
221                )?,
222                metrics_timeout_seconds: parse_non_negative_float(
223                    env_value(env, &["PROVIDE_EXPORTER_METRICS_TIMEOUT_SECONDS"]),
224                    10.0,
225                    "PROVIDE_EXPORTER_METRICS_TIMEOUT_SECONDS",
226                )?,
227                logs_fail_open: parse_bool(
228                    env_value(env, &["PROVIDE_EXPORTER_LOGS_FAIL_OPEN"]),
229                    true,
230                    "PROVIDE_EXPORTER_LOGS_FAIL_OPEN",
231                )?,
232                traces_fail_open: parse_bool(
233                    env_value(env, &["PROVIDE_EXPORTER_TRACES_FAIL_OPEN"]),
234                    true,
235                    "PROVIDE_EXPORTER_TRACES_FAIL_OPEN",
236                )?,
237                metrics_fail_open: parse_bool(
238                    env_value(env, &["PROVIDE_EXPORTER_METRICS_FAIL_OPEN"]),
239                    true,
240                    "PROVIDE_EXPORTER_METRICS_FAIL_OPEN",
241                )?,
242            },
243            slo: SLOConfig {
244                enable_red_metrics: parse_bool(
245                    env_value(env, &["PROVIDE_SLO_ENABLE_RED_METRICS"]),
246                    false,
247                    "PROVIDE_SLO_ENABLE_RED_METRICS",
248                )?,
249                enable_use_metrics: parse_bool(
250                    env_value(env, &["PROVIDE_SLO_ENABLE_USE_METRICS"]),
251                    false,
252                    "PROVIDE_SLO_ENABLE_USE_METRICS",
253                )?,
254            },
255            security: SecurityConfig {
256                max_attr_value_length: parse_usize(
257                    env_value(env, &["PROVIDE_SECURITY_MAX_ATTR_VALUE_LENGTH"]),
258                    1024,
259                    "PROVIDE_SECURITY_MAX_ATTR_VALUE_LENGTH",
260                )?,
261                max_attr_count: parse_usize(
262                    env_value(env, &["PROVIDE_SECURITY_MAX_ATTR_COUNT"]),
263                    64,
264                    "PROVIDE_SECURITY_MAX_ATTR_COUNT",
265                )?,
266                max_nesting_depth: parse_usize(
267                    env_value(env, &["PROVIDE_SECURITY_MAX_NESTING_DEPTH"]),
268                    8,
269                    "PROVIDE_SECURITY_MAX_NESTING_DEPTH",
270                )?,
271            },
272        })
273    }
274}
275
276#[cfg(test)]
277#[path = "from_env_tests.rs"]
278mod tests;