toolkit/telemetry/
config.rs1use serde::{Deserialize, Serialize};
7use std::collections::{BTreeMap, HashMap};
8
9#[derive(Debug, Clone, Deserialize, Serialize, Default)]
12#[serde(deny_unknown_fields)]
13pub struct OpenTelemetryConfig {
14 #[serde(default)]
15 pub resource: OpenTelemetryResource,
16 pub exporter: Option<Exporter>,
19 #[serde(default)]
20 pub tracing: TracingConfig,
21 #[serde(default)]
22 pub metrics: MetricsConfig,
23}
24
25impl OpenTelemetryConfig {
26 #[must_use]
28 pub fn tracing_exporter(&self) -> Option<&Exporter> {
29 self.tracing.exporter.as_ref().or(self.exporter.as_ref())
30 }
31 #[must_use]
33 pub fn metrics_exporter(&self) -> Option<&Exporter> {
34 self.metrics.exporter.as_ref().or(self.exporter.as_ref())
35 }
36 #[must_use]
43 pub fn inject_trace_ids_into_logs(&self) -> bool {
44 self.tracing
45 .logs_correlation
46 .as_ref()
47 .and_then(|c| c.inject_trace_ids_into_logs)
48 .unwrap_or(true)
49 }
50}
51
52#[derive(Debug, Clone, Deserialize, Serialize)]
54#[serde(deny_unknown_fields)]
55pub struct OpenTelemetryResource {
56 #[serde(default = "default_service_name")]
58 pub service_name: String,
59 #[serde(default)]
61 pub attributes: BTreeMap<String, String>,
62}
63
64fn default_service_name() -> String {
66 "cf-gears".to_owned()
67}
68
69impl Default for OpenTelemetryResource {
70 fn default() -> Self {
71 Self {
72 service_name: default_service_name(),
73 attributes: BTreeMap::default(),
74 }
75 }
76}
77
78#[derive(Debug, Clone, Deserialize, Serialize, Default)]
80#[serde(deny_unknown_fields)]
81pub struct TracingConfig {
82 pub enabled: bool,
83 pub exporter: Option<Exporter>,
86 pub sampler: Option<Sampler>,
87 pub logs_correlation: Option<LogsCorrelation>,
88}
89
90#[derive(Debug, Clone, Deserialize, Serialize, Default)]
92#[serde(deny_unknown_fields)]
93pub struct MetricsConfig {
94 pub enabled: bool,
95 pub exporter: Option<Exporter>,
98 #[serde(skip_serializing_if = "Option::is_none")]
102 pub cardinality_limit: Option<usize>,
103}
104
105#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, Copy)]
106#[serde(rename_all = "snake_case")]
107pub enum ExporterKind {
108 #[default]
109 OtlpGrpc,
110 OtlpHttp,
111}
112
113#[derive(Debug, Clone, Deserialize, Serialize)]
114pub struct Exporter {
115 #[serde(default)]
120 pub kind: ExporterKind,
121 pub endpoint: Option<String>,
122 pub headers: Option<HashMap<String, String>>,
123 pub timeout_ms: Option<u64>,
124}
125
126#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
127#[serde(rename_all = "snake_case")]
128pub enum Sampler {
129 ParentBasedAlwaysOn {},
130 ParentBasedRatio {
131 #[serde(skip_serializing_if = "Option::is_none")]
132 ratio: Option<f64>,
133 },
134 AlwaysOn {},
135 AlwaysOff {},
136}
137
138#[derive(Debug, Clone, Deserialize, Serialize)]
139pub struct LogsCorrelation {
140 pub inject_trace_ids_into_logs: Option<bool>,
141}
142
143#[cfg(test)]
144#[cfg_attr(coverage_nightly, coverage(off))]
145mod tests {
146 use super::{LogsCorrelation, OpenTelemetryConfig, TracingConfig};
147
148 fn with_logs_correlation(lc: Option<LogsCorrelation>) -> OpenTelemetryConfig {
150 OpenTelemetryConfig {
151 tracing: TracingConfig {
152 logs_correlation: lc,
153 ..TracingConfig::default()
154 },
155 ..OpenTelemetryConfig::default()
156 }
157 }
158
159 fn flag(inject: Option<bool>) -> LogsCorrelation {
160 LogsCorrelation {
161 inject_trace_ids_into_logs: inject,
162 }
163 }
164
165 #[test]
169 fn inject_trace_ids_default_is_on_unless_explicitly_disabled() {
170 assert!(
171 OpenTelemetryConfig::default().inject_trace_ids_into_logs(),
172 "the default config must splice ids into logs"
173 );
174 assert!(
175 with_logs_correlation(None).inject_trace_ids_into_logs(),
176 "an absent logs_correlation section must default ON"
177 );
178 assert!(
179 with_logs_correlation(Some(flag(None))).inject_trace_ids_into_logs(),
180 "a logs_correlation section without the flag must default ON"
181 );
182 assert!(
183 with_logs_correlation(Some(flag(Some(true)))).inject_trace_ids_into_logs(),
184 "an explicit true stays ON"
185 );
186 assert!(
187 !with_logs_correlation(Some(flag(Some(false)))).inject_trace_ids_into_logs(),
188 "an explicit false is the only way to opt out"
189 );
190 }
191
192 #[test]
196 fn absent_opentelemetry_section_resolves_the_splice_on() {
197 let absent: Option<&OpenTelemetryConfig> = None;
198 assert!(
199 absent.is_none_or(OpenTelemetryConfig::inject_trace_ids_into_logs),
200 "no [opentelemetry] section must resolve the splice ON"
201 );
202
203 let disabled = with_logs_correlation(Some(flag(Some(false))));
204 assert!(
205 !Some(&disabled).is_none_or(OpenTelemetryConfig::inject_trace_ids_into_logs),
206 "an explicit false must still opt out when a section is present"
207 );
208 }
209}