Skip to main content

subms_otel/
resource.rs

1//! Resource semconv detector. Surfaces the `service.*`, `host.*`, `os.*`,
2//! `process.runtime.*`, `subms.*`, and `cloud.provider` attributes the
3//! OpenTelemetry spec calls out. Returns a plain `Vec<KeyValue>` so the
4//! bridge stays SDK-free; callers wiring an SDK Resource do
5//! `Resource::builder_empty().with_attributes(detect()).build()`.
6
7use std::env;
8
9use opentelemetry::KeyValue;
10
11const DEFAULT_SERVICE_NAME: &str = "subms";
12
13/// Static helper carrying the detected resource set.
14pub struct SubMsOtelResource;
15
16impl SubMsOtelResource {
17    /// Detect the resource attributes from environment + runtime introspection.
18    /// Honors `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`,
19    /// `OTEL_RESOURCE_ATTRIBUTES`, `SUBMS_HOST`, `SUBMS_HARDWARE_TIER`,
20    /// and the standard cloud-provider env probes.
21    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}