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_retries, 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_enabled: parse_bool(
81                    env_value(env, &["PROVIDE_LOG_OTLP_ENABLED"]),
82                    true,
83                    "PROVIDE_LOG_OTLP_ENABLED",
84                )?,
85                otlp_protocol: env_value(env, &["OTEL_EXPORTER_OTLP_LOGS_PROTOCOL"])
86                    .unwrap_or(shared_protocol)
87                    .to_string(),
88                module_levels: parse_module_levels(
89                    env_value(env, &["PROVIDE_LOG_MODULE_LEVELS"]).unwrap_or(""),
90                ),
91            },
92            tracing: TracingConfig {
93                enabled: parse_bool(
94                    env_value(env, &["PROVIDE_TRACE_ENABLED"]),
95                    true,
96                    "PROVIDE_TRACE_ENABLED",
97                )?,
98                sample_rate: parse_rate(
99                    env_value(env, &["PROVIDE_TRACE_SAMPLE_RATE"]),
100                    1.0,
101                    "PROVIDE_TRACE_SAMPLE_RATE",
102                )?,
103                otlp_headers: parse_otlp_headers(env_value(
104                    env,
105                    &["OTEL_EXPORTER_OTLP_TRACES_HEADERS"],
106                ))
107                .unwrap_or_else(|| shared_headers.clone()),
108                otlp_endpoint: nonempty_env_value(env, &["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"])
109                    .map(str::to_string)
110                    .or_else(|| with_signal_path("v1/traces")),
111                otlp_protocol: env_value(env, &["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"])
112                    .unwrap_or(shared_protocol)
113                    .to_string(),
114            },
115            metrics: MetricsConfig {
116                enabled: parse_bool(
117                    env_value(env, &["PROVIDE_METRICS_ENABLED"]),
118                    true,
119                    "PROVIDE_METRICS_ENABLED",
120                )?,
121                otlp_headers: parse_otlp_headers(env_value(
122                    env,
123                    &["OTEL_EXPORTER_OTLP_METRICS_HEADERS"],
124                ))
125                .unwrap_or(shared_headers),
126                otlp_endpoint: nonempty_env_value(env, &["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"])
127                    .map(str::to_string)
128                    .or_else(|| with_signal_path("v1/metrics")),
129                otlp_protocol: env_value(env, &["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"])
130                    .unwrap_or(shared_protocol)
131                    .to_string(),
132                metric_export_interval_ms: parse_usize(
133                    env_value(env, &["OTEL_METRIC_EXPORT_INTERVAL"]),
134                    60_000,
135                    "OTEL_METRIC_EXPORT_INTERVAL",
136                )? as u64,
137            },
138            event_schema: EventSchemaConfig {
139                strict_event_name: parse_bool(
140                    env_value(env, &["PROVIDE_TELEMETRY_STRICT_EVENT_NAME"]),
141                    false,
142                    "PROVIDE_TELEMETRY_STRICT_EVENT_NAME",
143                )?,
144                required_keys: env_value(env, &["PROVIDE_TELEMETRY_REQUIRED_KEYS"])
145                    .unwrap_or("")
146                    .split(',')
147                    .map(str::trim)
148                    .filter(|value| !value.is_empty())
149                    .map(str::to_string)
150                    .collect(),
151            },
152            sampling: SamplingConfig {
153                logs_rate: parse_rate(
154                    env_value(env, &["PROVIDE_SAMPLING_LOGS_RATE"]),
155                    1.0,
156                    "PROVIDE_SAMPLING_LOGS_RATE",
157                )?,
158                traces_rate: parse_rate(
159                    env_value(env, &["PROVIDE_SAMPLING_TRACES_RATE"]),
160                    1.0,
161                    "PROVIDE_SAMPLING_TRACES_RATE",
162                )?,
163                metrics_rate: parse_rate(
164                    env_value(env, &["PROVIDE_SAMPLING_METRICS_RATE"]),
165                    1.0,
166                    "PROVIDE_SAMPLING_METRICS_RATE",
167                )?,
168            },
169            backpressure: BackpressureConfig {
170                logs_maxsize: parse_usize(
171                    env_value(env, &["PROVIDE_BACKPRESSURE_LOGS_MAXSIZE"]),
172                    0,
173                    "PROVIDE_BACKPRESSURE_LOGS_MAXSIZE",
174                )?,
175                traces_maxsize: parse_usize(
176                    env_value(env, &["PROVIDE_BACKPRESSURE_TRACES_MAXSIZE"]),
177                    0,
178                    "PROVIDE_BACKPRESSURE_TRACES_MAXSIZE",
179                )?,
180                metrics_maxsize: parse_usize(
181                    env_value(env, &["PROVIDE_BACKPRESSURE_METRICS_MAXSIZE"]),
182                    0,
183                    "PROVIDE_BACKPRESSURE_METRICS_MAXSIZE",
184                )?,
185            },
186            exporter: ExporterPolicyConfig {
187                logs_retries: parse_retries(
188                    env_value(env, &["PROVIDE_EXPORTER_LOGS_RETRIES"]),
189                    0,
190                    "PROVIDE_EXPORTER_LOGS_RETRIES",
191                )?,
192                traces_retries: parse_retries(
193                    env_value(env, &["PROVIDE_EXPORTER_TRACES_RETRIES"]),
194                    0,
195                    "PROVIDE_EXPORTER_TRACES_RETRIES",
196                )?,
197                metrics_retries: parse_retries(
198                    env_value(env, &["PROVIDE_EXPORTER_METRICS_RETRIES"]),
199                    0,
200                    "PROVIDE_EXPORTER_METRICS_RETRIES",
201                )?,
202                logs_backoff_seconds: parse_non_negative_float(
203                    env_value(env, &["PROVIDE_EXPORTER_LOGS_BACKOFF_SECONDS"]),
204                    0.0,
205                    "PROVIDE_EXPORTER_LOGS_BACKOFF_SECONDS",
206                )?,
207                traces_backoff_seconds: parse_non_negative_float(
208                    env_value(env, &["PROVIDE_EXPORTER_TRACES_BACKOFF_SECONDS"]),
209                    0.0,
210                    "PROVIDE_EXPORTER_TRACES_BACKOFF_SECONDS",
211                )?,
212                metrics_backoff_seconds: parse_non_negative_float(
213                    env_value(env, &["PROVIDE_EXPORTER_METRICS_BACKOFF_SECONDS"]),
214                    0.0,
215                    "PROVIDE_EXPORTER_METRICS_BACKOFF_SECONDS",
216                )?,
217                logs_timeout_seconds: parse_non_negative_float(
218                    env_value(env, &["PROVIDE_EXPORTER_LOGS_TIMEOUT_SECONDS"]),
219                    10.0,
220                    "PROVIDE_EXPORTER_LOGS_TIMEOUT_SECONDS",
221                )?,
222                traces_timeout_seconds: parse_non_negative_float(
223                    env_value(env, &["PROVIDE_EXPORTER_TRACES_TIMEOUT_SECONDS"]),
224                    10.0,
225                    "PROVIDE_EXPORTER_TRACES_TIMEOUT_SECONDS",
226                )?,
227                metrics_timeout_seconds: parse_non_negative_float(
228                    env_value(env, &["PROVIDE_EXPORTER_METRICS_TIMEOUT_SECONDS"]),
229                    10.0,
230                    "PROVIDE_EXPORTER_METRICS_TIMEOUT_SECONDS",
231                )?,
232                logs_shutdown_timeout_seconds: parse_non_negative_float(
233                    env_value(env, &["PROVIDE_EXPORTER_LOGS_SHUTDOWN_TIMEOUT_SECONDS"]),
234                    5.0,
235                    "PROVIDE_EXPORTER_LOGS_SHUTDOWN_TIMEOUT_SECONDS",
236                )?,
237                logs_fail_open: parse_bool(
238                    env_value(env, &["PROVIDE_EXPORTER_LOGS_FAIL_OPEN"]),
239                    true,
240                    "PROVIDE_EXPORTER_LOGS_FAIL_OPEN",
241                )?,
242                traces_fail_open: parse_bool(
243                    env_value(env, &["PROVIDE_EXPORTER_TRACES_FAIL_OPEN"]),
244                    true,
245                    "PROVIDE_EXPORTER_TRACES_FAIL_OPEN",
246                )?,
247                metrics_fail_open: parse_bool(
248                    env_value(env, &["PROVIDE_EXPORTER_METRICS_FAIL_OPEN"]),
249                    true,
250                    "PROVIDE_EXPORTER_METRICS_FAIL_OPEN",
251                )?,
252            },
253            slo: SLOConfig {
254                enable_red_metrics: parse_bool(
255                    env_value(env, &["PROVIDE_SLO_ENABLE_RED_METRICS"]),
256                    false,
257                    "PROVIDE_SLO_ENABLE_RED_METRICS",
258                )?,
259                enable_use_metrics: parse_bool(
260                    env_value(env, &["PROVIDE_SLO_ENABLE_USE_METRICS"]),
261                    false,
262                    "PROVIDE_SLO_ENABLE_USE_METRICS",
263                )?,
264            },
265            security: SecurityConfig {
266                max_attr_value_length: parse_usize(
267                    env_value(env, &["PROVIDE_SECURITY_MAX_ATTR_VALUE_LENGTH"]),
268                    1024,
269                    "PROVIDE_SECURITY_MAX_ATTR_VALUE_LENGTH",
270                )?,
271                max_attr_count: parse_usize(
272                    env_value(env, &["PROVIDE_SECURITY_MAX_ATTR_COUNT"]),
273                    64,
274                    "PROVIDE_SECURITY_MAX_ATTR_COUNT",
275                )?,
276                max_nesting_depth: parse_usize(
277                    env_value(env, &["PROVIDE_SECURITY_MAX_NESTING_DEPTH"]),
278                    8,
279                    "PROVIDE_SECURITY_MAX_NESTING_DEPTH",
280                )?,
281            },
282        })
283    }
284}
285
286#[cfg(test)]
287#[path = "from_env_tests.rs"]
288mod tests;