The emit logo

I’ve spent some time recently improving emit’s integration with the OpenTelemetry SDK. If you’re not familiar, emit is a framework for instrumenting your Rust applications with logs, traces, and metrics. The OpenTelemetry project builds and maintains SDKs for collecting and exporting these same signals in many common languages, including Rust.

The result is the emit_opentelemetry crate. Here’s how it looks:

# emit
[dependencies.emit]
version = "1"

[dependencies.emit_opentelemetry]
version = "0.31"

# opentelemetry
[dependencies.opentelemetry_sdk]
version = "0.31"
features = ["rt-tokio", "trace", "logs"]

[dependencies.opentelemetry]
version = "0.31"
features = ["trace", "logs"]

use opentelemetry_otlp::WithTonicConfig as _;

#[tokio::main]
async fn main() {
    // 1. Configure the OpenTelemetry SDK
    //    The full configuration isn't important here, you just need
    //    a `LoggerProvider` and `TracerProvider`
    let (logger_provider, tracer_provider) = configure_opentelemetry().await;

    // 2. Configure `emit` to point to `opentelemetry`
    //    This is the only bit of plumbing needed between `emit` and the OpenTelemetry SDK
    let _ = emit_opentelemetry::setup(logger_provider.clone(), tracer_provider.clone()).init();

    // Your app code goes here
    {
        #[derive(serde::Serialize)]
        struct User<'a> {
            id: u32,
            name: &'a str,
        }

        // `emit` spans are managed by the OpenTelemetry SDK, and emitted as spans
        #[emit::span("Greet {user}", #[emit::as_serde] user)]
        async fn greet(user: &User) {
            // `emit` logs are recorded as log records
            emit::info!("Hello, {user: user.name}!");
        }

        greet(&User { id: 1, name: "Rust" }).await;
    }

    // Shutdown the SDK
    let _ = logger_provider.shutdown();
    let _ = tracer_provider.shutdown();
}

What this means is you can use emit’s APIs to instrument your applications instead of the OpenTelemetry SDK directly, falling back to it for things emit doesn’t natively support, like sampling.

While I’m personally not a fan of the OpenTelemetry SDK’s design (this is largely down to the API specification that all implementations must follow, rather than the Rust opentelemetry crates themselves), I think it serves an important role as a common target for other frameworks to integrate with, and as a baseline for expected behaviour. Instead of log, tracing, emit, fastrace, etc all needing to integrate with eachother, they can instead just integrate with the OpenTelemetry SDK.

I’ve made a little sample application making sure emit (via emit_opentelemetry), log (via opentelemetry-appender-log), and tracing (via tracing-opentelemetry) all get along when sharing the OpenTelemetry SDK, and it all works quite well. If anything, I think this achievement belongs to tracing-opentelemetry. The tracing framework predates OpenTelemetry, taking inspiration from the earlier OpenTracing model. It’s also evolved a feature rich runtime of its own over years of active support that’s different from the OpenTelemetry SDK’s. I imagine bridging those two stacks would be challenging.

Generally speaking, the main concern for a framework integrating with the OpenTelemetry SDK is around handling of trace context between them. Frameworks need to use the OpenTelemetry SDK as the source of truth for span context, otherwise you’ll get broken traces when they’re mixed. That includes communicating and respecting any sampling decisions made when starting traces.

I haven’t explored metric integration between emit and the OpenTelemetry SDK yet. I think this will prove more difficult than logs and traces, since the OpenTelemetry SDK’s API around metrics is more consumer facing than raw plumbing. I’ll spend some more time looking at it though.

Rust is a language that rewards specialization in design and implementation. You’ll find many seemingly competing alternative solutions to the same problems, each focusing on the needs of a particular target audience. I think this is a strength for the ecosystem rather than a weakness. There’s always room for more invention in Rust. The same is true of diagnostic frameworks. We have new options appearing all the time, each exploring a different niche. Having some common thread between frameworks is still a worthwhile thing though. Not every application will want to build on the OpenTelemetry SDK, but I think it’s worth any diagnostic framework putting effort into building and maintaining good external integration with it. I’m excited to see where this space continues to evolve over time!