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:

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.