1use std::env;
8
9use opentelemetry::KeyValue;
10
11const DEFAULT_SERVICE_NAME: &str = "subms";
12
13pub struct SubMsOtelResource;
15
16impl SubMsOtelResource {
17 pub fn detect() -> Vec<KeyValue> {
22 let mut kvs: Vec<KeyValue> = Vec::new();
23
24 let service_name = env::var("OTEL_SERVICE_NAME")
25 .ok()
26 .filter(|s| !s.is_empty())
27 .unwrap_or_else(|| DEFAULT_SERVICE_NAME.to_string());
28 kvs.push(KeyValue::new("service.name", service_name));
29
30 let service_version = env::var("OTEL_SERVICE_VERSION")
31 .ok()
32 .filter(|s| !s.is_empty())
33 .unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
34 kvs.push(KeyValue::new("service.version", service_version));
35
36 kvs.push(KeyValue::new("service.instance.id", random_instance_id()));
37
38 if let Some(host) = detect_host_name() {
39 kvs.push(KeyValue::new("host.name", host));
40 }
41 kvs.push(KeyValue::new("host.arch", std::env::consts::ARCH));
42 kvs.push(KeyValue::new("os.type", std::env::consts::OS));
43 if let Some(v) = os_version() {
44 kvs.push(KeyValue::new("os.version", v));
45 }
46
47 kvs.push(KeyValue::new("process.runtime.name", "rustc"));
48 kvs.push(KeyValue::new(
49 "process.runtime.version",
50 env!("CARGO_PKG_RUST_VERSION"),
51 ));
52
53 if let Ok(v) = env::var("SUBMS_HOST")
54 && !v.is_empty()
55 {
56 kvs.push(KeyValue::new("subms.host", v));
57 }
58 if let Ok(v) = env::var("SUBMS_HARDWARE_TIER")
59 && !v.is_empty()
60 {
61 kvs.push(KeyValue::new("subms.hardware.tier", v));
62 }
63
64 if env::var("AWS_REGION").is_ok() {
65 kvs.push(KeyValue::new("cloud.provider", "aws"));
66 } else if env::var("GCP_PROJECT").is_ok() || env::var("GOOGLE_CLOUD_PROJECT").is_ok() {
67 kvs.push(KeyValue::new("cloud.provider", "gcp"));
68 } else if env::var("AZURE_REGION").is_ok() {
69 kvs.push(KeyValue::new("cloud.provider", "azure"));
70 }
71
72 if let Ok(extra) = env::var("OTEL_RESOURCE_ATTRIBUTES") {
73 for entry in extra.split(',') {
74 if let Some((k, v)) = entry.split_once('=') {
75 let k = k.trim();
76 let v = v.trim();
77 if !k.is_empty() && !v.is_empty() {
78 kvs.push(KeyValue::new(k.to_string(), v.to_string()));
79 }
80 }
81 }
82 }
83
84 kvs
85 }
86}
87
88fn detect_host_name() -> Option<String> {
89 if let Ok(v) = env::var("SUBMS_HOST")
90 && !v.is_empty()
91 {
92 return Some(v);
93 }
94 if let Ok(v) = env::var("HOSTNAME")
95 && !v.is_empty()
96 {
97 return Some(v);
98 }
99 if let Ok(v) = env::var("COMPUTERNAME")
100 && !v.is_empty()
101 {
102 return Some(v);
103 }
104 None
105}
106
107fn os_version() -> Option<String> {
108 env::var("OS_VERSION").ok().filter(|s| !s.is_empty())
109}
110
111fn random_instance_id() -> String {
112 use std::time::{SystemTime, UNIX_EPOCH};
113 let nanos = SystemTime::now()
114 .duration_since(UNIX_EPOCH)
115 .map(|d| d.as_nanos())
116 .unwrap_or(0);
117 let pid = std::process::id() as u128;
118 let mixed = nanos.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(pid);
119 format!("{:032x}", mixed)
120}