Skip to content

Commit 1121ddf

Browse files
authored
feat(ext/telemetry): honor OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT (#34787)
This honors the OpenTelemetry SDK attribute-count limit environment variables for spans, which were previously ignored (spans recorded an unbounded number of attributes). Resolution order (per spec): 1. `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT` 2. `OTEL_ATTRIBUTE_COUNT_LIMIT` (general fallback) 3. default `128` Once a span's (or span event's / link's) attribute collection reaches the limit, additional attributes are dropped and counted in `droppedAttributesCount`, as required by the spec. When neither env var is set, the default of 128 preserves existing behavior.
1 parent fbeb47c commit 1121ddf

4 files changed

Lines changed: 114 additions & 14 deletions

File tree

ext/telemetry/lib.rs

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

15861623
macro_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

tests/specs/cli/otel_basic/__test__.jsonc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@
167167
},
168168
"output": "grpc_trailers_error.out"
169169
},
170+
"span_attribute_limit": {
171+
"args": "run -A main.ts span_attribute_limit.ts",
172+
"envs": {
173+
"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT": "2"
174+
},
175+
"output": "span_attribute_limit.out"
176+
},
170177
"traces_sampler_always_on": {
171178
"args": "run -A main.ts traces_sampler.ts",
172179
"envs": {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"spans": [
3+
{
4+
"traceId": "00000000000000000000000000000001",
5+
"spanId": "0000000000000001",
6+
"traceState": "",
7+
"parentSpanId": "",
8+
"flags": 1,
9+
"name": "span",
10+
"kind": 1,
11+
"startTimeUnixNano": "[WILDCARD]",
12+
"endTimeUnixNano": "[WILDCARD]",
13+
"attributes": [
14+
{
15+
"key": "a",
16+
"value": {
17+
"stringValue": "1"
18+
}
19+
},
20+
{
21+
"key": "b",
22+
"value": {
23+
"stringValue": "2"
24+
}
25+
}
26+
],
27+
"droppedAttributesCount": 3,
28+
"events": [],
29+
"droppedEventsCount": 0,
30+
"links": [],
31+
"droppedLinksCount": 0,
32+
"status": {
33+
"message": "",
34+
"code": 0
35+
}
36+
}
37+
],
38+
"logs": [],
39+
"metrics": []
40+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2018-2026 the Deno authors. MIT license.
2+
3+
import { trace } from "npm:@opentelemetry/api@1.9.0";
4+
5+
const tracer = trace.getTracer("example-tracer");
6+
7+
tracer.startActiveSpan("span", (span) => {
8+
// With OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=2, only the first two attributes are
9+
// recorded; the rest are dropped and counted in droppedAttributesCount.
10+
span.setAttributes({ a: "1", b: "2", c: "3", d: "4", e: "5" });
11+
span.end();
12+
});

0 commit comments

Comments
 (0)