diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f2d7eb515b..ae59734125 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -52,8 +52,8 @@ inferno = "0.10.0" tempfile = "3" # opentelemetry example -opentelemetry = { version = "0.16", default-features = false, features = ["trace"] } -opentelemetry-jaeger = "0.15" +opentelemetry = { version = "0.17", default-features = false, features = ["trace"] } +opentelemetry-jaeger = "0.16" # fmt examples snafu = "0.6.10" diff --git a/tracing-opentelemetry/Cargo.toml b/tracing-opentelemetry/Cargo.toml index f5c5eb5421..a03a92cbe4 100644 --- a/tracing-opentelemetry/Cargo.toml +++ b/tracing-opentelemetry/Cargo.toml @@ -17,13 +17,13 @@ categories = [ keywords = ["tracing", "opentelemetry", "jaeger", "zipkin", "async"] license = "MIT" edition = "2018" -rust-version = "1.42.0" +rust-version = "1.46.0" [features] default = ["tracing-log"] [dependencies] -opentelemetry = { version = "0.16", default-features = false, features = ["trace"] } +opentelemetry = { version = "0.17", default-features = false, features = ["trace"] } tracing = { path = "../tracing", version = "0.1", default-features = false, features = ["std"] } tracing-core = { path = "../tracing-core", version = "0.1" } tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, features = ["registry", "std"] } @@ -32,7 +32,7 @@ tracing-log = { path = "../tracing-log", version = "0.1", default-features = fal [dev-dependencies] async-trait = "0.1" criterion = { version = "0.3", default_features = false } -opentelemetry-jaeger = "0.15" +opentelemetry-jaeger = "0.16" [lib] bench = false diff --git a/tracing-opentelemetry/README.md b/tracing-opentelemetry/README.md index 5ce4dbd586..7826485820 100644 --- a/tracing-opentelemetry/README.md +++ b/tracing-opentelemetry/README.md @@ -103,9 +103,9 @@ $ firefox http://localhost:16686/ ## Supported Rust Versions -Tracing is built against the latest stable release. The minimum supported -version is 1.42. The current Tracing version is not guaranteed to build on Rust -versions earlier than the minimum supported version. +Tracing Opentelemetry is built against the latest stable release. The minimum +supported version is 1.46. The current Tracing version is not guaranteed to +build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor diff --git a/tracing-opentelemetry/benches/trace.rs b/tracing-opentelemetry/benches/trace.rs index 298527f32f..e8316309e5 100644 --- a/tracing-opentelemetry/benches/trace.rs +++ b/tracing-opentelemetry/benches/trace.rs @@ -13,11 +13,11 @@ fn many_children(c: &mut Criterion) { group.bench_function("spec_baseline", |b| { let provider = TracerProvider::default(); - let tracer = provider.tracer("bench", None); + let tracer = provider.tracer("bench"); b.iter(|| { fn dummy(tracer: &Tracer, cx: &Context) { for _ in 0..99 { - tracer.start_with_context("child", cx.clone()); + tracer.start_with_context("child", cx); } } @@ -41,7 +41,7 @@ fn many_children(c: &mut Criterion) { { let provider = TracerProvider::default(); - let tracer = provider.tracer("bench", None); + let tracer = provider.tracer("bench"); let otel_layer = tracing_opentelemetry::layer() .with_tracer(tracer) .with_tracked_inactivity(false); @@ -96,8 +96,7 @@ where let span = ctx.span(id).expect("Span not found, this is a bug"); let mut extensions = span.extensions_mut(); extensions.insert( - SpanBuilder::from_name(attrs.metadata().name().to_string()) - .with_start_time(SystemTime::now()), + SpanBuilder::from_name(attrs.metadata().name()).with_start_time(SystemTime::now()), ); } diff --git a/tracing-opentelemetry/src/layer.rs b/tracing-opentelemetry/src/layer.rs index 87068d405e..2abbb4a4d6 100644 --- a/tracing-opentelemetry/src/layer.rs +++ b/tracing-opentelemetry/src/layer.rs @@ -1,4 +1,4 @@ -use crate::PreSampledTracer; +use crate::{OtelData, PreSampledTracer}; use opentelemetry::{ trace::{self as otel, noop, TraceContextExt}, Context as OtelContext, Key, KeyValue, @@ -69,11 +69,7 @@ where // // See https://github.com/tokio-rs/tracing/blob/4dad420ee1d4607bad79270c1520673fa6266a3d/tracing-error/src/layer.rs pub(crate) struct WithContext( - fn( - &tracing::Dispatch, - &span::Id, - f: &mut dyn FnMut(&mut otel::SpanBuilder, &dyn PreSampledTracer), - ), + fn(&tracing::Dispatch, &span::Id, f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer)), ); impl WithContext { @@ -83,7 +79,7 @@ impl WithContext { &self, dispatch: &'a tracing::Dispatch, id: &span::Id, - mut f: impl FnMut(&mut otel::SpanBuilder, &dyn PreSampledTracer), + mut f: impl FnMut(&mut OtelData, &dyn PreSampledTracer), ) { (self.0)(dispatch, id, &mut f) } @@ -360,7 +356,7 @@ where let span = ctx.span(parent).expect("Span not found, this is a bug"); let mut extensions = span.extensions_mut(); extensions - .get_mut::() + .get_mut::() .map(|builder| self.tracer.sampled_context(builder)) .unwrap_or_default() // Else if the span is inferred from context, look up any available current span. @@ -369,7 +365,7 @@ where .and_then(|span| { let mut extensions = span.extensions_mut(); extensions - .get_mut::() + .get_mut::() .map(|builder| self.tracer.sampled_context(builder)) }) .unwrap_or_else(OtelContext::current) @@ -382,7 +378,7 @@ where fn get_context( dispatch: &tracing::Dispatch, id: &span::Id, - f: &mut dyn FnMut(&mut otel::SpanBuilder, &dyn PreSampledTracer), + f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer), ) { let subscriber = dispatch .downcast_ref::() @@ -395,7 +391,7 @@ where .expect("layer should downcast to expected type; this is a bug!"); let mut extensions = span.extensions_mut(); - if let Some(builder) = extensions.get_mut::() { + if let Some(builder) = extensions.get_mut::() { f(builder, &layer.tracer); } } @@ -418,16 +414,16 @@ where extensions.insert(Timings::new()); } + let parent_cx = self.parent_context(attrs, &ctx); let mut builder = self .tracer .span_builder(attrs.metadata().name()) .with_start_time(SystemTime::now()) - .with_parent_context(self.parent_context(attrs, &ctx)) // Eagerly assign span id so children have stable parent id .with_span_id(self.tracer.new_span_id()); // Record new trace id if there is no active parent span - if !builder.parent_context.has_active_span() { + if !parent_cx.has_active_span() { builder.trace_id = Some(self.tracer.new_trace_id()); } @@ -450,7 +446,7 @@ where } attrs.record(&mut SpanAttributeVisitor(&mut builder)); - extensions.insert(builder); + extensions.insert(OtelData { builder, parent_cx }); } fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) { @@ -489,37 +485,37 @@ where fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) { let span = ctx.span(id).expect("Span not found, this is a bug"); let mut extensions = span.extensions_mut(); - if let Some(builder) = extensions.get_mut::() { - values.record(&mut SpanAttributeVisitor(builder)); + if let Some(data) = extensions.get_mut::() { + values.record(&mut SpanAttributeVisitor(&mut data.builder)); } } fn on_follows_from(&self, id: &Id, follows: &Id, ctx: Context) { let span = ctx.span(id).expect("Span not found, this is a bug"); let mut extensions = span.extensions_mut(); - let builder = extensions - .get_mut::() - .expect("Missing SpanBuilder span extensions"); + let data = extensions + .get_mut::() + .expect("Missing otel data span extensions"); let follows_span = ctx .span(follows) .expect("Span to follow not found, this is a bug"); let mut follows_extensions = follows_span.extensions_mut(); - let follows_builder = follows_extensions - .get_mut::() - .expect("Missing SpanBuilder span extensions"); + let follows_data = follows_extensions + .get_mut::() + .expect("Missing otel data span extensions"); let follows_context = self .tracer - .sampled_context(follows_builder) + .sampled_context(follows_data) .span() .span_context() .clone(); let follows_link = otel::Link::new(follows_context, Vec::new()); - if let Some(ref mut links) = builder.links { + if let Some(ref mut links) = data.builder.links { links.push(follows_link); } else { - builder.links = Some(vec![follows_link]); + data.builder.links = Some(vec![follows_link]); } } @@ -554,7 +550,7 @@ where event.record(&mut SpanEventVisitor(&mut otel_event)); let mut extensions = span.extensions_mut(); - if let Some(builder) = extensions.get_mut::() { + if let Some(OtelData { builder, .. }) = extensions.get_mut::() { if builder.status_code.is_none() && *meta.level() == tracing_core::Level::ERROR { builder.status_code = Some(otel::StatusCode::Error); } @@ -574,7 +570,12 @@ where fn on_close(&self, id: span::Id, ctx: Context<'_, S>) { let span = ctx.span(&id).expect("Span not found, this is a bug"); let mut extensions = span.extensions_mut(); - if let Some(mut builder) = extensions.remove::() { + + if let Some(OtelData { + mut builder, + parent_cx, + }) = extensions.remove::() + { if self.tracked_inactivity { // Append busy/idle timings when enabled. if let Some(timings) = extensions.get_mut::() { @@ -591,7 +592,9 @@ where } // Assign end time, build and start span, drop span to export - builder.with_end_time(SystemTime::now()).start(&self.tracer); + builder + .with_end_time(SystemTime::now()) + .start_with_context(&self.tracer, &parent_cx); } } @@ -627,6 +630,7 @@ impl Timings { #[cfg(test)] mod tests { use super::*; + use crate::OtelData; use opentelemetry::trace::{noop, SpanKind, TraceFlags}; use std::borrow::Cow; use std::sync::{Arc, Mutex}; @@ -634,17 +638,14 @@ mod tests { use tracing_subscriber::prelude::*; #[derive(Debug, Clone)] - struct TestTracer(Arc>>); + struct TestTracer(Arc>>); impl otel::Tracer for TestTracer { type Span = noop::NoopSpan; - fn invalid(&self) -> Self::Span { - noop::NoopSpan::new() - } - fn start_with_context(&self, _name: T, _context: OtelContext) -> Self::Span + fn start_with_context(&self, _name: T, _context: &OtelContext) -> Self::Span where T: Into>, { - self.invalid() + noop::NoopSpan::new() } fn span_builder(&self, name: T) -> otel::SpanBuilder where @@ -652,28 +653,41 @@ mod tests { { otel::SpanBuilder::from_name(name) } - fn build(&self, builder: otel::SpanBuilder) -> Self::Span { - *self.0.lock().unwrap() = Some(builder); - self.invalid() + fn build_with_context( + &self, + builder: otel::SpanBuilder, + parent_cx: &OtelContext, + ) -> Self::Span { + *self.0.lock().unwrap() = Some(OtelData { + builder, + parent_cx: parent_cx.clone(), + }); + noop::NoopSpan::new() } } impl PreSampledTracer for TestTracer { - fn sampled_context(&self, _builder: &mut otel::SpanBuilder) -> OtelContext { + fn sampled_context(&self, _builder: &mut crate::OtelData) -> OtelContext { OtelContext::new() } fn new_trace_id(&self) -> otel::TraceId { - otel::TraceId::invalid() + otel::TraceId::INVALID } fn new_span_id(&self) -> otel::SpanId { - otel::SpanId::invalid() + otel::SpanId::INVALID } } #[derive(Debug, Clone)] struct TestSpan(otel::SpanContext); impl otel::Span for TestSpan { - fn add_event_with_timestamp(&mut self, _: String, _: SystemTime, _: Vec) {} + fn add_event_with_timestamp>>( + &mut self, + _: T, + _: SystemTime, + _: Vec, + ) { + } fn span_context(&self) -> &otel::SpanContext { &self.0 } @@ -682,7 +696,7 @@ mod tests { } fn set_attribute(&mut self, _attribute: KeyValue) {} fn set_status(&mut self, _code: otel::StatusCode, _message: String) {} - fn update_name(&mut self, _new_name: String) {} + fn update_name>>(&mut self, _new_name: T) {} fn end_with_timestamp(&mut self, _timestamp: SystemTime) {} } @@ -696,7 +710,12 @@ mod tests { tracing::debug_span!("static_name", otel.name = dynamic_name.as_str()); }); - let recorded_name = tracer.0.lock().unwrap().as_ref().map(|b| b.name.clone()); + let recorded_name = tracer + .0 + .lock() + .unwrap() + .as_ref() + .map(|b| b.builder.name.clone()); assert_eq!(recorded_name, Some(dynamic_name.into())) } @@ -709,7 +728,15 @@ mod tests { tracing::debug_span!("request", otel.kind = %SpanKind::Server); }); - let recorded_kind = tracer.0.lock().unwrap().as_ref().unwrap().span_kind.clone(); + let recorded_kind = tracer + .0 + .lock() + .unwrap() + .as_ref() + .unwrap() + .builder + .span_kind + .clone(); assert_eq!(recorded_kind, Some(otel::SpanKind::Server)) } @@ -721,7 +748,14 @@ mod tests { tracing::subscriber::with_default(subscriber, || { tracing::debug_span!("request", otel.status_code = ?otel::StatusCode::Ok); }); - let recorded_status_code = tracer.0.lock().unwrap().as_ref().unwrap().status_code; + let recorded_status_code = tracer + .0 + .lock() + .unwrap() + .as_ref() + .unwrap() + .builder + .status_code; assert_eq!(recorded_status_code, Some(otel::StatusCode::Ok)) } @@ -742,6 +776,7 @@ mod tests { .unwrap() .as_ref() .unwrap() + .builder .status_message .clone(); @@ -752,10 +787,10 @@ mod tests { fn trace_id_from_existing_context() { let tracer = TestTracer(Arc::new(Mutex::new(None))); let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - let trace_id = otel::TraceId::from_u128(42); + let trace_id = otel::TraceId::from(42u128.to_be_bytes()); let existing_cx = OtelContext::current_with_span(TestSpan(otel::SpanContext::new( trace_id, - otel::SpanId::from_u64(1), + otel::SpanId::from(1u64.to_be_bytes()), TraceFlags::default(), false, Default::default(), @@ -772,10 +807,42 @@ mod tests { .unwrap() .as_ref() .unwrap() - .parent_context + .parent_cx .span() .span_context() .trace_id(); assert_eq!(recorded_trace_id, trace_id) } + + #[test] + fn includes_timings() { + let tracer = TestTracer(Arc::new(Mutex::new(None))); + let subscriber = tracing_subscriber::registry().with( + layer() + .with_tracer(tracer.clone()) + .with_tracked_inactivity(true), + ); + + tracing::subscriber::with_default(subscriber, || { + tracing::debug_span!("request"); + }); + + let attributes = tracer + .0 + .lock() + .unwrap() + .as_ref() + .unwrap() + .builder + .attributes + .as_ref() + .unwrap() + .clone(); + let keys = attributes + .iter() + .map(|attr| attr.key.as_str()) + .collect::>(); + assert!(keys.contains(&"idle_ns")); + assert!(keys.contains(&"busy_ns")); + } } diff --git a/tracing-opentelemetry/src/lib.rs b/tracing-opentelemetry/src/lib.rs index f66761240d..35c0895c97 100644 --- a/tracing-opentelemetry/src/lib.rs +++ b/tracing-opentelemetry/src/lib.rs @@ -109,3 +109,15 @@ mod tracer; pub use layer::{layer, OpenTelemetryLayer}; pub use span_ext::OpenTelemetrySpanExt; pub use tracer::PreSampledTracer; + +/// Per-span OpenTelemetry data tracked by this crate. +/// +/// Useful for implementing [PreSampledTracer] in alternate otel SDKs. +#[derive(Debug, Clone)] +pub struct OtelData { + /// The parent otel `Context` for the current tracing span. + pub parent_cx: opentelemetry::Context, + + /// The otel span data recorded during the current tracing span. + pub builder: opentelemetry::trace::SpanBuilder, +} diff --git a/tracing-opentelemetry/src/span_ext.rs b/tracing-opentelemetry/src/span_ext.rs index 33c1adc9be..98912dc79f 100644 --- a/tracing-opentelemetry/src/span_ext.rs +++ b/tracing-opentelemetry/src/span_ext.rs @@ -121,9 +121,9 @@ impl OpenTelemetrySpanExt for tracing::Span { let mut cx = Some(cx); self.with_subscriber(move |(id, subscriber)| { if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, move |builder, _tracer| { + get_context.with_context(subscriber, id, move |data, _tracer| { if let Some(cx) = cx.take() { - builder.parent_context = cx; + data.parent_cx = cx; } }); } @@ -140,11 +140,11 @@ impl OpenTelemetrySpanExt for tracing::Span { let mut att = Some(attributes); self.with_subscriber(move |(id, subscriber)| { if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, move |builder, _tracer| { + get_context.with_context(subscriber, id, move |data, _tracer| { if let Some(cx) = cx.take() { let attr = att.take().unwrap_or_default(); let follows_link = opentelemetry::trace::Link::new(cx, attr); - builder + data.builder .links .get_or_insert_with(|| Vec::with_capacity(1)) .push(follows_link); diff --git a/tracing-opentelemetry/src/tracer.rs b/tracing-opentelemetry/src/tracer.rs index bc6be57c10..9cd6d378d5 100644 --- a/tracing-opentelemetry/src/tracer.rs +++ b/tracing-opentelemetry/src/tracer.rs @@ -40,7 +40,7 @@ pub trait PreSampledTracer { /// /// The sampling decision, span context information, and parent context /// values must match the values recorded when the tracing span is closed. - fn sampled_context(&self, builder: &mut otel::SpanBuilder) -> OtelContext; + fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext; /// Generate a new trace id. fn new_trace_id(&self) -> otel::TraceId; @@ -50,27 +50,28 @@ pub trait PreSampledTracer { } impl PreSampledTracer for noop::NoopTracer { - fn sampled_context(&self, builder: &mut otel::SpanBuilder) -> OtelContext { - builder.parent_context.clone() + fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { + data.parent_cx.clone() } fn new_trace_id(&self) -> otel::TraceId { - otel::TraceId::invalid() + otel::TraceId::INVALID } fn new_span_id(&self) -> otel::SpanId { - otel::SpanId::invalid() + otel::SpanId::INVALID } } impl PreSampledTracer for Tracer { - fn sampled_context(&self, builder: &mut otel::SpanBuilder) -> OtelContext { + fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { // Ensure tracing pipeline is still installed. if self.provider().is_none() { return OtelContext::new(); } let provider = self.provider().unwrap(); - let parent_cx = &builder.parent_context; + let parent_cx = &data.parent_cx; + let builder = &mut data.builder; // Gather trace state let (no_parent, trace_id, remote_parent, parent_trace_flags) = @@ -87,6 +88,7 @@ impl PreSampledTracer for Tracer { builder.span_kind.as_ref().unwrap_or(&SpanKind::Internal), builder.attributes.as_deref().unwrap_or(&[]), builder.links.as_deref().unwrap_or(&[]), + self.instrumentation_library(), )); process_sampling_result( @@ -102,7 +104,7 @@ impl PreSampledTracer for Tracer { } .unwrap_or_default(); - let span_id = builder.span_id.unwrap_or_else(SpanId::invalid); + let span_id = builder.span_id.unwrap_or(SpanId::INVALID); let span_context = SpanContext::new(trace_id, span_id, flags, false, trace_state); parent_cx.with_remote_span_context(span_context) } @@ -110,13 +112,13 @@ impl PreSampledTracer for Tracer { fn new_trace_id(&self) -> otel::TraceId { self.provider() .map(|provider| provider.config().id_generator.new_trace_id()) - .unwrap_or_else(otel::TraceId::invalid) + .unwrap_or(otel::TraceId::INVALID) } fn new_span_id(&self) -> otel::SpanId { self.provider() .map(|provider| provider.config().id_generator.new_span_id()) - .unwrap_or_else(otel::SpanId::invalid) + .unwrap_or(otel::SpanId::INVALID) } } @@ -166,17 +168,19 @@ fn process_sampling_result( #[cfg(test)] mod tests { use super::*; + use crate::OtelData; use opentelemetry::sdk::trace::{config, Sampler, TracerProvider}; use opentelemetry::trace::{SpanBuilder, SpanId, TracerProvider as _}; #[test] fn assigns_default_trace_id_if_missing() { let provider = TracerProvider::default(); - let tracer = provider.tracer("test", None); + let tracer = provider.tracer("test"); let mut builder = SpanBuilder::from_name("empty".to_string()); - builder.span_id = Some(SpanId::from_u64(1)); + builder.span_id = Some(SpanId::from(1u64.to_be_bytes())); builder.trace_id = None; - let cx = tracer.sampled_context(&mut builder); + let parent_cx = OtelContext::new(); + let cx = tracer.sampled_context(&mut OtelData { builder, parent_cx }); let span = cx.span(); let span_context = span.span_context(); @@ -212,11 +216,10 @@ mod tests { let provider = TracerProvider::builder() .with_config(config().with_sampler(sampler)) .build(); - let tracer = provider.tracer("test", None); + let tracer = provider.tracer("test"); let mut builder = SpanBuilder::from_name("parent".to_string()); - builder.parent_context = parent_cx; builder.sampling_result = previous_sampling_result; - let sampled = tracer.sampled_context(&mut builder); + let sampled = tracer.sampled_context(&mut OtelData { builder, parent_cx }); assert_eq!( sampled.span().span_context().is_sampled(), @@ -229,8 +232,8 @@ mod tests { fn span_context(trace_flags: TraceFlags, is_remote: bool) -> SpanContext { SpanContext::new( - TraceId::from_u128(1), - SpanId::from_u64(1), + TraceId::from(1u128.to_be_bytes()), + SpanId::from(1u64.to_be_bytes()), trace_flags, is_remote, Default::default(), diff --git a/tracing-opentelemetry/tests/trace_state_propagation.rs b/tracing-opentelemetry/tests/trace_state_propagation.rs index 8b0255269a..0f92bc47aa 100644 --- a/tracing-opentelemetry/tests/trace_state_propagation.rs +++ b/tracing-opentelemetry/tests/trace_state_propagation.rs @@ -115,7 +115,7 @@ fn test_tracer() -> (Tracer, TracerProvider, TestExporter, impl Subscriber) { let provider = TracerProvider::builder() .with_simple_exporter(exporter.clone()) .build(); - let tracer = provider.tracer("test", None); + let tracer = provider.tracer("test"); let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); (tracer, provider, exporter, subscriber)