6

I'm using the tracing library in my project and there is one thing I'm not able to figure out: How can I access a value (that I set in my span when I create it) in my Layer?

My layer looks like this:

impl<S> Layer<S> for CustomLayer where S: Subscriber {
    fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
        Interest::sometimes() //hardcoding so enabled() will be called everytime a span is created
    }

    fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool {
        if metadata.is_span() {
            // How do I access value of key here?
            if value == X {
                true
            } else if value == Y {
                false
            }
        }
        true // default
    }
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
Boss Man
  • 587
  • 2
  • 12

1 Answers1

3

You can access the data in a Span if you have access to either its ValueSet (as found in new_span() or on_new_span() via Attributes) or a Record entry for it (as found in record() or on_record()). With that you can use the visitor pattern to find the information you desire. Here's a simple implementation that checks if a field exists and its value is a matching string:

use std::fmt::Debug;
use tracing::field::{ValueSet, Visit, Field};
use tracing::span::Record;

struct MatchStrVisitor<'a> {
    field: &'a str,
    value: &'a str,
    matched: bool,
}

impl Visit for MatchStrVisitor<'_> {
    fn record_debug(&mut self, _field: &Field, _value: &dyn Debug) {}
    fn record_str(&mut self, field: &Field, value: &str) {
        if field.name() == self.field && value == self.value {
            self.matched = true;
        }
    }
}

fn value_in_valueset(valueset: &ValueSet<'_>, field: &str, value: &str) -> bool {
    let mut visitor = MatchStrVisitor { field, value, matched: false };
    valueset.record(&mut visitor);
    visitor.matched
}

fn value_in_record(record: &Record<'_>, field: &str, value: &str) -> bool {
    let mut visitor = MatchStrVisitor { field, value, matched: false };
    record.record(&mut visitor);
    visitor.matched
}

This is pretty rudimentary but hopefully demonstrates what is possible. One thing to note is that the "value" that is stored is either a primitive value (i64, u64, bool, str, etc.) or in a type-erased form via &dyn Debug. Those are the only types of values you can receive from the visitor.


Addressing OP's case in particular, as explained in this issue you cannot access this information in the enabled() method since that occurs before any values are recorded. You will need to make your determination in the new_span() method, and use span extensions via the registry to track whether you consider the span is "enabled" in your other methods.

Here's another rudimentary example:

use tracing::span::Attributes;
use tracing::{Subscriber, Metadata, Id, Event};
use tracing::subscriber::Interest;
use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::registry::LookupSpan;

struct CustomLayer;
struct CustomLayerEnabled;

impl<S> Layer<S> for CustomLayer where S: Subscriber + for <'a> LookupSpan<'a> {
    fn register_callsite(&self, _metadata: &'static Metadata<'static>) -> Interest {
        Interest::sometimes()
    }

    fn enabled(&self, metadata: &Metadata<'_>, _ctx: Context<'_, S>) -> bool {
        metadata.is_span()
    }

    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
        if value_in_valueset(attrs.values(), "myfield", "myvalue") {
            ctx.span(id).unwrap().extensions_mut().insert(CustomLayerEnabled);
        }
    }

    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
        let span_id = event.parent().unwrap();
        if let None = ctx.span(span_id).unwrap().extensions().get::<CustomLayerEnabled>() {
            return;
        }

        // ... rest of your logic
    }
}

Note: I've completely rewritten this answer taking info from the comments and my newfound experience.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • 1
    There is a way to do this. Check [this](https://github.com/tokio-rs/tracing/issues/1293). I wish I could some how get my type (something other than `i64`, `u64`, etc.) but I guess that would be entering Rust territory not tracing. – Boss Man Mar 11 '21 at 23:04
  • And speaking of trait objects, Im willing to downcast. At least for my usecase, I can take that hit because I store my state in the layer. So I wont be downcasting everytime I get that key only the first time. – Boss Man Mar 11 '21 at 23:10
  • Can you expand on the way to do this? The link you note isn't very clear to me. – Alex Moore-Niemi Jul 29 '21 at 15:49