/emit

Developer-first diagnostics for Rust applications

Primary LanguageRustApache License 2.0Apache-2.0

emit

all

Developer-first diagnostics

emit is a framework for manually instrumenting Rust applications using an expressive syntax inspired by Message Templates. emit has a small, fundamental data model where everything is represented by a single Event type. It has a focused API that keeps configuration straightforward and doesn't stand out within code being instrumented.

Getting started

Add emit to your Cargo.toml:

[dependencies.emit]
version = "0.11.0-alpha.16"

[dependencies.emit_term]
version = "0.11.0-alpha.16"

Then initialize it in your main.rs and start peppering diagnostics throughout your application:

fn main() {
    // Configure `emit` to write events to the console
    let rt = emit::setup()
        .emit_to(emit_term::stdout())
        .init();

    // Your app code goes here
    greet("Rust");

    // Flush any remaining events before `main` returns
    rt.blocking_flush(std::time::Duration::from_secs(5));
}

#[emit::span("Greet {user}")]
fn greet(user: &str) {
    emit::info!("Hello, {user}!");
}

The output of running the above program

Emitting events

Use the emit::emit! macros to emit diagnostic events in your applications:

let user = "Rust";

emit::emit!("Hello, {user}!");

emit's macro syntax is more than just format args. Values you use in the string template are included on your events, without losing their original type.

Events produced by emit are passed to an emitter, which encodes and forwards them on to some external onserver. Typical emitters include:

  • emit_term for writing human-readable output to the console.
  • emit_file for writing JSON or another machine-readable format to rolling files.
  • emit_otlp for sending diagnostics to an OpenTelemetry compatible collector.
  • emit_opentelemetry for integrating emit into an application using the OpenTelemetry SDK for its diagnostics.

Instrumenting functions

Use the emit::span! macros to instrument a function, emitting a span event for it at the end with the time it took to execute:

#[emit::span("Greet {user}")]
fn greet(user: &str) {
    
}

Any other events emitted while this function executes will be correlated with it. Any other instrumented functions it calls will form a trace hierarchy.

emit doesn't actually hardcode the concept of spans. They're an extension of its data model where everything is an event. A span-aware emitter can treat these events specially. In OTLP for instance, these events can be sent via the traces signal instead of to logs.

Sampling metrics

emit doesn't have APIs for collecting metrics itself, that's left up to your application. What it does have is another extension to its data model for metric samples:

let sample = sample_bytes_written();

emit::emit!(
    "{metric_agg} of {metric_name} is {metric_value}",
    evt_kind: "metric",
    metric_agg: "count",
    metric_name: "bytes_written",
    metric_value: sample,
);

Metric-aware emitters like OTLP can send this event via the metrics signal. There's room in emit for many more extensions, including ones you define for your own applications.

Troubleshooting

emit uses itself for diagnostics in its emitters. If you aren't seeing your diagnostics or something seems wrong, you can initialize the internal runtime and get an idea of what's going on:

fn main() {
    // NEW: Configure the internal runtime before your regular setup
    let internal_rt = emit::setup()
        .emit_to(emit_term::stdout())
        .init_internal();

    // Configure `emit` normally now
    // This example configures OTLP
    let rt = emit::setup()
        .emit_to(
            emit_otlp::new()
                .logs(emit_otlp::logs_grpc_proto("http://localhost:4319"))
                .traces(emit_otlp::traces_grpc_proto("http://localhost:4319"))
                .metrics(emit_otlp::metrics_grpc_proto("http://localhost:4319"))
                .spawn()
        )
        .init();

    // Your app code goes here

    rt.blocking_flush(std::time::Duration::from_secs(5));

    // NEW: Flush the internal runtime after your regular setup
    internal_rt.blocking_flush(std::time::Duration::from_secs(5));
}

Next steps

Check the Guide for a complete introduction to emit. Also see the examples directory for common patterns, and the crate docs for technical details.

Current status

This is alpha-level software. It implements a complete framework but has almost no tests and needs a lot more documentation.