Skip to content

Commit cc87dd9

Browse files
authored
feat(appender-tracing): stabilize span attribute propagation (#3482)
1 parent f290595 commit cc87dd9

6 files changed

Lines changed: 310 additions & 102 deletions

File tree

opentelemetry-appender-tracing/CHANGELOG.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,31 @@
22

33
## vNext
44

5-
- New *experimental* feature to enrich log records with attributes from active
6-
tracing spans (`experimental_span_attributes`). Use
7-
`OpenTelemetryTracingBridge::builder()` with `with_span_attribute_allowlist`
8-
to control which span attributes are copied to log records.
5+
- **Add tracing span attribute enrichment.** When enabled, attributes attached
6+
to active [`tracing`] spans are copied onto each emitted log record. "Span"
7+
here refers to a [`tracing::span!`][tracing-span] from the [`tracing`] crate
8+
(the appender's source), **not** an OpenTelemetry span.
9+
10+
Enrichment is **disabled by default** (no per-span overhead) and must be
11+
opted into at runtime via a single builder method that accepts a
12+
[`TracingSpanAttributes`] value:
13+
14+
```rust
15+
use opentelemetry_appender_tracing::layer::TracingSpanAttributes;
16+
17+
// Copy all tracing-span attributes onto log records:
18+
let layer = OpenTelemetryTracingBridge::builder(&provider)
19+
.with_tracing_span_attributes(TracingSpanAttributes::all())
20+
.build();
21+
22+
// Or copy only an allowlist of attributes:
23+
let layer = OpenTelemetryTracingBridge::builder(&provider)
24+
.with_tracing_span_attributes(TracingSpanAttributes::allowlist(["session.id"]))
25+
.build();
26+
```
27+
28+
[`tracing`]: https://crates.io/crates/tracing
29+
[tracing-span]: https://docs.rs/tracing/latest/tracing/macro.span.html
930

1031
- Remove the `experimental_use_tracing_span_context` since
1132
`tracing-opentelemetry` now supports [activating][31901] the OpenTelemetry

opentelemetry-appender-tracing/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ pprof = { workspace = true }
3434
[features]
3535
default = []
3636
experimental_metadata_attributes = ["dep:tracing-log"]
37-
experimental_span_attributes = []
3837
bench_profiling = []
3938

4039
[[bench]]

opentelemetry-appender-tracing/benches/logs.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
1515
Hardware: Apple M4 Pro
1616
Total Number of Cores: 14 (10 performance and 4 efficiency)
17+
rustc 1.95.0 (59807616e 2026-04-14)
1718
| Test | Average time|
1819
|-----------------------------|-------------|
19-
| log_no_subscriber | 285 ps |
20-
| noop_layer_disabled | 8 ns |
21-
| noop_layer_enabled | 14 ns |
22-
| ot_layer_disabled | 12 ns |
23-
| ot_layer_enabled | 130 ns |
20+
| log_no_subscriber | 262 ps |
21+
| noop_layer_disabled | 4 ns |
22+
| noop_layer_enabled | 12 ns |
23+
| ot_layer_disabled | 7 ns |
24+
| ot_layer_enabled | 122 ns |
2425
*/
2526

2627
use criterion::{criterion_group, criterion_main, Criterion};

opentelemetry-appender-tracing/benches/span-attributes.rs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
/*
22
// Run this benchmark with:
3-
// cargo bench --bench span-attributes --features experimental_span_attributes
3+
// cargo bench --bench span-attributes
44
// The benchmark results:
55
// Hardware: Mac M4 Pro
66
// Total Number of Cores: 14 (10 performance and 4 efficiency)
7-
// rustc 1.93.0 (254b59607 2026-01-19)
8-
// cargo 1.93.0 (083ac5135 2025-12-15)
7+
// rustc 1.95.0 (59807616e 2026-04-14)
8+
// cargo 1.95.0 (f2d3ce0bd 2026-03-21)
99
//
1010
// Attribute counts are chosen to stay within the SDK's inline-array optimisation
11-
// threshold (5 attributes). With `experimental_span_attributes` enabled, span
11+
// threshold (5 attributes). With tracing-span attribute enrichment enabled (via
12+
// `OpenTelemetryTracingBridgeBuilder::with_tracing_span_attributes`), tracing-span
1213
// attributes are copied onto the log record, so total = log attrs + span attrs.
1314
//
1415
// Log + span benchmarks (showing incremental cost of span attributes on logging):
1516
// | Test | Total attrs | Average time | Increment vs baseline |
1617
// |-------------------------------------------|-------------|--------------|------------------------------|
17-
// | log_1_attr_no_span | 1 | 81 ns | - |
18-
// | log_1_attr_in_span_2_attr | 3 | 325 ns | +244 ns |
19-
// | log_1_attr_in_nested_spans_2plus2_attr | 5 | 570 ns | +489 ns (+245 ns vs 1 span) |
18+
// | log_1_attr_no_span | 1 | 85 ns | - |
19+
// | log_1_attr_in_span_2_attr | 3 | 382 ns | +297 ns |
20+
// | log_1_attr_in_nested_spans_2plus2_attr | 5 | 590 ns | +505 ns (+208 ns vs 1 span) |
2021
//
2122
// Span-only benchmarks (no log emission, kept for reference):
2223
// | Test | Average time | Increment |
2324
// |-----------------------|--------------|-----------|
24-
// | span_4_attributes | 175 ns | - |
25-
// | span_8_attributes | 272 ns | +97 ns |
26-
// | nested_spans_1_levels | 183 ns | - |
27-
// | nested_spans_2_levels | 385 ns | +202 ns |
28-
// | nested_spans_3_levels | 583 ns | +198 ns |
25+
// | span_4_attributes | 194 ns | - |
26+
// | span_8_attributes | 314 ns | +120 ns |
27+
// | nested_spans_1_levels | 194 ns | - |
28+
// | nested_spans_2_levels | 413 ns | +219 ns |
29+
// | nested_spans_3_levels | 630 ns | +217 ns |
2930
3031
*/
3132

3233
use criterion::{criterion_group, criterion_main, Criterion};
3334
use opentelemetry::InstrumentationScope;
3435
use opentelemetry_appender_tracing::layer as tracing_layer;
36+
use opentelemetry_appender_tracing::layer::TracingSpanAttributes;
3537
use opentelemetry_sdk::error::OTelSdkResult;
3638
use opentelemetry_sdk::logs::{LogProcessor, SdkLogRecord, SdkLoggerProvider};
3739
use opentelemetry_sdk::Resource;
@@ -67,7 +69,9 @@ fn benchmark_span_attributes(c: &mut Criterion, num_attributes: usize) {
6769
.with_log_processor(NoopProcessor)
6870
.build();
6971

70-
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider);
72+
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::builder(&provider)
73+
.with_tracing_span_attributes(TracingSpanAttributes::all())
74+
.build();
7175
let subscriber = Registry::default().with(ot_layer);
7276

7377
tracing::subscriber::with_default(subscriber, || {
@@ -129,7 +133,9 @@ fn benchmark_nested_spans(c: &mut Criterion, depth: usize) {
129133
.with_log_processor(NoopProcessor)
130134
.build();
131135

132-
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider);
136+
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::builder(&provider)
137+
.with_tracing_span_attributes(TracingSpanAttributes::all())
138+
.build();
133139
let subscriber = Registry::default().with(ot_layer);
134140

135141
tracing::subscriber::with_default(subscriber, || {
@@ -246,7 +252,9 @@ fn make_provider() -> SdkLoggerProvider {
246252
/// 5-attribute inline-array optimisation threshold.
247253
fn benchmark_log_1_attr_no_span(c: &mut Criterion) {
248254
let provider = make_provider();
249-
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider);
255+
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::builder(&provider)
256+
.with_tracing_span_attributes(TracingSpanAttributes::all())
257+
.build();
250258
let subscriber = Registry::default().with(ot_layer);
251259

252260
tracing::subscriber::with_default(subscriber, || {
@@ -264,11 +272,13 @@ fn benchmark_log_1_attr_no_span(c: &mut Criterion) {
264272

265273
/// Emit an error log with 1 attribute while inside a span that carries 2 attributes
266274
/// (total = 3 attrs on the log record, within the SDK's 5-attr inline threshold).
267-
/// With `experimental_span_attributes` the 2 span attributes are propagated onto the
275+
/// With tracing-span attribute enrichment enabled, the 2 span attributes are copied onto the
268276
/// log record, so the delta vs `log_1_attr_no_span` shows the pure span-attribute cost.
269277
fn benchmark_log_1_attr_in_span_2_attr(c: &mut Criterion) {
270278
let provider = make_provider();
271-
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider);
279+
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::builder(&provider)
280+
.with_tracing_span_attributes(TracingSpanAttributes::all())
281+
.build();
272282
let subscriber = Registry::default().with(ot_layer);
273283

274284
tracing::subscriber::with_default(subscriber, || {
@@ -293,7 +303,9 @@ fn benchmark_log_1_attr_in_span_2_attr(c: &mut Criterion) {
293303
/// two spans) vs the same total number of span attributes on a single span.
294304
fn benchmark_log_1_attr_in_nested_spans_2plus2_attr(c: &mut Criterion) {
295305
let provider = make_provider();
296-
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider);
306+
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::builder(&provider)
307+
.with_tracing_span_attributes(TracingSpanAttributes::all())
308+
.build();
297309
let subscriber = Registry::default().with(ot_layer);
298310

299311
tracing::subscriber::with_default(subscriber, || {

0 commit comments

Comments
 (0)