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 Span
s to the new Span
.
Span
s 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 LocalSpan
s is built implicitly. It doesn't need to explicitly pass any contexts to connect
two LocalSpan
s. Even within a deeply nested function calls, the inner LocalSpanGuard
can automatically figure out
its parent.
LocalCollector
is provided to retrieve LocalSpan
s from thread local. Such collected LocalSpan
s 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 LocalSpan
s, which is collected by different LocalCollector
s,
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
Span
and aCollector
viaSpan::root()
, then attach theSpan
to 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
Span
and all guards are dropped, then callCollector
'scollect
to get allSpan
s.
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::enter
at every pollin_span
: wrap theFuture
with aSpan
, then callSpan::try_enter
at 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:latest
cargo run --example synchronous
Open http://localhost:16686 to see the results.
cargo run --example asynchronous
Open http://localhost:16686 to see the results.