Skip to content

Commit 159135c

Browse files
feat: Add is_remote flag in exporter for spans and span links (#3153)
1 parent b7ff11b commit 159135c

File tree

11 files changed

+142
-4
lines changed

11 files changed

+142
-4
lines changed

opentelemetry-otlp/src/exporter/http/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ mod tests {
10471047
SpanData {
10481048
span_context,
10491049
parent_span_id: SpanId::from(0),
1050+
parent_span_is_remote: false,
10501051
span_kind: SpanKind::Internal,
10511052
name: Cow::Borrowed("test_span"),
10521053
start_time: SystemTime::UNIX_EPOCH,

opentelemetry-proto/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## vNext
44

5+
- **Feature**: Add span flags support for `isRemote` property in OTLP trace transformation ([#3153](https://github.com/open-telemetry/opentelemetry-rust/pull/3153))
6+
- Updated span and link transformations to properly set flags field (0x100 for local, 0x300 for remote)
7+
58
## 0.30.1
69

710
Released 2025-Sep-11

opentelemetry-proto/src/transform/trace.rs

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
#[cfg(feature = "gen-tonic-messages")]
2+
/// Builds span flags based on the parent span's remote property.
3+
/// This follows the OTLP specification for span flags.
4+
pub(crate) fn build_span_flags(parent_span_is_remote: bool, base_flags: u32) -> u32 {
5+
use crate::proto::tonic::trace::v1::SpanFlags;
6+
let mut flags = base_flags;
7+
flags |= SpanFlags::ContextHasIsRemoteMask as u32;
8+
if parent_span_is_remote {
9+
flags |= SpanFlags::ContextIsRemoteMask as u32;
10+
}
11+
flags
12+
}
13+
114
#[cfg(feature = "gen-tonic-messages")]
215
pub mod tonic {
316
use crate::proto::tonic::resource::v1::Resource;
@@ -41,7 +54,10 @@ pub mod tonic {
4154
trace_state: link.span_context.trace_state().header(),
4255
attributes: Attributes::from(link.attributes).0,
4356
dropped_attributes_count: link.dropped_attributes_count,
44-
flags: link.span_context.trace_flags().to_u8() as u32,
57+
flags: super::build_span_flags(
58+
link.span_context.is_remote(),
59+
link.span_context.trace_flags().to_u8() as u32,
60+
),
4561
}
4662
}
4763
}
@@ -59,7 +75,10 @@ pub mod tonic {
5975
vec![]
6076
}
6177
},
62-
flags: source_span.span_context.trace_flags().to_u8() as u32,
78+
flags: super::build_span_flags(
79+
source_span.parent_span_is_remote,
80+
source_span.span_context.trace_flags().to_u8() as u32,
81+
),
6382
name: source_span.name.into_owned(),
6483
kind: span_kind as i32,
6584
start_time_unix_nano: to_nanos(source_span.start_time),
@@ -118,7 +137,10 @@ pub mod tonic {
118137
vec![]
119138
}
120139
},
121-
flags: source_span.span_context.trace_flags().to_u8() as u32,
140+
flags: super::build_span_flags(
141+
source_span.parent_span_is_remote,
142+
source_span.span_context.trace_flags().to_u8() as u32,
143+
),
122144
name: source_span.name.into_owned(),
123145
kind: span_kind as i32,
124146
start_time_unix_nano: to_nanos(source_span.start_time),
@@ -191,6 +213,101 @@ pub mod tonic {
191213
}
192214
}
193215

216+
#[cfg(all(test, feature = "gen-tonic-messages"))]
217+
mod span_flags_tests {
218+
use crate::proto::tonic::trace::v1::{Span, SpanFlags};
219+
use opentelemetry::trace::{SpanContext, SpanId, TraceFlags, TraceId, TraceState};
220+
use opentelemetry::InstrumentationScope;
221+
use opentelemetry_sdk::trace::SpanData;
222+
use std::borrow::Cow;
223+
224+
#[test]
225+
fn test_build_span_flags_local_parent() {
226+
let flags = super::build_span_flags(false, 0); // is_remote = false
227+
assert_eq!(flags, SpanFlags::ContextHasIsRemoteMask as u32); // 0x100
228+
}
229+
230+
#[test]
231+
fn test_build_span_flags_remote_parent() {
232+
let flags = super::build_span_flags(true, 0); // is_remote = true
233+
assert_eq!(
234+
flags,
235+
(SpanFlags::ContextHasIsRemoteMask as u32) | (SpanFlags::ContextIsRemoteMask as u32)
236+
); // 0x300
237+
}
238+
239+
#[test]
240+
fn test_build_span_flags_no_parent() {
241+
let flags = super::build_span_flags(false, 0); // no parent = false
242+
assert_eq!(flags, SpanFlags::ContextHasIsRemoteMask as u32); // 0x100
243+
}
244+
245+
#[test]
246+
fn test_build_span_flags_preserves_base_flags() {
247+
let flags = super::build_span_flags(false, 0x01); // SAMPLED flag, local parent
248+
assert_eq!(flags, 0x01 | (SpanFlags::ContextHasIsRemoteMask as u32)); // 0x101
249+
}
250+
251+
#[test]
252+
fn test_span_transformation_with_flags() {
253+
let span_data = SpanData {
254+
span_context: SpanContext::new(
255+
TraceId::from(789),
256+
SpanId::from(101112),
257+
TraceFlags::default(),
258+
false,
259+
TraceState::default(),
260+
),
261+
parent_span_id: SpanId::from(456),
262+
parent_span_is_remote: false,
263+
span_kind: opentelemetry::trace::SpanKind::Internal,
264+
name: Cow::Borrowed("test_span"),
265+
start_time: std::time::SystemTime::now(),
266+
end_time: std::time::SystemTime::now(),
267+
attributes: vec![],
268+
dropped_attributes_count: 0,
269+
events: opentelemetry_sdk::trace::SpanEvents::default(),
270+
links: opentelemetry_sdk::trace::SpanLinks::default(),
271+
status: opentelemetry::trace::Status::Unset,
272+
instrumentation_scope: InstrumentationScope::builder("test").build(),
273+
};
274+
275+
let otlp_span: Span = span_data.into();
276+
assert_eq!(otlp_span.flags, SpanFlags::ContextHasIsRemoteMask as u32); // 0x100
277+
}
278+
279+
#[test]
280+
fn test_span_transformation_with_remote_parent() {
281+
let span_data = SpanData {
282+
span_context: SpanContext::new(
283+
TraceId::from(789),
284+
SpanId::from(101112),
285+
TraceFlags::default(),
286+
false,
287+
TraceState::default(),
288+
),
289+
parent_span_id: SpanId::from(456),
290+
parent_span_is_remote: true,
291+
span_kind: opentelemetry::trace::SpanKind::Internal,
292+
name: Cow::Borrowed("test_span"),
293+
start_time: std::time::SystemTime::now(),
294+
end_time: std::time::SystemTime::now(),
295+
attributes: vec![],
296+
dropped_attributes_count: 0,
297+
events: opentelemetry_sdk::trace::SpanEvents::default(),
298+
links: opentelemetry_sdk::trace::SpanLinks::default(),
299+
status: opentelemetry::trace::Status::Unset,
300+
instrumentation_scope: InstrumentationScope::builder("test").build(),
301+
};
302+
303+
let otlp_span: Span = span_data.into();
304+
assert_eq!(
305+
otlp_span.flags,
306+
(SpanFlags::ContextHasIsRemoteMask as u32) | (SpanFlags::ContextIsRemoteMask as u32)
307+
); // 0x300
308+
}
309+
}
310+
194311
#[cfg(test)]
195312
mod tests {
196313
use crate::tonic::common::v1::any_value::Value;
@@ -219,6 +336,7 @@ mod tests {
219336
SpanData {
220337
span_context,
221338
parent_span_id: SpanId::from(0),
339+
parent_span_is_remote: false,
222340
span_kind: SpanKind::Internal,
223341
name: Cow::Borrowed("test_span"),
224342
start_time: now(),

opentelemetry-sdk/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## vNext
44

5+
- **Feature**: Add span flags support for `isRemote` property in OTLP exporter ([#3153](https://github.com/open-telemetry/opentelemetry-rust/pull/3153))
6+
- Updated span and link transformations to properly set flags field (0x100 for local, 0x300 for remote)
7+
58
- TODO: Placeholder for Span processor related things
69
- *Fix* SpanProcessor::on_start is no longer called on non recording spans
710
- **Fix**: Restore true parallel exports in the async-native `BatchSpanProcessor` by honoring `OTEL_BSP_MAX_CONCURRENT_EXPORTS` ([#2959](https://github.com/open-telemetry/opentelemetry-rust/pull/3028)). A regression in [#2685](https://github.com/open-telemetry/opentelemetry-rust/pull/2685) inadvertently awaited the `export()` future directly in `opentelemetry-sdk/src/trace/span_processor_with_async_runtime.rs` instead of spawning it on the runtime, forcing all exports to run sequentially.

opentelemetry-sdk/benches/batch_span_processor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ fn get_span_data() -> Vec<SpanData> {
2222
TraceState::default(),
2323
),
2424
parent_span_id: SpanId::from(12),
25+
parent_span_is_remote: false,
2526
span_kind: SpanKind::Client,
2627
name: Default::default(),
2728
start_time: now(),

opentelemetry-sdk/src/testing/trace/span_exporters.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub fn new_test_export_span_data() -> SpanData {
2121
TraceState::default(),
2222
),
2323
parent_span_id: SpanId::INVALID,
24+
parent_span_is_remote: false,
2425
span_kind: SpanKind::Internal,
2526
name: "opentelemetry".into(),
2627
start_time: opentelemetry::time::now(),

opentelemetry-sdk/src/trace/export.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub struct SpanData {
8282
pub span_context: SpanContext,
8383
/// Span parent id
8484
pub parent_span_id: SpanId,
85+
/// Parent span is remote flag (for span flags)
86+
pub parent_span_is_remote: bool,
8587
/// Span kind
8688
pub span_kind: SpanKind,
8789
/// Span name

opentelemetry-sdk/src/trace/span.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub struct Span {
2727
pub(crate) struct SpanData {
2828
/// Span parent id
2929
pub(crate) parent_span_id: SpanId,
30+
/// Parent span is remote flag (for span flags)
31+
pub(crate) parent_span_is_remote: bool,
3032
/// Span kind
3133
pub(crate) span_kind: SpanKind,
3234
/// Span name
@@ -254,6 +256,7 @@ fn build_export_data(
254256
crate::trace::SpanData {
255257
span_context,
256258
parent_span_id: data.parent_span_id,
259+
parent_span_is_remote: data.parent_span_is_remote,
257260
span_kind: data.span_kind,
258261
name: data.name,
259262
start_time: data.start_time,
@@ -286,6 +289,7 @@ mod tests {
286289
let tracer = provider.tracer("opentelemetry");
287290
let data = SpanData {
288291
parent_span_id: SpanId::from(0),
292+
parent_span_is_remote: false,
289293
span_kind: trace::SpanKind::Internal,
290294
name: "opentelemetry".into(),
291295
start_time: opentelemetry::time::now(),

opentelemetry-sdk/src/trace/span_processor.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ mod tests {
968968
let unsampled = SpanData {
969969
span_context: SpanContext::empty_context(),
970970
parent_span_id: SpanId::INVALID,
971+
parent_span_is_remote: false,
971972
span_kind: SpanKind::Internal,
972973
name: "opentelemetry".into(),
973974
start_time: opentelemetry::time::now(),
@@ -1129,6 +1130,7 @@ mod tests {
11291130
SpanData {
11301131
span_context: SpanContext::empty_context(),
11311132
parent_span_id: SpanId::INVALID,
1133+
parent_span_is_remote: false,
11321134
span_kind: SpanKind::Internal,
11331135
name: name.to_string().into(),
11341136
start_time: opentelemetry::time::now(),
@@ -1256,6 +1258,7 @@ mod tests {
12561258
let exporter_shared = exporter.exported_spans.clone(); // Shared access to verify exported spans
12571259
let config = BatchConfigBuilder::default()
12581260
.with_max_queue_size(2) // Small queue size to test span dropping
1261+
.with_max_export_batch_size(512) // Explicitly set to avoid env var override
12591262
.with_scheduled_delay(Duration::from_secs(5))
12601263
.build();
12611264
let processor = BatchSpanProcessor::new(exporter, config);
@@ -1270,7 +1273,7 @@ mod tests {
12701273
processor.on_end(span3.clone()); // This span exceeds the queue size
12711274

12721275
// Wait for the scheduled delay to expire
1273-
std::thread::sleep(Duration::from_secs(3));
1276+
std::thread::sleep(Duration::from_secs(6));
12741277

12751278
let exported_spans = exporter_shared.lock().unwrap();
12761279

opentelemetry-sdk/src/trace/tracer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ impl SdkTracer {
137137
sc,
138138
Some(SpanData {
139139
parent_span_id: psc.span_id(),
140+
parent_span_is_remote: psc.is_valid() && psc.is_remote(),
140141
span_kind: builder.span_kind.take().unwrap_or(SpanKind::Internal),
141142
name,
142143
start_time,

0 commit comments

Comments
 (0)