Skip to content

Make Writer::with_ansi public #3287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ThatGeoGuy
Copy link

@ThatGeoGuy ThatGeoGuy commented May 23, 2025

Motivation

I'm in the process of trying to work with tracing_subscriber::fmt::Layer in such a way that I can duplicate my console output from a command to a file. Specifically, I use a newtype wrapper alongside Layer::map_event_format to convert this output to HTML in conjunction with the ansi-to-html crate.

It looks something like this:

/// A custom formatter for writing format layer events as HTML.
#[derive(Debug, Clone)]
pub struct FormatHtml<Formatter> {
    /// The inner layer.
    inner: Formatter,

    /// The converter to use to convert ANSI codes in the formatted output into HTML sequences.
    converter: ansi_to_html::Converter,
}

impl<S, N, Formatter> FormatEvent<S, N> for FormatHtml<Formatter>
where
    S: Subscriber + for<'lookup> LookupSpan<'lookup>,
    N: for<'writer> FormatFields<'writer> + 'static,
    Formatter: FormatEvent<S, N>,
{
    fn format_event(
        &self,
        ctx: &FmtContext<'_, S, N>,
        mut writer: Writer<'_>,
        event: &Event<'_>,
    ) -> fmt::Result {
        let mut source = String::new();
        let source_writer = Writer::new(&mut source);
        self.inner.format_event(ctx, source_writer, event)?;
        self.converter
            .convert(&source)
            .map_err(|err| {
                let err = miette::Report::from_err(err);
                // eprintln is used here so that we do not get locked in an 
                // infinite loop of tracing the same error event over and 
                // over and over again.
                eprintln!("TRACING_ERROR: Event conversion {err:?}");
                std::fmt::Error
            })
            .and_then(|converted| write!(writer, "{}", &converted))
    }
}

The calling code for this looks something along the lines of:

// NOTE: this is incomplete since I usually write some html preamble
// to the writer via a newtype.
let report_writer = BufWriter::new(
    File::create("/tmp/sample_report.html").unwrap(),
)
.unwrap();
                                                                     
let (report_writer, _guard) =
    tracing_appender::non_blocking::NonBlocking::new(report_writer);

let converter = ansi_to_html::Converter::new()
    .skip_escape(true)
    .skip_optimize(true);

let stderr_layer = tracing_subscriber::fmt::layer()
    .with_writer(std::io::stderr)
    .pretty()
    .with_ansi(true);
                                                              
let report_layer = tracing_subscriber::fmt::layer()
    .with_writer(report_writer)
    .pretty()
    .with_ansi(true)
    .map_event_format(move |e| FormatHtml::new(e, converter));
                                                              
let registry = tracing_subscriber::Registry::default()
    .with(stderr_layer)
    .with(report_layer);
                                                              
tracing::subscriber::set_global_default(registry).unwrap();

However, because the default Writer::new implementation doesn't let you set whether or not ANSI codes can be output, I can't actually make a Writer in my FormatEvent impl that accepts ANSI output. As such, even though I'm converting via ansi_to_html, the String that intercepts the event formatter prior to conversion contains no ANSI codes.

Solution

The solution is to just make Writer::with_ansi public:

let source_writer = Writer::new(&mut source).with_ansi(true);

With this, all of the above works and I get colorized HTML output of the exact same log that goes to stderr.

cc @hawkw since there was a comment left behind to make this public.

@ThatGeoGuy ThatGeoGuy requested review from hawkw, davidbarsky and a team as code owners May 23, 2025 18:10
@ThatGeoGuy
Copy link
Author

I forgot to search before I pushed this, so I see #2806 as well. I would be happy to close my PR and get either merged.

@ThatGeoGuy
Copy link
Author

To note: I think I can also make this work by overriding the writer settings for ANSI by doing:

let report_layer = tracing_subscriber::fmt::layer()
    .with_writer(report_writer)
    .pretty()
    .map_event_format(move |e| {
        let f = e.with_ansi(true);
        FormatHtml::new(f, converter)
    })
    .with_ansi(true);

So perhaps this isn't necessary. That additional with_ansi(true) is certainly not ideal, but it does do the right thing 🤔

Perhaps I should just close this so as to not add one more spot where you can get this wrong...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant