use std::ffi::OsStr;
use crate::ObservabilityError;
use crate::TelemetryBootstrapConfig;
pub const OTEL_EXPORTER_OTLP_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_ENDPOINT";
pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StandardOtlpEnvironmentStatus {
Unchanged,
Applied,
}
pub fn apply_standard_otlp_environment(
config: &mut TelemetryBootstrapConfig,
) -> Result<StandardOtlpEnvironmentStatus, ObservabilityError> {
let endpoint = crate::resolver::read_telemetry_environment_value(OTEL_EXPORTER_OTLP_ENDPOINT);
let protocol = crate::resolver::read_telemetry_environment_value(OTEL_EXPORTER_OTLP_PROTOCOL);
apply_standard_otlp_environment_values(config, endpoint.as_deref(), protocol.as_deref())
}
pub fn apply_standard_otlp_environment_values(
config: &mut TelemetryBootstrapConfig,
endpoint: Option<&OsStr>,
protocol: Option<&OsStr>,
) -> Result<StandardOtlpEnvironmentStatus, ObservabilityError> {
crate::resolver::apply_standard_otlp_environment_values(config, endpoint, protocol)
}
#[cfg(test)]
mod tests {
use std::ffi::OsStr;
#[cfg(any(unix, windows))]
use std::ffi::OsString;
use super::*;
use crate::LogsExporter;
use crate::MetricsExporter;
use crate::OtlpProtocol;
use crate::TraceExporter;
#[test]
fn missing_or_blank_endpoint_preserves_existing_configuration() {
let mut config = configured_bootstrap();
let before = snapshot(&config);
assert_eq!(
apply_standard_otlp_environment_values(&mut config, None, Some(OsStr::new("http/protobuf"))).unwrap(),
StandardOtlpEnvironmentStatus::Unchanged
);
assert_eq!(snapshot(&config), before);
assert_eq!(
apply_standard_otlp_environment_values(&mut config, Some(OsStr::new(" ")), Some(OsStr::new("grpc")))
.unwrap(),
StandardOtlpEnvironmentStatus::Unchanged
);
assert_eq!(snapshot(&config), before);
}
#[test]
fn exact_grpc_protocol_enables_all_otlp_signals() {
let mut config = configured_bootstrap();
let status = apply_standard_otlp_environment_values(
&mut config,
Some(OsStr::new(" http://collector:4317 ")),
Some(OsStr::new("grpc")),
)
.unwrap();
assert_eq!(status, StandardOtlpEnvironmentStatus::Applied);
assert!(config.observability.enabled);
assert!(config.observability.metrics.enabled);
assert_eq!(config.observability.metrics.exporter, MetricsExporter::OtlpGrpc);
assert!(config.observability.traces.enabled);
assert_eq!(config.observability.traces.exporter, TraceExporter::OtlpGrpc);
assert!(config.observability.logs.enabled);
assert_eq!(config.observability.logs.exporter, LogsExporter::OtlpGrpc);
assert_eq!(config.observability.otlp.endpoint, "http://collector:4317");
assert_eq!(config.observability.otlp.protocol, OtlpProtocol::Grpc);
}
#[test]
fn missing_or_non_grpc_protocol_fails_without_mutating_configuration() {
let mut config = configured_bootstrap();
let before = snapshot(&config);
let missing =
apply_standard_otlp_environment_values(&mut config, Some(OsStr::new("http://collector:4317")), None)
.unwrap_err();
assert!(!missing.to_string().contains("http://collector:4317"));
assert_eq!(snapshot(&config), before);
let invalid = apply_standard_otlp_environment_values(
&mut config,
Some(OsStr::new("http://user:secret@collector:4317")),
Some(OsStr::new("http/protobuf")),
)
.unwrap_err();
assert!(!invalid.to_string().contains("secret"));
assert_eq!(snapshot(&config), before);
}
#[cfg(any(unix, windows))]
#[test]
fn non_utf8_values_fail_without_mutating_configuration() {
let mut config = configured_bootstrap();
let before = snapshot(&config);
let invalid = invalid_os_string();
assert!(apply_standard_otlp_environment_values(
&mut config,
Some(invalid.as_os_str()),
Some(OsStr::new("grpc"))
)
.is_err());
assert_eq!(snapshot(&config), before);
assert!(apply_standard_otlp_environment_values(
&mut config,
Some(OsStr::new("http://collector:4317")),
Some(invalid.as_os_str())
)
.is_err());
assert_eq!(snapshot(&config), before);
}
fn configured_bootstrap() -> TelemetryBootstrapConfig {
let mut config = TelemetryBootstrapConfig::default();
config.observability.enabled = true;
config.observability.metrics.enabled = true;
config.observability.metrics.exporter = MetricsExporter::Log;
config.observability.traces.enabled = false;
config.observability.traces.exporter = TraceExporter::Disable;
config.observability.logs.enabled = true;
config.observability.logs.exporter = LogsExporter::Log;
config.observability.otlp.endpoint = "http://existing:4317".to_owned();
config
}
fn snapshot(
config: &TelemetryBootstrapConfig,
) -> (
bool,
bool,
MetricsExporter,
bool,
TraceExporter,
bool,
LogsExporter,
String,
OtlpProtocol,
) {
(
config.observability.enabled,
config.observability.metrics.enabled,
config.observability.metrics.exporter,
config.observability.traces.enabled,
config.observability.traces.exporter,
config.observability.logs.enabled,
config.observability.logs.exporter,
config.observability.otlp.endpoint.clone(),
config.observability.otlp.protocol,
)
}
#[cfg(unix)]
fn invalid_os_string() -> OsString {
use std::os::unix::ffi::OsStringExt;
OsString::from_vec(vec![0xff])
}
#[cfg(windows)]
fn invalid_os_string() -> OsString {
use std::os::windows::ffi::OsStringExt;
OsString::from_wide(&[0xd800])
}
}