use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct OpenTelemetryConfig {
#[serde(default)]
pub resource: OpenTelemetryResource,
pub exporter: Option<Exporter>,
#[serde(default)]
pub tracing: TracingConfig,
#[serde(default)]
pub metrics: MetricsConfig,
}
impl OpenTelemetryConfig {
#[must_use]
pub fn tracing_exporter(&self) -> Option<&Exporter> {
self.tracing.exporter.as_ref().or(self.exporter.as_ref())
}
#[must_use]
pub fn metrics_exporter(&self) -> Option<&Exporter> {
self.metrics.exporter.as_ref().or(self.exporter.as_ref())
}
#[must_use]
pub fn inject_trace_ids_into_logs(&self) -> bool {
self.tracing
.logs_correlation
.as_ref()
.and_then(|c| c.inject_trace_ids_into_logs)
.unwrap_or(true)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OpenTelemetryResource {
#[serde(default = "default_service_name")]
pub service_name: String,
#[serde(default)]
pub attributes: BTreeMap<String, String>,
}
fn default_service_name() -> String {
"cf-gears".to_owned()
}
impl Default for OpenTelemetryResource {
fn default() -> Self {
Self {
service_name: default_service_name(),
attributes: BTreeMap::default(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct TracingConfig {
pub enabled: bool,
pub exporter: Option<Exporter>,
pub sampler: Option<Sampler>,
pub logs_correlation: Option<LogsCorrelation>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct MetricsConfig {
pub enabled: bool,
pub exporter: Option<Exporter>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cardinality_limit: Option<usize>,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, Copy)]
#[serde(rename_all = "snake_case")]
pub enum ExporterKind {
#[default]
OtlpGrpc,
OtlpHttp,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Exporter {
#[serde(default)]
pub kind: ExporterKind,
pub endpoint: Option<String>,
pub headers: Option<HashMap<String, String>>,
pub timeout_ms: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Sampler {
ParentBasedAlwaysOn {},
ParentBasedRatio {
#[serde(skip_serializing_if = "Option::is_none")]
ratio: Option<f64>,
},
AlwaysOn {},
AlwaysOff {},
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LogsCorrelation {
pub inject_trace_ids_into_logs: Option<bool>,
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::{LogsCorrelation, OpenTelemetryConfig, TracingConfig};
fn with_logs_correlation(lc: Option<LogsCorrelation>) -> OpenTelemetryConfig {
OpenTelemetryConfig {
tracing: TracingConfig {
logs_correlation: lc,
..TracingConfig::default()
},
..OpenTelemetryConfig::default()
}
}
fn flag(inject: Option<bool>) -> LogsCorrelation {
LogsCorrelation {
inject_trace_ids_into_logs: inject,
}
}
#[test]
fn inject_trace_ids_default_is_on_unless_explicitly_disabled() {
assert!(
OpenTelemetryConfig::default().inject_trace_ids_into_logs(),
"the default config must splice ids into logs"
);
assert!(
with_logs_correlation(None).inject_trace_ids_into_logs(),
"an absent logs_correlation section must default ON"
);
assert!(
with_logs_correlation(Some(flag(None))).inject_trace_ids_into_logs(),
"a logs_correlation section without the flag must default ON"
);
assert!(
with_logs_correlation(Some(flag(Some(true)))).inject_trace_ids_into_logs(),
"an explicit true stays ON"
);
assert!(
!with_logs_correlation(Some(flag(Some(false)))).inject_trace_ids_into_logs(),
"an explicit false is the only way to opt out"
);
}
#[test]
fn absent_opentelemetry_section_resolves_the_splice_on() {
let absent: Option<&OpenTelemetryConfig> = None;
assert!(
absent.is_none_or(OpenTelemetryConfig::inject_trace_ids_into_logs),
"no [opentelemetry] section must resolve the splice ON"
);
let disabled = with_logs_correlation(Some(flag(Some(false))));
assert!(
!Some(&disabled).is_none_or(OpenTelemetryConfig::inject_trace_ids_into_logs),
"an explicit false must still opt out when a section is present"
);
}
}