Skip to main content

provide_telemetry/config/
mod.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::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10mod from_env;
11mod parse;
12pub(crate) mod probe;
13mod redact;
14mod validate;
15
16pub use redact::redact_config;
17
18/// Ceiling on exporter retries per signal, shared with the resilience layer's
19/// `MAX_EXPORT_ATTEMPTS` (retries + the first attempt). Mirrors TypeScript's
20/// `MAX_EXPORT_ATTEMPTS = 101`, so the same `PROVIDE_EXPORTER_*_RETRIES` value
21/// is accepted or rejected identically in every language.
22pub(crate) const MAX_EXPORTER_RETRIES: usize = 100;
23
24#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
25#[serde(default)]
26pub struct RuntimeOverrides {
27    pub sampling: Option<SamplingConfig>,
28    pub backpressure: Option<BackpressureConfig>,
29    pub exporter: Option<ExporterPolicyConfig>,
30    pub security: Option<SecurityConfig>,
31    pub slo: Option<SLOConfig>,
32    pub pii_max_depth: Option<usize>,
33    pub strict_schema: Option<bool>,
34    pub event_schema: Option<EventSchemaConfig>,
35    /// Hot-reloadable logging overrides. When `Some(cfg)`, the logger is
36    /// reconfigured so subsequent log events honor the new level, format,
37    /// and module-level thresholds. Matches Python's reference behavior.
38    pub logging: Option<LoggingConfig>,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(default)]
43pub struct LoggingConfig {
44    pub level: String,
45    pub fmt: String,
46    /// Whether to include an ISO 8601 timestamp in JSON log output.
47    /// Controlled by `PROVIDE_LOG_INCLUDE_TIMESTAMP` (default: true).
48    pub include_timestamp: bool,
49    pub otlp_headers: HashMap<String, String>,
50    /// OTLP endpoint URL for logs export. Falls back to the shared
51    /// `OTEL_EXPORTER_OTLP_ENDPOINT` when `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`
52    /// is unset. `None` means no endpoint configured.
53    pub otlp_endpoint: Option<String>,
54    /// Per-signal kill switch for the OTLP log provider. When false, the
55    /// logger provider is skipped even if `otlp_endpoint` is set — useful
56    /// to escape shutdown hangs against unreachable collectors without
57    /// unsetting `OTEL_EXPORTER_OTLP_ENDPOINT`. Controlled by
58    /// `PROVIDE_LOG_OTLP_ENABLED` (default: true).
59    pub otlp_enabled: bool,
60    /// OTLP transport protocol for logs. Empty string means default
61    /// (resolved at exporter-build time to `http/protobuf`). Values:
62    /// `http/protobuf`, `http/json`, `grpc` (the latter requires the
63    /// `otel-grpc` cargo feature).
64    pub otlp_protocol: String,
65    /// Per-module log level overrides. Keys are module-name prefixes
66    /// (longest-prefix wins); values are level strings (TRACE/DEBUG/
67    /// INFO/WARN/ERROR). Controlled by `PROVIDE_LOG_MODULE_LEVELS`.
68    pub module_levels: HashMap<String, String>,
69}
70
71impl Default for LoggingConfig {
72    fn default() -> Self {
73        Self {
74            level: "INFO".to_string(),
75            fmt: "console".to_string(),
76            include_timestamp: true,
77            otlp_headers: HashMap::new(),
78            otlp_endpoint: None,
79            otlp_enabled: true,
80            otlp_protocol: String::new(),
81            module_levels: HashMap::new(),
82        }
83    }
84}
85
86#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
87#[serde(default)]
88pub struct TracingConfig {
89    pub enabled: bool,
90    /// Per-signal sample rate for traces (PROVIDE_TRACE_SAMPLE_RATE).
91    /// Combined with sampling.traces_rate via min() in apply_policies.
92    pub sample_rate: f64,
93    pub otlp_headers: HashMap<String, String>,
94    /// OTLP endpoint URL for traces export. Falls back to the shared
95    /// `OTEL_EXPORTER_OTLP_ENDPOINT` when `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`
96    /// is unset.
97    pub otlp_endpoint: Option<String>,
98    /// OTLP transport protocol for traces. See `LoggingConfig::otlp_protocol`.
99    pub otlp_protocol: String,
100}
101
102impl Default for TracingConfig {
103    fn default() -> Self {
104        Self {
105            enabled: true,
106            sample_rate: 1.0,
107            otlp_headers: HashMap::new(),
108            otlp_endpoint: None,
109            otlp_protocol: String::new(),
110        }
111    }
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(default)]
116pub struct MetricsConfig {
117    pub enabled: bool,
118    pub otlp_headers: HashMap<String, String>,
119    /// OTLP endpoint URL for metrics export. Falls back to the shared
120    /// `OTEL_EXPORTER_OTLP_ENDPOINT` when `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`
121    /// is unset.
122    pub otlp_endpoint: Option<String>,
123    /// OTLP transport protocol for metrics. See `LoggingConfig::otlp_protocol`.
124    pub otlp_protocol: String,
125    /// How often (in milliseconds) the `PeriodicReader` pushes metrics to the
126    /// OTLP endpoint. Parsed from `OTEL_METRIC_EXPORT_INTERVAL` (OTel spec).
127    /// Default: 60 000 ms (60 seconds).
128    pub metric_export_interval_ms: u64,
129}
130
131fn default_metric_export_interval_ms() -> u64 {
132    60_000
133}
134
135impl Default for MetricsConfig {
136    fn default() -> Self {
137        Self {
138            enabled: true,
139            otlp_headers: HashMap::new(),
140            otlp_endpoint: None,
141            otlp_protocol: String::new(),
142            metric_export_interval_ms: default_metric_export_interval_ms(),
143        }
144    }
145}
146
147#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
148#[serde(default)]
149pub struct EventSchemaConfig {
150    pub strict_event_name: bool,
151    pub required_keys: Vec<String>,
152}
153
154#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
155#[serde(default)]
156pub struct SamplingConfig {
157    pub logs_rate: f64,
158    pub traces_rate: f64,
159    pub metrics_rate: f64,
160}
161
162impl Default for SamplingConfig {
163    fn default() -> Self {
164        Self {
165            logs_rate: 1.0,
166            traces_rate: 1.0,
167            metrics_rate: 1.0,
168        }
169    }
170}
171
172#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
173#[serde(default)]
174pub struct BackpressureConfig {
175    pub logs_maxsize: usize,
176    pub traces_maxsize: usize,
177    pub metrics_maxsize: usize,
178}
179
180#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
181#[serde(default)]
182pub struct ExporterPolicyConfig {
183    pub logs_retries: usize,
184    pub traces_retries: usize,
185    pub metrics_retries: usize,
186    pub logs_backoff_seconds: f64,
187    pub traces_backoff_seconds: f64,
188    pub metrics_backoff_seconds: f64,
189    pub logs_timeout_seconds: f64,
190    pub traces_timeout_seconds: f64,
191    pub metrics_timeout_seconds: f64,
192    /// Hard deadline for `shutdown_telemetry(None)`'s flush+shutdown sequence
193    /// per signal (seconds). When the OTLP endpoint is unreachable the OTel
194    /// SDK's `force_flush()`/`shutdown()` can sit in its internal retry
195    /// loop; this deadline forces `shutdown_telemetry(None)` to return. Mirrors
196    /// `PROVIDE_EXPORTER_LOGS_SHUTDOWN_TIMEOUT_SECONDS`. Default 5.0.
197    pub logs_shutdown_timeout_seconds: f64,
198    pub logs_fail_open: bool,
199    pub traces_fail_open: bool,
200    pub metrics_fail_open: bool,
201}
202
203impl Default for ExporterPolicyConfig {
204    fn default() -> Self {
205        Self {
206            logs_retries: 0,
207            traces_retries: 0,
208            metrics_retries: 0,
209            logs_backoff_seconds: 0.0,
210            traces_backoff_seconds: 0.0,
211            metrics_backoff_seconds: 0.0,
212            logs_timeout_seconds: 10.0,
213            traces_timeout_seconds: 10.0,
214            metrics_timeout_seconds: 10.0,
215            logs_shutdown_timeout_seconds: 5.0,
216            logs_fail_open: true,
217            traces_fail_open: true,
218            metrics_fail_open: true,
219        }
220    }
221}
222
223#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(default)]
225pub struct SLOConfig {
226    pub enable_red_metrics: bool,
227    pub enable_use_metrics: bool,
228}
229
230#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
231#[serde(default)]
232pub struct SecurityConfig {
233    pub max_attr_value_length: usize,
234    pub max_attr_count: usize,
235    pub max_nesting_depth: usize,
236}
237
238impl Default for SecurityConfig {
239    fn default() -> Self {
240        Self {
241            max_attr_value_length: 1024,
242            max_attr_count: 64,
243            max_nesting_depth: 8,
244        }
245    }
246}
247
248#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
249#[serde(default)]
250pub struct TelemetryConfig {
251    pub service_name: String,
252    pub environment: String,
253    pub version: String,
254    pub strict_schema: bool,
255    pub pii_max_depth: usize,
256    pub logging: LoggingConfig,
257    pub tracing: TracingConfig,
258    pub metrics: MetricsConfig,
259    pub event_schema: EventSchemaConfig,
260    pub sampling: SamplingConfig,
261    pub backpressure: BackpressureConfig,
262    pub exporter: ExporterPolicyConfig,
263    pub slo: SLOConfig,
264    pub security: SecurityConfig,
265}
266
267impl Default for TelemetryConfig {
268    fn default() -> Self {
269        Self {
270            service_name: "provide-service".to_string(),
271            environment: "dev".to_string(),
272            version: "0.0.0".to_string(),
273            strict_schema: false,
274            pii_max_depth: 8,
275            logging: LoggingConfig::default(),
276            tracing: TracingConfig::default(),
277            metrics: MetricsConfig::default(),
278            event_schema: EventSchemaConfig::default(),
279            sampling: SamplingConfig::default(),
280            backpressure: BackpressureConfig::default(),
281            exporter: ExporterPolicyConfig::default(),
282            slo: SLOConfig::default(),
283            security: SecurityConfig::default(),
284        }
285    }
286}