fastrace

repository·main·Indexed 22 days ago

https://github.com/fast/fastrace

A high-performance, lightweight tracing library for Rust applications and performance-sensitive libraries. It provides OpenTelemetry compatibility, integration with the standard log crate, and a compatibility layer for tokio-tracing. The ecosystem includes specialized crates such as fastrace-futures for Stream and Sink tracing, fastrace-macro for boilerplate reduction via attribute macros, and fastrace-opentelemetry for exporting spans to OpenTelemetry Collectors.

Tokens
15.5K
Snippets
56
Records
60
Agent score
77%

What's inside fastrace

  1. Use fastrace-macro to eliminate boilerplate

    main
    The fastrace-macro crate provides attribute macros designed to automatically generate the necessary instrumentation code for fastrace. Instead of manually writing span creation and management logic, you can apply these macros to functions to instrument them with minimal effort.
  2. Activate OpenTelemetry Trace Context from fastrace

    main

    If you are using fastrace but need to interoperate with libraries that rely on the OpenTelemetry Context (e.g., for propagating trace IDs), you can bridge the current fastrace local parent into an OpenTelemetry context.

    This requires that a local parent is already set for the current thread (using Span::set_local_parent). You can then use current_opentelemetry_context() to create an OpenTelemetry context that carries the fastrace span information.

    use fastrace::prelude::*;
    use fastrace_opentelemetry::current_opentelemetry_context;
    use opentelemetry::trace::TraceContextExt;
    use opentelemetry::Context;
    
    fn main() {
        let span = Span::root("root", SpanContext::random());
        let _guard = span.set_local_parent();
    
        let _otel_guard = current_opentelemetry_context()
            .map(|cx| Context::current().with_remote_span_context(cx).attach());
    
        // Call library code that uses `Context::current()`.
    }
  3. Configure Fastrace for Libraries

    main

    When developing a library, include fastrace as a dependency without enabling any extra features. This ensures minimal overhead for your users.

    To trace a function, use the #[fastrace::trace] attribute. This will collect a SpanRecord every time the function is called, provided the caller has established a tracing context.

    If your library needs to establish its own tracing context independently of the caller, use Span::root() to start a new trace and Span::set_local_parent() to set the context for the current thread. You can use the func_path!() macro to automatically use the function's full name as the root span name.

    [dependencies]
    fastrace = "0.7"
    #[fastrace::trace]
    pub fn send_request(req: HttpRequest) -> Result<(), Error> {
        // ...
    }
    use fastrace::prelude::*;
    
    pub fn send_request(req: HttpRequest) -> Result<(), Error> {
        let root = Span::root(func_path!(), SpanContext::random());
        let _guard = root.set_local_parent();
    
        // ...
    }
  4. Configure Fastrace for Applications

    main

    Applications must enable the enable feature to activate tracing. To disable tracing statically, remove this feature.

    To use Fastrace in an application, you must:

    1. Initialize a Reporter implementation early in the program's runtime using fastrace::set_reporter. Spans generated before this initialization will be ignored.
    2. Create root spans for discrete tasks (e.g., handling a single request). Because spans are reported when the root span is dropped, long-running root spans will prevent traces from being reported.
    3. Call fastrace::flush() before the program terminates to ensure all remaining collected span records are reported.
    [dependencies]
    fastrace = { version = "0.7", features = ["enable"] }
    use fastrace::collector::Config;
    use fastrace::collector::ConsoleReporter;
    use fastrace::prelude::*;
    
    fn main() {
        fastrace::set_reporter(ConsoleReporter, Config::default());
    
        loop {
            let root = Span::root("worker-loop", SpanContext::random());
            let _guard = root.set_local_parent();
    
            handle_request();
        }
    
        fastrace::flush();
    }
  5. Migrate from tokio-tracing to Fastrace

    main

    If you are currently using the tokio-tracing ecosystem, you can use the fastrace-tracing compatibility layer to capture spans from libraries instrumented with tokio-tracing. This allows for a smoother transition to Fastrace's performance benefits.

    let subscriber = tracing_subscriber::Registry::default().with(fastrace_tracing::FastraceCompatLayer::new());
    tracing::subscriber::set_global_default(subscriber).unwrap();
  6. Generate benchmark results in a fresh VM

    main

    To reproduce or generate new benchmark results for fastrace, set up a fresh Ubuntu Server 20.04 LTS environment and run the compare command using cargo-criterion. This process requires installing build essentials, Rust, and the specific version of cargo-criterion used for these benchmarks.

    sudo apt update
    sudo apt install build-essential libssl-dev pkg-config -y
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    source $HOME/.cargo/env
    cargo install --version=1.0.0-alpha3 cargo-criterion
    git clone https://github.com/fast/fastrace.git
    cd fastrace
    cargo criterion compare --message-format=json | grep "benchmark-complete" > compare-xxx.txt
  7. Use Async OpenTelemetry Exporters with a runtime

    main

    By default, OpenTelemetryReporter uses pollster::block_on to drive exporter futures. If your exporter (like OTLP HTTP with reqwest) requires a specific async runtime (e.g., Tokio), you must provide a custom blocking function using .with_block_on().

    Example using a Tokio handle:

    let handle = tokio::runtime::Handle::current();
    
    let reporter = OpenTelemetryReporter::new(exporter, resource, instrumentation_scope)
        .with_block_on(move |future| handle.block_on(future));
  8. Propagate trace context using W3C Trace Context

    main

    To propagate tracing information across process boundaries (e.g., via HTTP headers), use W3CTraceContext. This struct wraps a SpanContext and an optional tracestate string for vendor-specific metadata.

    Important: W3CTraceContext is a boundary wrapper. Converting a W3CTraceContext to a SpanContext (e.g., when starting a new Span) will discard the tracestate. If you need to preserve tracestate for outbound propagation, you must store the W3CTraceContext or the tracestate string separately.

    Encoding for Outbound Requests

    Use encode_headers() to get a list of key-value pairs (e.g., for HTTP headers). It always includes traceparent and includes tracestate only if it is present.

    Decoding from Inbound Requests

    • From raw strings: Use W3CTraceContext::decode(traceparent, tracestate).
    • From header iterators: Use W3CTraceContext::decode_headers(headers). This method is case-insensitive for header names and handles multiple tracestate headers by joining them with commas per the W3C spec.
    use fastrace::collector::W3CTraceContext;
    use fastrace::prelude::*;
    
    // 1. Decoding from incoming HTTP headers
    let headers = vec![
        ("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"),
        ("tracestate", "rw=frontend,congo=t61rcWkgMzE"),
    ];
    let ctx = W3CTraceContext::decode_headers(headers).unwrap();
    
    // 2. Starting a new span using the extracted context
    // Note: tracestate is NOT carried into the span
    let root = Span::root("server", ctx.span_context);
    
    // 3. Encoding for outgoing HTTP headers
    let outgoing_headers = ctx.encode_headers();
    // returns: [("traceparent", "..."), ("tracestate", "...")]
  9. Use LocalSpan for thread-local tracing

    main

    A LocalSpan is an optimized Span designed for tracing operations within a single thread. It allows you to create a hierarchy of spans that exist on a thread-local stack. When a LocalSpan is dropped, it automatically exits the span, making it ideal for RAII-style tracing.

    To use LocalSpan, you typically start by setting a root Span as a local parent, then use LocalSpan::enter_with_local_parent to create child spans.

    use fastrace::prelude::*;
    
    // 1. Establish a root span and set it as the local parent
    let root = Span::root("root", SpanContext::random());
    let _g = root.set_local_parent();
    
    // 2. Create a child span using LocalSpan
    let _child = LocalSpan::enter_with_local_parent("child");