opentelemetry_configuration/
rust_detector.rs1use opentelemetry::KeyValue;
9use opentelemetry_sdk::resource::{Resource, ResourceDetector};
10use opentelemetry_semantic_conventions::resource::{
11 PROCESS_RUNTIME_DESCRIPTION, PROCESS_RUNTIME_NAME, PROCESS_RUNTIME_VERSION,
12};
13
14pub struct RustResourceDetector;
25
26impl ResourceDetector for RustResourceDetector {
27 fn detect(&self) -> Resource {
28 let mut attrs = vec![
29 KeyValue::new(PROCESS_RUNTIME_NAME, "rust"),
30 KeyValue::new("rust.target_os", std::env::consts::OS),
31 KeyValue::new("rust.target_arch", std::env::consts::ARCH),
32 KeyValue::new("rust.target_family", std::env::consts::FAMILY),
33 KeyValue::new("rust.debug", cfg!(debug_assertions)),
34 ];
35
36 if let Ok(exe_path) = std::env::current_exe()
37 && let Ok(metadata) = std::fs::metadata(&exe_path)
38 {
39 let size = i64::try_from(metadata.len()).unwrap_or(i64::MAX);
40 attrs.push(KeyValue::new("process.executable.size", size));
41 }
42
43 Resource::builder().with_attributes(attrs).build()
44 }
45}
46
47#[derive(Debug, Clone, Copy, Default)]
74pub struct RustBuildInfo {
75 pub rustc_version: Option<&'static str>,
77 pub rust_channel: Option<&'static str>,
79 pub rustc_version_full: Option<&'static str>,
81}
82
83impl RustBuildInfo {
84 #[must_use]
91 pub fn to_key_values(&self) -> Vec<KeyValue> {
92 let mut attrs = Vec::new();
93
94 if let Some(version) = self.rustc_version {
95 attrs.push(KeyValue::new(PROCESS_RUNTIME_VERSION, version));
96 }
97 if let Some(channel) = self.rust_channel {
98 attrs.push(KeyValue::new("rust.channel", channel));
99 }
100 if let Some(full) = self.rustc_version_full {
101 attrs.push(KeyValue::new(PROCESS_RUNTIME_DESCRIPTION, full));
102 }
103
104 attrs
105 }
106}
107
108pub fn emit_rustc_env() {
127 use std::process::Command;
128
129 println!("cargo::rerun-if-env-changed=RUSTC");
130
131 let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
132
133 if let Ok(output) = Command::new(&rustc).arg("--version").output()
134 && let Ok(version_str) = String::from_utf8(output.stdout)
135 {
136 let version_str = version_str.trim();
137 println!("cargo::rustc-env=RUSTC_VERSION_FULL={version_str}");
138
139 if let Some(version) = version_str.strip_prefix("rustc ")
140 && let Some(ver) = version.split_whitespace().next()
141 {
142 println!("cargo::rustc-env=RUSTC_VERSION={ver}");
143 }
144 }
145
146 if let Ok(output) = Command::new(&rustc).arg("-vV").output()
147 && let Ok(verbose) = String::from_utf8(output.stdout)
148 {
149 for line in verbose.lines() {
150 if let Some(release) = line.strip_prefix("release: ") {
151 let channel_name = if release.contains("nightly") {
152 "nightly"
153 } else if release.contains("beta") {
154 "beta"
155 } else {
156 "stable"
157 };
158 println!("cargo::rustc-env=RUST_CHANNEL={channel_name}");
159 break;
160 }
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use opentelemetry_sdk::resource::ResourceDetector;
169
170 #[test]
171 fn test_rust_detector_includes_runtime_name() {
172 let detector = RustResourceDetector;
173 let resource = detector.detect();
174
175 let runtime_name = resource
176 .iter()
177 .find(|(k, _)| k.as_str() == PROCESS_RUNTIME_NAME);
178 assert!(runtime_name.is_some());
179 }
180
181 #[test]
182 fn test_rust_build_info_to_key_values_empty() {
183 let info = RustBuildInfo::default();
184 assert!(info.to_key_values().is_empty());
185 }
186
187 #[test]
188 fn test_rust_build_info_to_key_values_with_data() {
189 let info = RustBuildInfo {
190 rustc_version: Some("1.84.0"),
191 rust_channel: Some("stable"),
192 rustc_version_full: Some("rustc 1.84.0"),
193 };
194 let kvs = info.to_key_values();
195 assert_eq!(kvs.len(), 3);
196 }
197
198 #[test]
199 fn test_rust_build_info_partial_data() {
200 let info = RustBuildInfo {
201 rustc_version: Some("1.84.0"),
202 rust_channel: None,
203 rustc_version_full: None,
204 };
205 let kvs = info.to_key_values();
206 assert_eq!(kvs.len(), 1);
207 }
208}