use opentelemetry::KeyValue;
use opentelemetry_sdk::resource::{Resource, ResourceDetector};
use opentelemetry_semantic_conventions::resource::{
PROCESS_RUNTIME_DESCRIPTION, PROCESS_RUNTIME_NAME, PROCESS_RUNTIME_VERSION,
};
pub struct RustResourceDetector;
impl ResourceDetector for RustResourceDetector {
fn detect(&self) -> Resource {
let mut attrs = vec![
KeyValue::new(PROCESS_RUNTIME_NAME, "rust"),
KeyValue::new("rust.target_os", std::env::consts::OS),
KeyValue::new("rust.target_arch", std::env::consts::ARCH),
KeyValue::new("rust.target_family", std::env::consts::FAMILY),
KeyValue::new("rust.debug", cfg!(debug_assertions)),
];
if let Ok(exe_path) = std::env::current_exe()
&& let Ok(metadata) = std::fs::metadata(&exe_path)
{
let size = i64::try_from(metadata.len()).unwrap_or(i64::MAX);
attrs.push(KeyValue::new("process.executable.size", size));
}
Resource::builder().with_attributes(attrs).build()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RustBuildInfo {
pub rustc_version: Option<&'static str>,
pub rust_channel: Option<&'static str>,
pub rustc_version_full: Option<&'static str>,
}
impl RustBuildInfo {
#[must_use]
pub fn to_key_values(&self) -> Vec<KeyValue> {
let mut attrs = Vec::new();
if let Some(version) = self.rustc_version {
attrs.push(KeyValue::new(PROCESS_RUNTIME_VERSION, version));
}
if let Some(channel) = self.rust_channel {
attrs.push(KeyValue::new("rust.channel", channel));
}
if let Some(full) = self.rustc_version_full {
attrs.push(KeyValue::new(PROCESS_RUNTIME_DESCRIPTION, full));
}
attrs
}
}
pub fn emit_rustc_env() {
use std::process::Command;
println!("cargo::rerun-if-env-changed=RUSTC");
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
if let Ok(output) = Command::new(&rustc).arg("--version").output()
&& let Ok(version_str) = String::from_utf8(output.stdout)
{
let version_str = version_str.trim();
println!("cargo::rustc-env=RUSTC_VERSION_FULL={version_str}");
if let Some(version) = version_str.strip_prefix("rustc ")
&& let Some(ver) = version.split_whitespace().next()
{
println!("cargo::rustc-env=RUSTC_VERSION={ver}");
}
}
if let Ok(output) = Command::new(&rustc).arg("-vV").output()
&& let Ok(verbose) = String::from_utf8(output.stdout)
{
for line in verbose.lines() {
if let Some(release) = line.strip_prefix("release: ") {
let channel_name = if release.contains("nightly") {
"nightly"
} else if release.contains("beta") {
"beta"
} else {
"stable"
};
println!("cargo::rustc-env=RUST_CHANNEL={channel_name}");
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use opentelemetry_sdk::resource::ResourceDetector;
#[test]
fn test_rust_detector_includes_runtime_name() {
let detector = RustResourceDetector;
let resource = detector.detect();
let runtime_name = resource
.iter()
.find(|(k, _)| k.as_str() == PROCESS_RUNTIME_NAME);
assert!(runtime_name.is_some());
}
#[test]
fn test_rust_build_info_to_key_values_empty() {
let info = RustBuildInfo::default();
assert!(info.to_key_values().is_empty());
}
#[test]
fn test_rust_build_info_to_key_values_with_data() {
let info = RustBuildInfo {
rustc_version: Some("1.84.0"),
rust_channel: Some("stable"),
rustc_version_full: Some("rustc 1.84.0"),
};
let kvs = info.to_key_values();
assert_eq!(kvs.len(), 3);
}
#[test]
fn test_rust_build_info_partial_data() {
let info = RustBuildInfo {
rustc_version: Some("1.84.0"),
rust_channel: None,
rustc_version_full: None,
};
let kvs = info.to_key_values();
assert_eq!(kvs.len(), 1);
}
}