How to access the value of a field in a tracing span?

2k Views Asked by At

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
    }
}
1

There are 1 best solutions below

3
On

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.