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}