@@ -945,6 +945,7 @@ pub struct OtelGlobals {
945945 pub id_generator : DenoIdGenerator ,
946946 pub meter_provider : SdkMeterProvider ,
947947 pub builtin_instrumentation_scope : InstrumentationScope ,
948+ pub span_attribute_count_limit : usize ,
948949 pub sampler : Sampler ,
949950 pub config : OtelConfig ,
950951}
@@ -959,6 +960,40 @@ impl OtelGlobals {
959960 }
960961}
961962
963+ /// Default maximum number of attributes per span (and per span event / link),
964+ /// as defined by the OpenTelemetry SDK spec for `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT`
965+ /// (and `OTEL_ATTRIBUTE_COUNT_LIMIT`).
966+ const DEFAULT_ATTRIBUTE_COUNT_LIMIT : usize = 128 ;
967+
968+ /// Resolve the span attribute count limit from the environment, honoring
969+ /// `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT` and falling back to the general
970+ /// `OTEL_ATTRIBUTE_COUNT_LIMIT`, then to the spec default of 128.
971+ ///
972+ /// See <https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#attribute-limits>
973+ fn span_attribute_count_limit_from_env ( sys : & impl TelemetrySys ) -> usize {
974+ [
975+ "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT" ,
976+ "OTEL_ATTRIBUTE_COUNT_LIMIT" ,
977+ ]
978+ . into_iter ( )
979+ . find_map ( |name| {
980+ sys
981+ . env_var ( name)
982+ . ok ( )
983+ . and_then ( |v| v. trim ( ) . parse :: < usize > ( ) . ok ( ) )
984+ } )
985+ . unwrap_or ( DEFAULT_ATTRIBUTE_COUNT_LIMIT )
986+ }
987+
988+ /// The effective span attribute count limit from the initialized globals,
989+ /// falling back to the spec default if telemetry is not yet initialized.
990+ fn attribute_count_limit ( ) -> usize {
991+ OTEL_GLOBALS
992+ . get ( )
993+ . map ( |g| g. span_attribute_count_limit )
994+ . unwrap_or ( DEFAULT_ATTRIBUTE_COUNT_LIMIT )
995+ }
996+
962997/// Head-based trace sampler configured via the `OTEL_TRACES_SAMPLER` and
963998/// `OTEL_TRACES_SAMPLER_ARG` environment variables.
964999///
@@ -1268,6 +1303,7 @@ pub fn init(
12681303 DenoIdGenerator :: random ( )
12691304 } ;
12701305
1306+ let span_attribute_count_limit = span_attribute_count_limit_from_env ( sys) ;
12711307 let sampler = Sampler :: from_env ( sys) ?;
12721308
12731309 OTEL_GLOBALS
@@ -1277,6 +1313,7 @@ pub fn init(
12771313 id_generator,
12781314 meter_provider,
12791315 builtin_instrumentation_scope,
1316+ span_attribute_count_limit,
12801317 sampler,
12811318 config,
12821319 } )
@@ -1584,16 +1621,17 @@ macro_rules! attr_raw {
15841621}
15851622
15861623macro_rules! attr {
1587- ( $scope: ident, $attributes: expr $( => $dropped_attributes_count: expr) ?, $name: expr, $value: expr) => {
1588- let attr = attr_raw!( $scope, $name, $value) ;
1589- if let Some ( kv) = attr {
1624+ ( $scope: ident, $attributes: expr => $dropped_attributes_count: expr, $limit: expr, $name: expr, $value: expr) => {
1625+ // Enforce the configured per-element attribute count limit
1626+ // (`OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT`): once the limit is reached, further
1627+ // attributes are dropped and counted, matching the OpenTelemetry SDK spec.
1628+ if $attributes. len( ) >= $limit {
1629+ $dropped_attributes_count += 1 ;
1630+ } else if let Some ( kv) = attr_raw!( $scope, $name, $value) {
15901631 $attributes. push( kv) ;
1632+ } else {
1633+ $dropped_attributes_count += 1 ;
15911634 }
1592- $(
1593- else {
1594- $dropped_attributes_count += 1 ;
1595- }
1596- ) ?
15971635 } ;
15981636}
15991637
@@ -2213,14 +2251,15 @@ fn op_otel_span_attribute1<'s>(
22132251 else {
22142252 return ;
22152253 } ;
2254+ let limit = attribute_count_limit ( ) ;
22162255 let mut state = span. 0 . borrow_mut ( ) ;
22172256 if let OtelSpanState :: Recording ( span) = & mut * * state {
22182257 let Some ( ( attributes, dropped_attributes_count) ) =
22192258 span_attributes ( span, location)
22202259 else {
22212260 return ;
22222261 } ;
2223- attr ! ( scope, attributes => * dropped_attributes_count, key, value) ;
2262+ attr ! ( scope, attributes => * dropped_attributes_count, limit , key, value) ;
22242263 }
22252264}
22262265
@@ -2239,15 +2278,16 @@ fn op_otel_span_attribute2<'s>(
22392278 else {
22402279 return ;
22412280 } ;
2281+ let limit = attribute_count_limit ( ) ;
22422282 let mut state = span. 0 . borrow_mut ( ) ;
22432283 if let OtelSpanState :: Recording ( span) = & mut * * state {
22442284 let Some ( ( attributes, dropped_attributes_count) ) =
22452285 span_attributes ( span, location)
22462286 else {
22472287 return ;
22482288 } ;
2249- attr ! ( scope, attributes => * dropped_attributes_count, key1, value1) ;
2250- attr ! ( scope, attributes => * dropped_attributes_count, key2, value2) ;
2289+ attr ! ( scope, attributes => * dropped_attributes_count, limit , key1, value1) ;
2290+ attr ! ( scope, attributes => * dropped_attributes_count, limit , key2, value2) ;
22512291 }
22522292}
22532293
@@ -2269,16 +2309,17 @@ fn op_otel_span_attribute3<'s>(
22692309 else {
22702310 return ;
22712311 } ;
2312+ let limit = attribute_count_limit ( ) ;
22722313 let mut state = span. 0 . borrow_mut ( ) ;
22732314 if let OtelSpanState :: Recording ( span) = & mut * * state {
22742315 let Some ( ( attributes, dropped_attributes_count) ) =
22752316 span_attributes ( span, location)
22762317 else {
22772318 return ;
22782319 } ;
2279- attr ! ( scope, attributes => * dropped_attributes_count, key1, value1) ;
2280- attr ! ( scope, attributes => * dropped_attributes_count, key2, value2) ;
2281- attr ! ( scope, attributes => * dropped_attributes_count, key3, value3) ;
2320+ attr ! ( scope, attributes => * dropped_attributes_count, limit , key1, value1) ;
2321+ attr ! ( scope, attributes => * dropped_attributes_count, limit , key2, value2) ;
2322+ attr ! ( scope, attributes => * dropped_attributes_count, limit , key3, value3) ;
22822323 }
22832324}
22842325
0 commit comments