0

I'm trying to instrument a function using tokio tracing, get its arguments and then walk to the parent span and get the values for a handful of fields. I was able to successfully get the argument by creating a new Layer as follows:

impl<S> Layer<S> for MyTestLayer
where
    S: tracing::Subscriber,
    S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
        let span = ctx.span(id).unwrap();
        let mut visitor = MyVisitor{ ... };

        attrs.record(&mut visitor);
        ...
}

This seems to work and lets me get the attributes for the expected span. However, visiting each field/value with a visitor feels like I'm doing something wrong. Is this the best way to get the attribute values?

From here, I'd like to get two fields from the parent span. It seems like I should be able to do something like:

let parent = span.parent().unwrap();
let parent_fields = p.fields();
// ... Read the data from the fields?

However, this doesn't seem to work as fields() provides a FieldSet and no apparent access to the values or ValueSet. What's the proper way of getting a value from the parent field? Is there an easier/better way to do this?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
Julio
  • 2,261
  • 4
  • 30
  • 56

1 Answers1

0

Attributes, and therefore the values contained, are only available when a new span is created. The tracing framework does not keep them around for inspection which keeps the overhead low. It is the Subscriber that is responsible for persisting any values that it needs when processing spans.

Fortunately there's some tools to help. You are already taking advantage of the Layer ecosystem from tracing-subscriber and are even relying on its Registry and the LookupSpan trait as well. The registry provides a general purpose mechanism for storing data with spans, done via Extensions.

What this means is that your MyTestLayer should store any data that will be needed later when processing the parent span, which can then be fetched from the registry when processing the child.

Here's an example that fetches and persists a "test" field value that can be looked-up later:

use std::fmt::Debug;

use tracing::field::{Field, Visit};
use tracing::span::{Attributes, Id};
use tracing_subscriber::layer::{Context, Layer};

struct TestVisitor(Option<String>);

impl Visit for TestVisitor {
    fn record_debug(&mut self, _field: &Field, _value: &dyn Debug) {
        // do nothing
    }

    fn record_str(&mut self, field: &Field, value: &str) {
        if field.name() == "test" {
            self.0 = Some(value.to_owned());
        }
    }
}

struct Test(String);

pub struct MyTestLayer;

impl<S> Layer<S> for MyTestLayer
where
    S: tracing::Subscriber,
    S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
        let span = ctx.span(id).unwrap();
        let mut visitor = TestVisitor(None);
        attrs.record(&mut visitor);
        if let Some(test) = visitor.0 {
            span.extensions_mut().insert(Test(test));
        } else {
            // span has no test value
        }

        let parent = span.parent().unwrap();
        if let Some(Test(test)) = parent.extensions().get::<Test>() {
            // parent test value is `test`
        } else {
            // parent has no test value
        };
    }
}

However, visiting each field/value with a visitor feels like I'm doing something wrong. Is this the best way to get the attribute values?

Using .record() is the only way to access the span's values as far as I'm aware.

kmdreko
  • 42,554
  • 6
  • 57
  • 106