Skip to content

Commit 1bfde50

Browse files
committed
feat: add support for OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT
1 parent 42139cb commit 1bfde50

File tree

7 files changed

+218
-2
lines changed

7 files changed

+218
-2
lines changed

opentelemetry-sdk/src/trace/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ impl Default for Config {
4343
config.span_limits.max_attributes_per_span = max_attributes_per_span;
4444
}
4545

46+
if let Some(max_attribute_value_length) = env::var("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT")
47+
.ok()
48+
.and_then(|count_limit| i32::from_str(&count_limit).ok())
49+
{
50+
config.span_limits.max_attribute_value_length = max_attribute_value_length;
51+
} else {
52+
if let Some(max_attribute_value_length) = env::var("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT")
53+
.ok()
54+
.and_then(|count_limit| i32::from_str(&count_limit).ok())
55+
{
56+
config.span_limits.max_attribute_value_length = max_attribute_value_length;
57+
}
58+
}
59+
4660
if let Some(max_events_per_span) = env::var("OTEL_SPAN_EVENT_COUNT_LIMIT")
4761
.ok()
4862
.and_then(|max_events| u32::from_str(&max_events).ok())

opentelemetry-sdk/src/trace/provider.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,12 @@ impl TracerProviderBuilder {
378378
self
379379
}
380380

