Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Context method for resolving an Event's SpanRef #1434

Merged
merged 9 commits into from
Jun 23, 2021
141 changes: 141 additions & 0 deletions tracing-subscriber/src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,78 @@ where
}
}

/// Returns a [`SpanRef`] for the parent span of the given [`Event`], if
/// it has a parent.
///
/// If the event has an explicitly overridden parent, this method returns
/// a reference to that span. If the event's parent is the current span,
/// this returns a reference to the current span, if there is one. If this
/// returns `None`, then either the event's parent was explicitly set to
/// `None`, or the event's parent was defined contextually, but no span
/// is currently entered.
///
/// Compared to [`Context::current_span`] and [`Context::lookup_current`],
/// this respects overrides provided by the [`Event`].
///
/// Compared to [`Event::parent`], this automatically falls back to the contextual
/// span, if required.
nightkr marked this conversation as resolved.
Show resolved Hide resolved
///
/// ```rust
/// use tracing::{Event, Subscriber};
/// use tracing_subscriber::{
/// layer::{Context, Layer},
/// prelude::*,
/// registry::LookupSpan,
/// };
///
/// struct PrintingLayer;
/// impl<S> Layer<S> for PrintingLayer
/// where
/// S: Subscriber + for<'lookup> LookupSpan<'lookup>,
/// {
/// fn on_event(&self, event: &Event, ctx: Context<S>) {
/// let span = ctx.event_span(event);
/// println!("Event in span: {:?}", span.map(|s| s.name()));
/// }
/// }
///
/// tracing::subscriber::with_default(tracing_subscriber::registry().with(PrintingLayer), || {
/// tracing::info!("no span");
/// // Prints: Event in span: None
///
/// let span = tracing::info_span!("span");
/// tracing::info!(parent: &span, "explicitly specified");
/// // Prints: Event in span: Some("span")
///
/// let _guard = span.enter();
/// tracing::info!("contextual span");
/// // Prints: Event in span: Some("span")
/// });
/// ```
///
/// <div class="example-wrap" style="display:inline-block">
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to implement the
/// <a href="../registry/trait.LookupSpan.html"><code>LookupSpan</code></a> trait.
/// See the documentation on <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre></div>
#[inline]
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
pub fn event_span(&self, event: &Event<'_>) -> Option<SpanRef<'_, S>>
where
S: for<'lookup> LookupSpan<'lookup>,
{
if event.is_root() {
None
} else if event.is_contextual() {
self.lookup_current()
} else {
event.parent().and_then(|id| self.span(id))
}
}

/// Returns metadata for the span with the given `id`, if it exists.
///
/// If this returns `None`, then no span exists for that ID (either it has
Expand Down Expand Up @@ -1165,6 +1237,36 @@ where
{
Some(self.span(id)?.scope())
}

/// Returns an iterator over the [stored data] for all the spans in the
/// current context, starting with the parent span of the specified event,
/// and ending with the root of the trace tree and ending with the current span.
///
/// <div class="example-wrap" style="display:inline-block">
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: Compared to <a href="#method.scope"><code>scope</code></a> this
/// returns the spans in reverse order (from leaf to root). Use
/// <a href="../registry/struct.Scope.html#method.from_root"><code>Scope::from_root</code></a>
/// in case root-to-leaf ordering is desired.
/// </pre></div>
///
/// <div class="example-wrap" style="display:inline-block">
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to implement the
/// <a href="../registry/trait.LookupSpan.html"><code>LookupSpan</code></a> trait.
/// See the documentation on <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre></div>
///
/// [stored data]: ../registry/struct.SpanRef.html
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
pub fn event_scope(&self, event: &Event<'_>) -> Option<registry::Scope<'_, S>>
where
S: for<'lookup> registry::LookupSpan<'lookup>,
{
Some(self.event_span(event)?.scope())
}
}

impl<'a, S> Context<'a, S> {
Expand Down Expand Up @@ -1194,6 +1296,8 @@ impl Identity {

#[cfg(test)]
pub(crate) mod tests {
use std::sync::{Arc, Mutex};

use super::*;

pub(crate) struct NopSubscriber;
Expand Down Expand Up @@ -1308,4 +1412,41 @@ pub(crate) mod tests {
<dyn Subscriber>::downcast_ref::<StringLayer3>(&s).expect("layer 3 should downcast");
assert_eq!(&layer.0, "layer_3");
}

#[test]
fn context_event_span() {
let last_event_span = Arc::new(Mutex::new(None));

struct RecordingLayer {
last_event_span: Arc<Mutex<Option<&'static str>>>,
}

impl<S> Layer<S> for RecordingLayer
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
let span = ctx.event_span(event);
*self.last_event_span.lock().unwrap() = span.map(|s| s.name());
}
}

tracing::subscriber::with_default(
crate::registry().with(RecordingLayer {
last_event_span: last_event_span.clone(),
}),
|| {
tracing::info!("no span");
assert_eq!(*last_event_span.lock().unwrap(), None);

let parent = tracing::info_span!("explicit");
tracing::info!(parent: &parent, "explicit span");
assert_eq!(*last_event_span.lock().unwrap(), Some("explicit"));

let _guard = tracing::info_span!("contextual").entered();
tracing::info!("contextual span");
assert_eq!(*last_event_span.lock().unwrap(), Some("contextual"));
},
);
}
}