Varying tracing_subscriber Format on an Enum?

I've had this problem in the past before and haven't yet found a good solution for it.

I'm using tracing and tracing_subscriber for logging in many applications I maintain. Due to the fact that individual formats have distinct types (e.g. Pretty and Json), I usually end up with something like this:

let (non_blocking_writer, _guard) = tracing_appender::non_blocking(std::io::stderr());
let env_filter = EnvFilter::builder()
    .with_default_directive(Level::INFO.into())
    .from_env_lossy();

// FIXME this is absurd
match args.log_format {
    LogFormat::Json => {
        tracing_subscriber::registry().with(tracing_subscriber::fmt::layer().json().with_writer(non_blocking_writer).with_filter(env_filter)).init();
    }
    LogFormat::Compact => {
        tracing_subscriber::registry().with(tracing_subscriber::fmt::layer().compact().with_writer(non_blocking_writer).with_filter(env_filter)).init();
    }
    LogFormat::Pretty => {
        tracing_subscriber::registry().with(tracing_subscriber::fmt::layer().pretty().with_writer(non_blocking_writer).with_filter(env_filter)).init();
    }
};

Is there a way to dyn this within a Box or another way of going about this to make it less tedious and duplicative? My CLI application has -F flag which can be pretty, compact, or json to be specified when the application is run.

you can create a custom wrapper for the different formatters:

enum MyFormat {
    Json(Format<Json>),
    Compact(Format<Compact>),
    Pretty(Format<Pretty>),
}

impl<S, N> FormatEvent<S, N> for MyFormat {
    //...
}

and set it as the event format for the fmt layer:

    let my_format: MyFormat = match args.log_format {
        //...
    };
    racing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer()
            .event_format(my_format)
            .with_xxx(...)
            //....
        .init();

EDIT:

I believe the trait FormatEvent is dyn safe, but the generic type parameters are hard to spell out, an enum wrapper is simpler IMO.

Awesome, I seem to have gotten things together with your help.

Final code looking something like this:

use tracing::{Event, Subscriber};
use tracing_subscriber::fmt::format::{FormatEvent, FormatFields, Writer};
use tracing_subscriber::fmt::fmt_layer::FmtContext;
use tracing_subscriber::registry::LookupSpan;

pub enum LogFormat {
    Json,
    Compact,
    Pretty,
}

pub enum LogFormatter {
    Json(Format<Json>),
    Compact(Format<Compact>),
    Pretty(Format<Pretty>),
}

// convert the cli argument enum to the actual formatter
impl From<LogFormat> for LogFormatter {
    fn from(format: LogFormat) -> Self {
        match format {
            LogFormat::Json => LogFormatter::Json(fmt::format::json()),
            LogFormat::Compact => LogFormatter::Compact(fmt::format().compact()),
            LogFormat::Pretty => LogFormatter::Pretty(fmt::format().pretty()),
        }
    }
}

impl<S, N> FormatEvent<S, N> for LogFormatter
where
    S: Subscriber + for<'a> LookupSpan<'a>,
    N: for<'a> FormatFields<'a> + 'static,
{
    fn format_event(&self, ctx: &FmtContext<'_, S, N>, writer: Writer<'_>, event: &Event<'_>) -> std::fmt::Result {
        match self {
            LogFormatter::Json(f) => f.format_event(ctx, writer, event),
            LogFormatter::Compact(f) => f.format_event(ctx, writer, event),
            LogFormatter::Pretty(f) => f.format_event(ctx, writer, event),
        }
    }
}

pub fn configure_logging(format: LogFormat) {
    let formatter = LogFormatter::from(format);

    tracing::subscriber::set_global_default(
        Registry::default().with(
            tracing_subscriber::fmt::layer()
                .event_format(formatter)),
        ),
    ).unwrap();
}

Thanks for the help! I'm not sure if any variance makes sense within the From<LogFormat> for LogFormatter, but it does seem to do exactly what I need.