381+
/// Specify the maximum allowed length of attribute values.
382+
pub fn with_max_attribute_value_length(mut self, max_attribute_value_length: i32) -> Self {
383+
self.config.span_limits.max_attribute_value_length = max_attribute_value_length;
384+
self
385+
}
386+
381387
/// Specify the number of events to be recorded per span.
382388
pub fn with_max_links_per_span(mut self, max_links: u32) -> Self {
383389
self.config.span_limits.max_links_per_span = max_links;

opentelemetry-sdk/src/trace/span.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,21 @@ impl opentelemetry::trace::Span for Span {
9999
{
100100
let span_events_limit = self.span_limits.max_events_per_span as usize;
101101
let event_attributes_limit = self.span_limits.max_attributes_per_event as usize;
102+
let span_attribute_value_limit = self.span_limits.max_attribute_value_length;
102103
self.with_data(|data| {
103104
if data.events.len() < span_events_limit {
104105
let dropped_attributes_count =
105106
attributes.len().saturating_sub(event_attributes_limit);
106107
attributes.truncate(event_attributes_limit);
107108

109+
if span_attribute_value_limit > -1 {
110+
for attribute in attributes.iter_mut() {
111+
attribute
112+
.value
113+
.truncate(span_attribute_value_limit as usize);
114+
}
115+
}
116+
108117
data.events.add_event(Event::new(
109118
name,
110119
timestamp,
@@ -134,10 +143,16 @@ impl opentelemetry::trace::Span for Span {
134143
/// Note that the OpenTelemetry project documents certain ["standard
135144
/// attributes"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
136145
/// that have prescribed semantic meanings.
137-
fn set_attribute(&mut self, attribute: KeyValue) {
146+
fn set_attribute(&mut self, mut attribute: KeyValue) {
138147
let span_attribute_limit = self.span_limits.max_attributes_per_span as usize;
148+
let span_attribute_value_limit = self.span_limits.max_attribute_value_length;
139149
self.with_data(|data| {
140150
if data.attributes.len() < span_attribute_limit {
151+
if span_attribute_value_limit > -1 {
152+
attribute
153+
.value
154+
.truncate(span_attribute_value_limit as usize);
155+
}
141156
data.attributes.push(attribute);
142157
} else {
143158
data.dropped_attributes_count += 1;
@@ -278,6 +293,7 @@ mod tests {
278293
use crate::trace::{SpanEvents, SpanLinks};
279294
use opentelemetry::trace::{self, SpanBuilder, TraceFlags, TraceId, Tracer};
280295
use opentelemetry::{trace::Span as _, trace::TracerProvider};
296+
use opentelemetry::{Array, StringValue, Value};
281297
use std::time::Duration;
282298
use std::vec;
283299

@@ -566,6 +582,59 @@ mod tests {
566582
);
567583
}
568584

585+
#[test]
586+
fn exceed_span_attributes_value_length_limit() {
587+
let max_attribute_value_length = 3;
588+
let exporter = NoopSpanExporter::new();
589+
let provider_builder = crate::trace::SdkTracerProvider::builder()
590+
.with_simple_exporter(exporter)
591+
.with_max_attribute_value_length(max_attribute_value_length);
592+
let provider = provider_builder.build();
593+
let tracer = provider.tracer("opentelemetry-test");
594+
595+
let mut initial_attributes = Vec::new();
596+
initial_attributes.push(KeyValue::new("first", String::from("test data")));
597+
initial_attributes.push(KeyValue::new(
598+
"second",
599+
Value::Array(
600+
[
601+
StringValue::from("test data"),
602+
StringValue::from("test data, once again"),
603+
]
604+
.to_vec()
605+
.into(),
606+
),
607+
));
608+
let span_builder = SpanBuilder::from_name("test_span").with_attributes(initial_attributes);
609+
610+
let mut span = tracer.build(span_builder);
611+
span.set_attribute(KeyValue::new("third", "test data, after span builder"));
612+
613+
span.with_data(|data| {
614+
assert_eq!(
615+
data.attributes[0].value.as_str().len(),
616+
max_attribute_value_length as usize,
617+
"Span attribute values should be truncated to the max length"
618+
);
619+
if let Value::Array(attr) = &data.attributes[1].value {
620+
if let Array::String(elems) = attr {
621+
for elem in elems {
622+
assert_eq!(
623+
elem.as_str().len(),
624+
max_attribute_value_length as usize,
625+
"Span attribute values should be truncated to the max length"
626+
);
627+
}
628+
}
629+
}
630+
assert_eq!(
631+
data.attributes[2].value.as_str().len(),
632+
max_attribute_value_length as usize,
633+
"Span attribute values should be truncated to the max length"
634+
);
635+
});
636+
}
637+
569638
#[test]
570639
fn exceed_event_attributes_limit() {
571640
let exporter = NoopSpanExporter::new();

opentelemetry-sdk/src/trace/span_limit.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
/// index in the collection. The one added to collections later will be dropped first.
1515
pub(crate) const DEFAULT_MAX_EVENT_PER_SPAN: u32 = 128;
1616
pub(crate) const DEFAULT_MAX_ATTRIBUTES_PER_SPAN: u32 = 128;
17+
pub(crate) const DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH: i32 = -1;
1718
pub(crate) const DEFAULT_MAX_LINKS_PER_SPAN: u32 = 128;
1819
pub(crate) const DEFAULT_MAX_ATTRIBUTES_PER_EVENT: u32 = 128;
1920
pub(crate) const DEFAULT_MAX_ATTRIBUTES_PER_LINK: u32 = 128;
@@ -25,6 +26,8 @@ pub struct SpanLimits {
2526
pub max_events_per_span: u32,
2627
/// The max attributes that can be added to a `Span`.
2728
pub max_attributes_per_span: u32,
29+
/// The max length of attribute values added to a `Span`.
30+
pub max_attribute_value_length: i32,
2831
/// The max links that can be added to a `Span`.
2932
pub max_links_per_span: u32,
3033
/// The max attributes that can be added into an `Event`
@@ -38,6 +41,7 @@ impl Default for SpanLimits {
3841
SpanLimits {
3942
max_events_per_span: DEFAULT_MAX_EVENT_PER_SPAN,
4043
max_attributes_per_span: DEFAULT_MAX_ATTRIBUTES_PER_SPAN,
44+
max_attribute_value_length: DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH,
4145
max_links_per_span: DEFAULT_MAX_LINKS_PER_SPAN,
4246
max_attributes_per_link: DEFAULT_MAX_ATTRIBUTES_PER_LINK,
4347
max_attributes_per_event: DEFAULT_MAX_ATTRIBUTES_PER_EVENT,

opentelemetry-sdk/src/trace/tracer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ impl SdkTracer {
7373
.saturating_sub(span_attributes_limit);
7474
attribute_options.truncate(span_attributes_limit);
7575
let dropped_attributes_count = dropped_attributes_count as u32;
76+
let span_attribute_value_limit = span_limits.max_attribute_value_length;
77+
if span_attribute_value_limit > -1 {
78+
for attribute in attribute_options.iter_mut() {
79+
attribute
80+
.value
81+
.truncate(span_attribute_value_limit as usize);
82+
}
83+
}
7684

7785
// Links are available as Option<Vec<Link>> in the builder
7886
// If it is None, then there are no links to process.

opentelemetry/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- *Breaking* Change return type of `opentelemetry::global::set_tracer_provider` to Unit to align with metrics counterpart
66
- Add `get_all` method to `opentelemetry::propagation::Extractor` to return all values of the given propagation key and provide a default implementation.
7+
- Add support for max span attribute value length; Applies to `Value::String` and `Array::String` only.
78

89
## 0.30.0
910

opentelemetry/src/common.rs

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,31 @@ impl StringValue {
263263
pub fn as_str(&self) -> &str {
264264
self.0.as_str()
265265
}
266+
267+
/// Shortens this `StringValue` to the specified character length.
268+
pub fn truncate(&mut self, new_len: usize) {
269+
let s = self.as_ref();
270+
if s.len() > new_len {
271+
let new_value = truncate_str(s, new_len).to_string();
272+
273+
match &self.0 {
274+
OtelString::Owned(_) | OtelString::Static(_) => {
275+
self.0 = OtelString::Owned(new_value.into_boxed_str());
276+
}
277+
OtelString::RefCounted(_) => {
278+
self.0 = OtelString::RefCounted(Arc::from(new_value.as_str()))
279+
}
280+
}
281+
}
282+
}
283+
}
284+
285+
/// Returns a str truncated to the specified character length.
286+
fn truncate_str(s: &str, new_len: usize) -> &str {
287+
match s.char_indices().nth(new_len) {
288+
None => s,
289+
Some((idx, _)) => &s[..idx],
290+
}
266291
}
267292

268293
impl From<StringValue> for String {
@@ -327,6 +352,20 @@ impl Value {
327352
Value::Array(v) => format!("{v}").into(),
328353
}
329354
}
355+
356+
/// Shortens this `Value` to the specified length.
357+
///
358+
/// Only String and Array(String) values are truncated.
359+
pub fn truncate(&mut self, new_len: usize) {
360+
match self {
361+
Value::Array(a) => match a {
362+
Array::String(v) => v.iter_mut().for_each(|s| s.truncate(new_len)),
363+
_ => (),
364+
},
365+
Value::String(s) => s.truncate(new_len),
366+
_ => (),
367+
}
368+
}
330369
}
331370

332371
macro_rules! from_values {
@@ -628,7 +667,7 @@ impl InstrumentationScopeBuilder {
628667
mod tests {
629668
use std::hash::{Hash, Hasher};
630669

631-
use crate::{InstrumentationScope, KeyValue};
670+
use crate::{Array, InstrumentationScope, KeyValue, Value};
632671

633672
use rand::random;
634673
use std::collections::hash_map::DefaultHasher;
@@ -695,6 +734,81 @@ mod tests {
695734
}
696735
}
697736

737+
#[test]
738+
fn value_truncate() {
739+
for test in [
740+
(0, Value::from(true), Value::from(true)),
741+
(
742+
0,
743+
Value::Array(Array::Bool(vec![true, false])),
744+
Value::Array(Array::Bool(vec![true, false])),
745+
),
746+
(0, Value::from(42), Value::from(42)),
747+
(
748+
0,
749+
Value::Array(Array::I64(vec![42, -1])),
750+
Value::Array(Array::I64(vec![42, -1])),
751+
),
752+
(0, Value::from(42.0), Value::from(42.0)),
753+
(
754+
0,
755+
Value::Array(Array::F64(vec![42.0, -1.0])),
756+
Value::Array(Array::F64(vec![42.0, -1.0])),
757+
),
758+
(0, Value::from("value"), Value::from("")),
759+
(
760+
0,
761+
Value::Array(Array::String(vec!["value-0".into(), "value-1".into()])),
762+
Value::Array(Array::String(vec!["".into(), "".into()])),
763+
),
764+
(1, Value::from("value"), Value::from("v")),
765+
(
766+
1,
767+
Value::Array(Array::String(vec!["value-0".into(), "value-1".into()])),
768+
Value::Array(Array::String(vec!["v".into(), "v".into()])),
769+
),
770+
(5, Value::from("value"), Value::from("value")),
771+
(
772+
7,
773+
Value::Array(Array::String(vec!["value-0".into(), "value-1".into()])),
774+
Value::Array(Array::String(vec!["value-0".into(), "value-1".into()])),
775+
),
776+
(
777+
6,
778+
Value::Array(Array::String(vec!["value".into(), "value-1".into()])),
779+
Value::Array(Array::String(vec!["value".into(), "value-".into()])),
780+
),
781+
(128, Value::from("value"), Value::from("value")),
782+
(
783+
128,
784+
Value::Array(Array::String(vec!["value-0".into(), "value-1".into()])),
785+
Value::Array(Array::String(vec!["value-0".into(), "value-1".into()])),
786+
),
787+
]
788+
.iter_mut()
789+
{
790+
test.1.truncate(test.0);
791+
assert_eq!(test.1, test.2)
792+
}
793+
}
794+
795+
#[test]
796+
fn truncate_str() {
797+
for test in [
798+
(5, "", ""),
799+
(0, "Zero", ""),
800+
(10, "Short text", "Short text"),
801+
(1, "Hello World!", "H"),
802+
(6, "hello€€", "hello€"),
803+
(2, "££££££", "££"),
804+
(8, "hello€world", "hello€wo"),
805+
]
806+
.iter()
807+
{
808+
assert_eq!(super::truncate_str(test.1, test.0), test.2);
809+
}
810+
}
811+
698812
fn hash_helper<T: Hash>(item: &T) -> u64 {
699813
let mut hasher = DefaultHasher::new();
700814
item.hash(&mut hasher);

0 commit comments

Comments
 (0)