sentry_contexts/
utils.rs

1use sentry_core::protocol::{Context, DeviceContext, Map, OsContext, RuntimeContext};
2
3include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
4
5#[cfg(target_os = "macos")]
6mod model_support {
7    use libc::c_void;
8    use std::ffi::CString;
9    use std::ptr;
10
11    fn sysctlbyname_call(name: &str) -> Option<String> {
12        unsafe {
13            let c_name = match CString::new(name) {
14                Ok(name) => name.into_bytes_with_nul(),
15                Err(_e) => return None,
16            };
17            let mut size = 0;
18            let res = libc::sysctlbyname(
19                c_name.as_ptr() as _,
20                ptr::null_mut(),
21                &mut size,
22                ptr::null_mut(),
23                0,
24            );
25            if res != 0 {
26                return None;
27            }
28
29            let mut buf = vec![0u8; size];
30            let res = libc::sysctlbyname(
31                c_name.as_ptr() as _,
32                buf.as_mut_ptr() as *mut c_void,
33                &mut size,
34                ptr::null_mut(),
35                0,
36            );
37            if res != 0 {
38                return None;
39            }
40
41            Some(
42                buf.into_iter()
43                    .take(size)
44                    .take_while(|&c| c != b'\0')
45                    .map(|c| c as char)
46                    .collect(),
47            )
48        }
49    }
50
51    pub fn get_model() -> Option<String> {
52        sysctlbyname_call("hw.model")
53    }
54
55    pub fn get_macos_version() -> Option<String> {
56        let version = sysctlbyname_call("kern.osproductversion")?;
57        let dot_count = version.split('.').count() - 1;
58        if dot_count < 2 {
59            return Some(version + ".0");
60        }
61        Some(version)
62    }
63
64    pub fn get_macos_build() -> Option<String> {
65        sysctlbyname_call("kern.osversion")
66    }
67
68    pub fn get_family() -> Option<String> {
69        get_model().map(|mut s| {
70            let len = s
71                .as_bytes()
72                .iter()
73                .take_while(|c| c.is_ascii_alphabetic())
74                .count();
75            s.truncate(len);
76            s
77        })
78    }
79
80    #[test]
81    fn test_macos_hw_model() {
82        let m = get_model().unwrap();
83        assert!(m.chars().all(|c| c != '\0'));
84        let f = get_family().unwrap();
85        assert!(f.chars().all(|c| !c.is_ascii_digit()));
86    }
87
88    #[test]
89    fn test_macos_version_and_build() {
90        let v = get_macos_version().unwrap();
91        assert!(v.chars().all(|c| c.is_ascii_digit() || c == '.'));
92        let dot_count = v.split('.').count() - 1;
93        assert_eq!(dot_count, 2);
94        let b = get_macos_build().unwrap();
95        assert!(b
96            .chars()
97            .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit()));
98    }
99}
100
101#[cfg(not(target_os = "macos"))]
102mod model_support {
103    pub fn get_model() -> Option<String> {
104        None
105    }
106
107    pub fn get_family() -> Option<String> {
108        None
109    }
110}
111
112/// Returns the server name (hostname) if available.
113pub fn server_name() -> Option<String> {
114    hostname::get().ok().and_then(|s| s.into_string().ok())
115}
116
117/// Returns the OS context
118#[cfg(not(windows))]
119pub fn os_context() -> Option<Context> {
120    use uname::uname;
121    if let Ok(info) = uname() {
122        #[cfg(target_os = "macos")]
123        {
124            Some(
125                OsContext {
126                    name: Some("macOS".into()),
127                    kernel_version: Some(info.version),
128                    version: model_support::get_macos_version(),
129                    build: model_support::get_macos_build(),
130                    ..Default::default()
131                }
132                .into(),
133            )
134        }
135        #[cfg(not(target_os = "macos"))]
136        {
137            Some(
138                OsContext {
139                    name: Some(info.sysname),
140                    kernel_version: Some(info.version),
141                    version: Some(info.release),
142                    ..Default::default()
143                }
144                .into(),
145            )
146        }
147    } else {
148        None
149    }
150}
151
152/// Returns the OS context
153#[cfg(windows)]
154pub fn os_context() -> Option<Context> {
155    use os_info::Version;
156    let version = match os_info::get().version() {
157        Version::Unknown => None,
158        version => Some(version.to_string()),
159    };
160
161    Some(
162        OsContext {
163            name: Some(PLATFORM.into()),
164            version,
165            ..Default::default()
166        }
167        .into(),
168    )
169}
170
171/// Returns the rust info.
172pub fn rust_context() -> Context {
173    RuntimeContext {
174        name: Some("rustc".into()),
175        version: RUSTC_VERSION.map(|x| x.into()),
176        other: {
177            let mut map = Map::default();
178            if let Some(channel) = RUSTC_CHANNEL {
179                map.insert("channel".to_string(), channel.into());
180            }
181            map
182        },
183    }
184    .into()
185}
186
187/// Returns the device context.
188pub fn device_context() -> Context {
189    DeviceContext {
190        model: model_support::get_model(),
191        family: model_support::get_family(),
192        arch: Some(ARCH.into()),
193        ..Default::default()
194    }
195    .into()
196}
197
198#[cfg(test)]
199mod tests {
200    #[cfg(windows)]
201    #[test]
202    fn windows_os_version_not_empty() {
203        use super::*;
204        let context = os_context();
205        match context {
206            Some(Context::Os(os_context)) => {
207                // verify the version is a non-empty string
208                let version = os_context.version.expect("OS version to be some");
209                assert!(!version.is_empty());
210
211                // verify the version is not equal to the unknown OS version
212                let unknown_version = os_info::Version::Unknown.to_string();
213                assert_ne!(version, unknown_version);
214            }
215            _ => unreachable!("os_context() should return a Context::Os"),
216        }
217    }
218}