Skip to main content

obs_core/
resource.rs

1//! `ResourceAttrs` — workspace-shared resource attribute set held by
2//! the observer. Spec 20 § 2.1 / spec 94 § 2.7 / P1-E.
3//!
4//! The observer owns one `Arc<ArcSwap<ResourceAttrs>>` so OTLP /
5//! Parquet / ClickHouse sinks can read the live snapshot at flush
6//! time. Phase-7 work moved this off the per-sink `OtlpResourceAttrs`
7//! so a config reload re-projects every sink without redeploying.
8
9use std::collections::BTreeMap;
10
11/// Resource attribute set carrying the OTel semantic-convention keys
12/// every sink projects onto its outbound batch (`resource.attributes`
13/// for OTLP, partition columns for Parquet, etc.).
14#[derive(Debug, Clone, Default)]
15pub struct ResourceAttrs {
16    /// `service.name` — typically `OTEL_SERVICE_NAME` or the observer's
17    /// configured service identity.
18    pub service_name: String,
19    /// `service.version`.
20    pub service_version: String,
21    /// `service.namespace` — logical service grouping (e.g. `payments`).
22    pub service_namespace: String,
23    /// `service.instance.id` — unique per process / replica.
24    pub service_instance_id: String,
25    /// `deployment.environment` — `production`, `staging`, `dev`, …
26    pub deployment_environment: String,
27    /// `host.name`.
28    pub host_name: String,
29    /// `host.arch` — `amd64`, `arm64`, …
30    pub host_arch: String,
31    /// Any additional `OTEL_RESOURCE_ATTRIBUTES` pairs that did not
32    /// land in a first-class slot.
33    pub extra: BTreeMap<String, String>,
34}
35
36impl ResourceAttrs {
37    /// Render the populated semconv keys as a flat `BTreeMap`. Useful
38    /// for sinks that project the attributes into a wire-format
39    /// `KeyValueList`.
40    #[must_use]
41    pub fn to_semconv_map(&self) -> BTreeMap<String, String> {
42        let mut m = self.extra.clone();
43        if !self.service_name.is_empty() {
44            m.insert("service.name".to_string(), self.service_name.clone());
45        }
46        if !self.service_version.is_empty() {
47            m.insert("service.version".to_string(), self.service_version.clone());
48        }
49        if !self.service_namespace.is_empty() {
50            m.insert(
51                "service.namespace".to_string(),
52                self.service_namespace.clone(),
53            );
54        }
55        if !self.service_instance_id.is_empty() {
56            m.insert(
57                "service.instance.id".to_string(),
58                self.service_instance_id.clone(),
59            );
60        }
61        if !self.deployment_environment.is_empty() {
62            m.insert(
63                "deployment.environment".to_string(),
64                self.deployment_environment.clone(),
65            );
66        }
67        if !self.host_name.is_empty() {
68            m.insert("host.name".to_string(), self.host_name.clone());
69        }
70        if !self.host_arch.is_empty() {
71            m.insert("host.arch".to_string(), self.host_arch.clone());
72        }
73        m
74    }
75}