Generating spans

We will use two crates to make a test example—rustracing and rustracing_jaeger. Create a new crate and add it to the [dependencies] section of Cargo.toml: 

rustracing = "0.1"
rustracing_jaeger = "0.1"

Add the following dependencies to the main.rs source file:

use rustracing::sampler::AllSampler;
use rustracing::tag::Tag;
use rustracing_jaeger::Tracer;
use rustracing_jaeger::reporter::JaegerCompactReporter;
use std::time::Duration;
use std::thread;

AppSampler implements the Sampler trait that is used to decide whether every new trace will be sampled or not. Consider samplers as filters of loggers, but smart ones that can limit the amount of traces per second or use other conditions. Tag is used to set extra data for spans. Tracer is the main object that's used for creating spans. The JaegerCompactReporter type is used to group spans and send them to the Jaeger instance.

Also, we need a function to sleep the current thread for milliseconds:

fn wait(ms: u64) {
thread::sleep(Duration::from_millis(ms));
}

Now, you can add the main function, and add the first part of the example to it:

let (tracer1, span_rx1) = Tracer::new(AllSampler);
let (tracer2, span_rx2) = Tracer::new(AllSampler);
thread::spawn(move || {
loop {
{
let req_span = tracer1
.span("incoming request")
.start();
wait(50);
{
let db_span = tracer2
.span("database query")
.child_of(&req_span)
.tag(Tag::new("query", "SELECT column FROM table;"))
.start();
wait(100);
let _resp_span = tracer2
.span("generating response")
.follows_from(&db_span)
.tag(Tag::new("user_id", "1234"))
.start();
wait(10);
}
}
wait(150);
}
});

In this code, we did a major part of the tracing routine. First, we created two Tracer instances that will pass all values by AllSampler. After this, we used spawn to create a new thread and created a loop that generated spans. You have to remember that the rustracing crate uses a Drop trait implementation to send a span value to a Reciever that was also created with the Tracer::new method call, and we have to drop values (we used the scoping rules of Rust to do dropping automatically).

We used the Tracer instance stored in the tracer1 variable to create a span with the span method call. It expects a name for the span and created a StartSpanOptions struct that can be used to configure a future Span value. For configuring, we can use the child_of method to set a parent, or the follows_from method to set a reference to the previous Span. Also, we can set extra information with the tag method call and provide a key-value pair called Tag, just as we did in structural logging before. After configuring, we have to call the start method of the StartSpanOptions instance to create a Span instance with a set span starting time. Using scopes and tracer, we emulated two parts of an application: the first that processes a request, and the second that performs a database query and generates a response, where the first is a parent of the latter.

Now, we have to use SpanReciever instances to collect all dropped Span values. (They've actually been sent to Reciever.) Also, we create two JaegerCompactReporter instances with names and add spans to reports using the report method call in the loop:

let reporter1 = JaegerCompactReporter::new("router").unwrap();
let reporter2 = JaegerCompactReporter::new("dbaccess").unwrap();
loop {
if let Ok(span) = span_rx1.try_recv() {
reporter1.report(&[span]).unwrap();
}
if let Ok(span) = span_rx2.try_recv() {
reporter2.report(&[span]).unwrap();
}
thread::yield_now();
}

Now, we can compile and run this tracing example.