rskit_observability/
attribute.rs1use tracing::Span;
4use tracing_opentelemetry::OpenTelemetrySpanExt;
5
6#[derive(Debug, Clone, PartialEq)]
8#[non_exhaustive]
9pub enum SpanAttributeValue {
10 String(String),
12 I64(i64),
14 F64(f64),
16 Bool(bool),
18}
19
20impl From<&str> for SpanAttributeValue {
21 fn from(value: &str) -> Self {
22 Self::String(value.to_owned())
23 }
24}
25
26impl From<String> for SpanAttributeValue {
27 fn from(value: String) -> Self {
28 Self::String(value)
29 }
30}
31
32impl From<&String> for SpanAttributeValue {
33 fn from(value: &String) -> Self {
34 Self::String(value.clone())
35 }
36}
37
38impl From<i64> for SpanAttributeValue {
39 fn from(value: i64) -> Self {
40 Self::I64(value)
41 }
42}
43
44impl From<i32> for SpanAttributeValue {
45 fn from(value: i32) -> Self {
46 Self::I64(i64::from(value))
47 }
48}
49
50impl From<u64> for SpanAttributeValue {
51 fn from(value: u64) -> Self {
52 match i64::try_from(value) {
53 Ok(value) => Self::I64(value),
54 Err(_) => Self::I64(i64::MAX),
55 }
56 }
57}
58
59impl From<usize> for SpanAttributeValue {
60 fn from(value: usize) -> Self {
61 match i64::try_from(value) {
62 Ok(value) => Self::I64(value),
63 Err(_) => Self::I64(i64::MAX),
64 }
65 }
66}
67
68impl From<f64> for SpanAttributeValue {
69 fn from(value: f64) -> Self {
70 Self::F64(value)
71 }
72}
73
74impl From<f32> for SpanAttributeValue {
75 fn from(value: f32) -> Self {
76 Self::F64(f64::from(value))
77 }
78}
79
80impl From<bool> for SpanAttributeValue {
81 fn from(value: bool) -> Self {
82 Self::Bool(value)
83 }
84}
85
86pub fn set_span_attribute(span: &Span, key: &'static str, value: impl Into<SpanAttributeValue>) {
88 set_opentelemetry_attribute(span, key, value.into());
89}
90
91pub fn record_span_attribute(span: &Span, key: &'static str, value: impl Into<SpanAttributeValue>) {
97 let value = value.into();
98 record_tracing_field(span, key, &value);
99 set_opentelemetry_attribute(span, key, value);
100}
101
102pub fn set_current_span_attribute(key: &'static str, value: impl Into<SpanAttributeValue>) {
104 set_span_attribute(&Span::current(), key, value);
105}
106
107pub fn record_current_span_attribute(key: &'static str, value: impl Into<SpanAttributeValue>) {
109 record_span_attribute(&Span::current(), key, value);
110}
111
112fn record_tracing_field(span: &Span, key: &'static str, value: &SpanAttributeValue) {
113 match value {
114 SpanAttributeValue::String(value) => span.record(key, value.as_str()),
115 SpanAttributeValue::I64(value) => span.record(key, *value),
116 SpanAttributeValue::F64(value) => span.record(key, *value),
117 SpanAttributeValue::Bool(value) => span.record(key, *value),
118 };
119}
120
121fn set_opentelemetry_attribute(span: &Span, key: &'static str, value: SpanAttributeValue) {
122 match value {
123 SpanAttributeValue::String(value) => span.set_attribute(key, value),
124 SpanAttributeValue::I64(value) => span.set_attribute(key, value),
125 SpanAttributeValue::F64(value) => span.set_attribute(key, value),
126 SpanAttributeValue::Bool(value) => span.set_attribute(key, value),
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn unsigned_values_saturate_to_otlp_integer_range() {
136 assert_eq!(
137 SpanAttributeValue::from(u64::MAX),
138 SpanAttributeValue::I64(i64::MAX)
139 );
140 assert_eq!(SpanAttributeValue::from(7_u64), SpanAttributeValue::I64(7));
141 assert_eq!(
142 SpanAttributeValue::from(usize::MAX),
143 SpanAttributeValue::I64(i64::MAX)
144 );
145 assert_eq!(
146 SpanAttributeValue::from(7_usize),
147 SpanAttributeValue::I64(7)
148 );
149 }
150
151 #[test]
152 fn helpers_accept_common_attribute_values() {
153 let owned = String::from("owned");
154 assert_eq!(
155 SpanAttributeValue::from(owned.clone()),
156 SpanAttributeValue::String(owned.clone())
157 );
158 assert_eq!(
159 SpanAttributeValue::from(&owned),
160 SpanAttributeValue::String(owned)
161 );
162 assert_eq!(SpanAttributeValue::from(7_i32), SpanAttributeValue::I64(7));
163 assert_eq!(
164 SpanAttributeValue::from(1.25_f32),
165 SpanAttributeValue::F64(1.25)
166 );
167
168 let span = tracing::info_span!(
169 "attribute-test",
170 string = tracing::field::Empty,
171 integer = tracing::field::Empty,
172 float = tracing::field::Empty,
173 boolean = tracing::field::Empty,
174 );
175
176 record_span_attribute(&span, "string", "value");
177 record_span_attribute(&span, "integer", 42_i64);
178 record_span_attribute(&span, "float", 1.5_f64);
179 record_span_attribute(&span, "boolean", true);
180 set_span_attribute(&span, "otel.only", "value");
181
182 let _entered = span.enter();
183 set_current_span_attribute("current.otel", false);
184 record_current_span_attribute("boolean", false);
185 }
186}