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