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.
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}!");
}
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 integratingemit
into an application using the OpenTelemetry SDK for its diagnostics.
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.
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.
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));
}
Check the Guide for a complete introduction to emit
. Also see the examples directory for common patterns, and the crate docs for technical details.
This is alpha-level software. It implements a complete framework but has almost no tests and needs a lot more documentation.