A high-performance, ergonomic timeline tracing library for Rust.
A Span represents an individual unit of work done. It contains:
- An operation name
- A start timestamp and finish timestamp
- A set of key-value properties
- A reference to a causally-related
Span
To record such a span, a struct of the same name, Span, is provided. Its lifetime indicates the begin and the end
of the span.
A new Span can be created via Span::root(), Span::from_parent() and Span::from_parents(), and the last two
methods refer existing related Spans to the new Span.
Spans are thread-safe and can be sent and accessed across threads.
A Collector associated to a root Span is used to collect all spans related to a request.
Cooperate with LocalCollector, LocalSpan provides an extremely low-cost tracing mechanism for performance-sensitive
apps.
A LocalSpan is recorded by a LocalSpanGuard which is thread-local and created via method LocalSpan::enter().
A LocalSpanGuard's creation means a LocalSpan's begin and its destruction means a LocalSpan's end. A completed
LocalSpan will be finally stored in thread local.
The causally-relation between LocalSpans is built implicitly. It doesn't need to explicitly pass any contexts to connect
two LocalSpans. Even within a deeply nested function calls, the inner LocalSpanGuard can automatically figure out
its parent.
LocalCollector is provided to retrieve LocalSpans from thread local. Such collected LocalSpans can be mounted to a
normal Span.
At most time, we should use the thread-local tracing mechanism (i.e LocalSpan and LocalCollector) to achieve the high
performance goal. In other situations where execution is crossing thread bound, and a Span has to be passed from one
thread to another thread, we just need to simply mount LocalSpans, which is collected by different LocalCollectors,
to the Span.
[dependencies]
minitrace = { git = "https://github.com/tikv/minitrace-rust.git" }To record a common span:
use minitrace::*;
let _span_guard = LocalSpan::enter("my event");To add properties:
use minitrace::*;
// add a property for a span
let _span_guard = LocalSpan::enter("my event").with_property(|| ("key", String::from("value")));
// or add multiple properties for a span
let _span_guard = LocalSpan::enter("my event").with_properties(|| {
vec![
("key1", String::from("value1")),
("key2", String::from("value2")),
]
});A common pattern to trace synchronous code:
- Create a root
Spanand aCollectorviaSpan::root(), then attach theSpanto the current thread. - Add
LocalSpan::enter()s somewhere, e.g. at the beginning of a code scope, at the beginning of a function, to record spans. - Make sure the root
Spanand all guards are dropped, then callCollector'scollectto get allSpans.
use minitrace::*;
let collector = {
let (root_span, collector) = Span::root("root");
let _span_guard = root_span.enter();
let _local_span_guard = LocalSpan::enter("child");
// do something ...
collector
};
let spans: Vec<span::Span> = collector.collect();To trace asynchronous code, we usually transmit Span from one thread to another thread.
use minitrace::*;
let collector = {
let (root_span, collector) = Span::root("task1");
let _span_guard = root_span.enter();
let _local_span_guard = LocalSpan::enter("span of task1");
// To trace a child task
let span = Span::from_local_parent("task2");
std::thread::spawn(move || {
let _span_guard = span.enter();
let _loal_span_guard = Span::enter("span of also task2");
});
collector
};
let spans: Vec<span::Span> = collector.collect();We provide two Future adaptors:
in_local_span: callLocalSpan::enterat every pollin_span: wrap theFuturewith aSpan, then callSpan::try_enterat every poll
The in_span adaptor is commonly used on a Future submitting to a runtime.
use minitrace::*;
let collector = {
let (root_span, collector) = Span::root("root");
let _span_guard = root_span.enter();
// To trace another task
runtime::spawn(async {
let _ = async {
// some works
}.in_local_span("");
}.in_span(Span::from_local_parent("new task")));
collector
};
let spans: Vec<span::Span> = collector.collect();We provide two macros to help reduce boilerplate code:
- trace
- trace_async
For normal functions, you can change:
use minitrace::*;
fn amazing_func() {
let _span_guard = LocalSpan::enter("wow");
// some works
}to
use minitrace::*;
use minitrace_macro::trace;
#[trace("wow")]
fn amazing_func() {
// some works
}For async functions, you can change:
use minitrace::*;
async fn amazing_async_func() {
async {
// some works
}
.in_local_span("wow")
.await
}to
use minitrace::*;
use minitrace_macro::trace_async;
#[trace_async("wow")]
async fn amazing_async_func() {
// some works
}To access these macros, a dependency should be added as:
[dependencies]
minitrace-macro = { git = "https://github.com/tikv/minitrace-rust.git" }We support visualization provided by an amazing tracing platform Jaeger.
To experience, a dependency should be added as:
[dependencies]
minitrace-jaeger = { git = "https://github.com/tikv/minitrace-rust.git" }use minitrace_jaeger::Reporter;
let spans = /* collect from a collector */;
let socket = SocketAddr::new("127.0.0.1".parse().unwrap(), 6831);
const TRACE_ID: u64 = 42;
const SPAN_ID_PREFIX: u32 = 42;
const ROOT_PARENT_SPAN_ID: u64 = 0;
let bytes = Reporter::encode(
String::from("service name"),
TRACE_ID,
ROOT_PARENT_SPAN_ID,
SPAN_ID_PREFIX,
&spans,
)
.expect("encode error");
Reporter::report(socket, &bytes).expect("report error");docker run --rm -d -p6831:6831/udp -p16686:16686 --name jaeger jaegertracing/all-in-one:latestcargo run --example synchronousOpen http://localhost:16686 to see the results.
cargo run --example asynchronousOpen http://localhost:16686 to see the results.

