krabka_telemetry/
config.rs1use {
9 crate::error::TelemetryError,
10 krabka_units::{
11 parse,
12 prelude::{Time, TimeExt, secs},
13 },
14 std::time::Duration,
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum OtlpProtocol {
22 Grpc,
24 HttpProtobuf,
27}
28
29impl OtlpProtocol {
30 #[must_use]
35 pub fn parse(s: &str) -> Self {
36 match s.trim().to_ascii_lowercase().as_str() {
37 "http/protobuf" | "http" | "httpbinary" | "http-protobuf" => Self::HttpProtobuf,
38 _ => Self::Grpc,
39 }
40 }
41
42 #[must_use]
43 pub fn default_endpoint(self) -> &'static str {
44 match self {
45 Self::Grpc => "http://localhost:4317",
46 Self::HttpProtobuf => "http://localhost:4318",
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
57pub struct OtlpConfig {
58 pub endpoint: String,
59 pub protocol: OtlpProtocol,
60 pub sample_ratio: f64,
63 pub service_name: String,
64 pub service_version: String,
65 pub service_instance_id: String,
66 pub timeout: Time,
67 pub heartbeat_interval: Option<Time>,
68}
69
70fn env_truthy(v: &str) -> bool {
72 matches!(
73 v.trim().to_ascii_lowercase().as_str(),
74 "1" | "true" | "yes" | "on"
75 )
76}
77
78fn parse_time(name: &'static str, value: &str) -> Result<Time, TelemetryError> {
79 let time = parse::non_negative_time(value).map_err(|error| TelemetryError::InvalidConfig {
80 name,
81 message: error.to_string(),
82 })?;
83 Duration::try_from_secs_f64(time.secs_f64()).map_err(|error| {
84 TelemetryError::InvalidConfig {
85 name,
86 message: error.to_string(),
87 }
88 })?;
89 Ok(time)
90}
91
92impl OtlpConfig {
93 pub fn from_env(
108 get: impl Fn(&str) -> Option<String>,
109 service_instance_id: &str,
110 service_version: &str,
111 default_service_name: &str,
112 ) -> Result<Option<Self>, TelemetryError> {
113 if get("OTEL_SDK_DISABLED").as_deref().is_some_and(env_truthy) {
114 return Ok(None);
115 }
116
117 let endpoint_override = get("KRABKA_OTLP_ENDPOINT")
118 .or_else(|| get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"))
119 .or_else(|| get("OTEL_EXPORTER_OTLP_ENDPOINT"))
120 .map(|s| s.trim().to_owned())
121 .filter(|s| !s.is_empty());
122
123 let explicitly_enabled = get("KRABKA_OTLP_ENABLED")
124 .as_deref()
125 .is_some_and(env_truthy);
126
127 if endpoint_override.is_none() && !explicitly_enabled {
129 return Ok(None);
130 }
131
132 let protocol = get("KRABKA_OTLP_PROTOCOL")
133 .or_else(|| get("OTEL_EXPORTER_OTLP_PROTOCOL"))
134 .map_or(OtlpProtocol::Grpc, |s| OtlpProtocol::parse(&s));
135
136 let endpoint = endpoint_override.unwrap_or_else(|| protocol.default_endpoint().to_owned());
137
138 let sample_ratio = get("KRABKA_OTLP_SAMPLE_RATIO")
139 .or_else(|| get("OTEL_TRACES_SAMPLER_ARG"))
140 .and_then(|s| s.trim().parse::<f64>().ok())
141 .map_or(1.0, |r| r.clamp(0.0, 1.0));
142
143 let service_name = get("OTEL_SERVICE_NAME")
144 .map(|s| s.trim().to_owned())
145 .filter(|s| !s.is_empty())
146 .unwrap_or_else(|| default_service_name.to_owned());
147
148 let timeout = match get("KRABKA_OTLP_TIMEOUT") {
149 Some(value) => parse_time("KRABKA_OTLP_TIMEOUT", &value)?,
150 None => get("OTEL_EXPORTER_OTLP_TIMEOUT_SECS")
151 .and_then(|s| s.trim().parse::<u64>().ok())
152 .map_or_else(
153 || secs(10),
154 |seconds| Time::from_std(Duration::from_secs(seconds)),
155 ),
156 };
157
158 let heartbeat_interval = get("KRABKA_OTLP_HEARTBEAT_INTERVAL")
159 .map(|value| parse_time("KRABKA_OTLP_HEARTBEAT_INTERVAL", &value))
160 .transpose()?
161 .filter(|interval| *interval > Time::ZERO);
162
163 Ok(Some(Self {
164 endpoint,
165 protocol,
166 sample_ratio,
167 service_name,
168 service_version: service_version.to_owned(),
169 service_instance_id: service_instance_id.to_owned(),
170 timeout,
171 heartbeat_interval,
172 }))
173 }
174}
175
176#[cfg(test)]
177mod tests;