opentelemetry_resource_detectors/
host.rs

1//! HOST resource detector
2//!
3//! Detect the unique host ID.
4use opentelemetry::KeyValue;
5use opentelemetry_sdk::resource::ResourceDetector;
6use opentelemetry_sdk::Resource;
7use std::env::consts::ARCH;
8#[cfg(target_os = "linux")]
9use std::fs::read_to_string;
10#[cfg(target_os = "linux")]
11use std::path::Path;
12#[cfg(target_os = "macos")]
13use std::process::Command;
14
15/// Detect host information.
16///
17/// This resource detector returns the following information:
18///
19/// - [`host.id from non-containerized systems`]: https://opentelemetry.io/docs/specs/semconv/resource/host/#collecting-hostid-from-non-containerized-systems
20/// - Host architecture (host.arch).
21pub struct HostResourceDetector {
22    host_id_detect: fn() -> Option<String>,
23}
24
25impl ResourceDetector for HostResourceDetector {
26    fn detect(&self) -> Resource {
27        Resource::builder_empty()
28            .with_attributes(
29                [
30                    // Get host.id
31                    (self.host_id_detect)().map(|host_id| {
32                        KeyValue::new(
33                            opentelemetry_semantic_conventions::attribute::HOST_ID,
34                            host_id,
35                        )
36                    }),
37                    // Get host.arch
38                    Some(KeyValue::new(
39                        opentelemetry_semantic_conventions::attribute::HOST_ARCH,
40                        ARCH,
41                    )),
42                ]
43                .into_iter()
44                .flatten(),
45            )
46            .build()
47    }
48}
49
50#[cfg(target_os = "linux")]
51fn host_id_detect() -> Option<String> {
52    let machine_id_path = Path::new("/etc/machine-id");
53    let dbus_machine_id_path = Path::new("/var/lib/dbus/machine-id");
54    read_to_string(machine_id_path)
55        .or_else(|_| read_to_string(dbus_machine_id_path))
56        .map(|id| id.trim().to_string())
57        .ok()
58}
59
60#[cfg(target_os = "macos")]
61fn host_id_detect() -> Option<String> {
62    let output = Command::new("ioreg")
63        .arg("-rd1")
64        .arg("-c")
65        .arg("IOPlatformExpertDevice")
66        .output()
67        .ok()?
68        .stdout;
69
70    let output = String::from_utf8(output).ok()?;
71    let line = output
72        .lines()
73        .find(|line| line.contains("IOPlatformUUID"))?;
74
75    Some(line.split_once('=')?.1.trim().trim_matches('"').to_owned())
76}
77
78// TODO: Implement non-linux platforms
79#[cfg(not(any(target_os = "linux", target_os = "macos")))]
80fn host_id_detect() -> Option<String> {
81    None
82}
83
84impl Default for HostResourceDetector {
85    fn default() -> Self {
86        Self { host_id_detect }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::HostResourceDetector;
93    use opentelemetry::{Key, Value};
94    use opentelemetry_sdk::resource::ResourceDetector;
95
96    #[cfg(target_os = "linux")]
97    #[test]
98    fn test_host_resource_detector_linux() {
99        let resource = HostResourceDetector::default().detect();
100        assert_eq!(resource.len(), 2);
101        assert!(resource
102            .get(&Key::from_static_str(
103                opentelemetry_semantic_conventions::attribute::HOST_ID
104            ))
105            .is_some());
106        assert!(resource
107            .get(&Key::from_static_str(
108                opentelemetry_semantic_conventions::attribute::HOST_ARCH
109            ))
110            .is_some())
111    }
112
113    #[cfg(target_os = "macos")]
114    #[test]
115    fn test_host_resource_detector_macos() {
116        let resource = HostResourceDetector::default().detect(Duration::from_secs(0));
117        dbg!(&resource);
118        assert_eq!(resource.len(), 2);
119        assert!(resource
120            .get(Key::from_static_str(
121                opentelemetry_semantic_conventions::attribute::HOST_ID
122            ))
123            .is_some());
124        assert!(resource
125            .get(Key::from_static_str(
126                opentelemetry_semantic_conventions::attribute::HOST_ARCH
127            ))
128            .is_some())
129    }
130
131    #[test]
132    fn test_resource_host_arch_value() {
133        let resource = HostResourceDetector::default().detect();
134
135        assert!(resource
136            .get(&Key::from_static_str(
137                opentelemetry_semantic_conventions::attribute::HOST_ARCH
138            ))
139            .is_some());
140
141        #[cfg(target_arch = "x86_64")]
142        assert_eq!(
143            resource.get(&Key::from_static_str(
144                opentelemetry_semantic_conventions::attribute::HOST_ARCH
145            )),
146            Some(Value::from("x86_64"))
147        );
148
149        #[cfg(target_arch = "aarch64")]
150        assert_eq!(
151            resource.get(&Key::from_static_str(
152                opentelemetry_semantic_conventions::attribute::HOST_ARCH
153            )),
154            Some(Value::from("aarch64"))
155        )
156    }
157}