Emitting events
Diagnostic events produced by emit
are sent to an Emitter
. emit
provides a few implementations in external libraries you can use in your applications:
emit_term
for emitting to the console.emit_file
for emitting to rolling files.emit_otlp
for emitting via OTLP.
Setup
Emitters are configured through the setup
function at the start of your application by calling emit_to
:
extern crate emit; extern crate emit_term; fn main() { let rt = emit::setup() // Set the emitter .emit_to(emit_term::stdout()) .init(); // Your app code goes here rt.blocking_flush(std::time::Duration::from_secs(5)); }
Once initialized, any subsequent calls to init
will panic.
emit_to
will replace any previously set emitter during the same setup. You can set multiple emitters by calling and_emit_to
:
extern crate emit; extern crate emit_term; extern crate emit_file; fn main() { let rt = emit::setup() // Set multiple emitters .emit_to(emit_term::stdout()) .and_emit_to(emit_file::set("./target/logs/my_app.txt").spawn()) .init(); // Your app code goes here rt.blocking_flush(std::time::Duration::from_secs(5)); }
You can map an emitter to a new value by calling map_emitter
:
extern crate emit; extern crate emit_file; use emit::Emitter; fn main() { let rt = emit::setup() // Set the emitter .emit_to(emit_file::set("./target/logs/my_app.txt").spawn()) // Map the emitter, wrapping it with a transformation that // sets the module to "new_path". This could be done in the call // to `emit_to`, but may be easier to follow when split across two calls .map_emitter(|emitter| emitter .wrap_emitter(emit::emitter::wrapping::from_fn(|emitter, evt| { let evt = evt.with_mdl(emit::path!("new_path")); emitter.emit(evt) })) ) .init(); // Your app code goes here rt.blocking_flush(std::time::Duration::from_secs(5)); }
Wrapping emitters
Emitters can be treated like middleware using a Wrapping
by calling Emitter::wrap_emitter
. Wrappings are functions over an Emitter
and Event
that may transform the event before emitting it, or discard it altogether.
Transforming events with a wrapping
Wrappings can freely modify an event before forwarding it through the wrapped emitter:
#![allow(unused)] fn main() { extern crate emit; use emit::Emitter; let emitter = emit::emitter::from_fn(|evt| println!("{evt:?}")) .wrap_emitter(emit::emitter::wrapping::from_fn(|emitter, evt| { // Wrappings can transform the event in any way before emitting it // In this example we clear any extent on the event let evt = evt.with_extent(emit::Empty); // Wrappings need to call the given emitter in order for the event // to be emitted emitter.emit(evt) })); }
Filtering events with a wrapping
If a wrapping doesn't forward an event then it will be discarded:
#![allow(unused)] fn main() { extern crate emit; use emit::{Emitter, Props}; let emitter = emit::emitter::from_fn(|evt| println!("{evt:?}")) .wrap_emitter(emit::emitter::wrapping::from_fn(|emitter, evt| { // If a wrapping doesn't call the given emitter then the event // will be discarded. In this example, we only emit events // carrying a property called "sampled" with the value `true` if evt.props().pull::<bool, _>("sampled").unwrap_or_default() { emitter.emit(evt) } })); }
You can also treat a Filter
as a wrapping directly:
#![allow(unused)] fn main() { extern crate emit; use emit::Emitter; let emitter = emit::emitter::from_fn(|evt| println!("{evt:?}")) .wrap_emitter(emit::emitter::wrapping::from_filter( emit::level::min_filter(emit::Level::Warn) )); }
Also see Filtering events for more details on filtering in emit
.
Flushing
Events may be processed asynchronously, so to ensure they're fulling flushed before your main
returns, you can call blocking_flush
at the end of your main
function:
extern crate emit; extern crate emit_term; fn main() { let rt = emit::setup() .emit_to(emit_term::stdout()) .init(); // Your app code goes here // Flush at the end of `main` rt.blocking_flush(std::time::Duration::from_secs(5)); }
It's a good idea to flush even if your emitter isn't asynchronous. In this case it'll be a no-op, but will ensure flushing does happen if you ever introduce an asynchronous emitter in the future.
Instead of blocking_flush
, you can call flush_on_drop
:
extern crate emit; extern crate emit_term; fn main() { let _rt = emit::setup() .emit_to(emit_term::stdout()) .init() .flush_on_drop(std::time::Duration::from_secs(5)); // Your app code goes here }
Once the returned guard goes out of scope it'll call blocking_flush
for you, even if a panic unwinds through your main
function. Make sure you give the guard an identifer like _rt
and not _
, otherwise it will be dropped immediately and not at the end of your main
function.