Skip to content

Commit 5a73cc1

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

File tree

7 files changed

+204
-3
lines changed

7 files changed

+204
-3
lines changed

opentelemetry-sdk/src/trace/config.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ 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 if let Some(max_attribute_value_length) =
52+
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+
4659
if let Some(max_events_per_span) = env::var("OTEL_SPAN_EVENT_COUNT_LIMIT")
4760
.ok()
4861
.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: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ impl opentelemetry::trace::Span for Span {
104104
let dropped_attributes_count =
105105
attributes.len().saturating_sub(event_attributes_limit);
106106
attributes.truncate(event_attributes_limit);
107-
108107
data.events.add_event(Event::new(
109108
name,
110109
timestamp,
@@ -134,10 +133,16 @@ impl opentelemetry::trace::Span for Span {
134133
/// Note that the OpenTelemetry project documents certain ["standard
135134
/// attributes"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
136135
/// that have prescribed semantic meanings.
137-
fn set_attribute(&mut self, attribute: KeyValue) {
136+
fn set_attribute(&mut self, mut attribute: KeyValue) {
138137
let span_attribute_limit = self.span_limits.max_attributes_per_span as usize;
138+
let span_attribute_value_limit = self.span_limits.max_attribute_value_length;
139139
self.with_data(|data| {
140140
if data.attributes.len() < span_attribute_limit {
141+
if span_attribute_value_limit > -1 {
142+
attribute
143+
.value
144+
.truncate(span_attribute_value_limit as usize);
145+
}
141146
data.attributes.push(attribute);
142147
} else {
143148
data.dropped_attributes_count += 1;
@@ -278,6 +283,7 @@ mod tests {
278283
use crate::trace::{SpanEvents, SpanLinks};
279284
use opentelemetry::trace::{self, SpanBuilder, TraceFlags, TraceId, Tracer};
280285
use opentelemetry::{trace::Span as _, trace::TracerProvider};
286+
use opentelemetry::{Array, StringValue, Value};
281287
use std::time::Duration;
282288
use std::vec;
283289

@@ -566,6 +572,58 @@ mod tests {
566572
);
567573
}
568574

575+
#[test]
576+
fn exceed_span_attributes_value_length_limit() {
577+
let max_attribute_value_length = 3;
578+
let exporter = NoopSpanExporter::new();
579+
let provider_builder = crate::trace::SdkTracerProvider::builder()
580+
.with_simple_exporter(exporter)
581+
.with_max_attribute_value_length(max_attribute_value_length);
582+
let provider = provider_builder.build();
583+
let tracer = provider.tracer("opentelemetry-test");
584+
585+
let initial_attributes = vec![
586+
KeyValue::new("first", String::from("test data")),
587+
KeyValue::new(
588+
"second",
589+
Value::Array(
590+
[
591+
StringValue::from("test data"),
592+
StringValue::from("test data, once again"),
593+
]
594+
.to_vec()
595+
.into(),
596+
),
597+
),
598+
];
599+
let span_builder = SpanBuilder::from_name("test_span").with_attributes(initial_attributes);
600+
601+
let mut span = tracer.build(span_builder);
602+
span.set_attribute(KeyValue::new("third", "test data, after span builder"));
603+
604+
span.with_data(|data| {
605+
assert_eq!(
606+
data.attributes[0].value.as_str().len(),
607+
max_attribute_value_length as usize,
608+
"Span attribute values should be truncated to the max length"
609+
);
610+
if let Value::Array(Array::String(elems)) = &data.attributes[1].value {
611+
for elem in elems {
612+
assert_eq!(
613+
elem.as_str().len(),
614+
max_attribute_value_length as usize,
615+
"Span attribute values should be truncated to the max length"
616+
);
617+
}
618+
}
619+
assert_eq!(
620+
data.attributes[2].value.as_str().len(),
621+
max_attribute_value_length as usize,
622+
"Span attribute values should be truncated to the max length"
623+
);
624+
});
625+
}
626+
569627
#[test]
570628
fn exceed_event_attributes_limit() {
571629
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: 112 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,17 @@ 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(Array::String(v)) => v.iter_mut().for_each(|s| s.truncate(new_len)),
362+
Value::String(s) => s.truncate(new_len),
363+
_ => (),
364+
}
365+
}
330366
}
331367

332368
macro_rules! from_values {
@@ -628,7 +664,7 @@ impl InstrumentationScopeBuilder {
628664
mod tests {
629665
use std::hash::{Hash, Hasher};
630666

631-
use crate::{InstrumentationScope, KeyValue};
667+
use crate::{Array, InstrumentationScope, KeyValue, Value};
632668

633669
use rand::random;
634670
use std::collections::hash_map::DefaultHasher;
@@ -695,6 +731,81 @@ mod tests {
695731
}
696732
}
697733

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

0 commit comments

Comments
 (0)