Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Expose fmt::format::Writer::new() #2512

@kaifastromai

Description

@kaifastromai

Feature Request

Make the constructer of Writer from tracing_subscriber available outside of the crate. I'm not sure I should file this issue in a different repo since tracing_subscriber is in it's own repo.

Crates

tracing-subscriber

Motivation

I have a server that needs to be logged remotely. What I am trying to do is add a Layer that copies the formatted output from fmt and sends it down a tokio channel (and subsequently a grpc stream), but I can't just use with_writer since I also want to attach the log level and other context data. I managed to implement my own Layer based upon the example:

pub struct JobLogLayer<E> {
    pub channel: tokio::sync::mpsc::UnboundedSender<Result<JobHostEvent, E>>,
}
pub struct DefaultVisitor {
    pub string: String,
}
impl tracing_subscriber::field::Visit for DefaultVisitor {
    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        if field.name() == "message" {
            self.string.push_str(&format!("{value:?}"));
        } else {
            self.string
                .push_str(&format!("\n{}: {value:?} ", field.name()));
        }
    }
}

impl<S, E> tracing_subscriber::Layer<S> for JobLogLayer<E>
where
    S: tracing::Subscriber,
    E: 'static,
{
    fn on_event(
        &self,
        _event: &tracing::Event<'_>,
        _ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        //send the event to the channel
        let mut v = DefaultVisitor {
            string: String::new(),
        };
        _event.record(&mut v);
        //get the log level
        let level = _event.metadata().level();
        let job_level = match level.as_str() {
            "TRACE" => crate::job::JobLogLevel::Trace,
            "DEBUG" => crate::job::JobLogLevel::Debug,
            "INFO" => crate::job::JobLogLevel::Info,
            "WARN" => crate::job::JobLogLevel::Warn,
            "ERROR" => crate::job::JobLogLevel::Error,
            _ => unreachable!(),
        };
        let job_log = crate::job::JobLog {
            message: v.string,
            level: job_level,
        };
        let json = serde_json::to_string(&job_log)
            .context("Could not serialize job log")
            .unwrap();
        let log = JobHostEvent {
            event_type: JobHostEventType::JobLog as i32,
            json,
        };
        let res = self.channel.send(Ok(log));
        match res {
            Ok(_) => {}
            Err(e) => {
                print!("Error sending log: {e}");
            }
        }
    }
}

and that allowed me get access to this data, but I'd rather not try to reimplement (and at present have no idea how one would implement) tracing-subscriber's fmt stuff. After digging through the source code some more, I found the formatter layer that seems to have mosts things already configured for me:
(this is ripped directly from the documentation in the source code)

pub struct MoscaEventFormatter {}
impl<S, N> FormatEvent<S, N> for MoscaEventFormatter
where
    S: Subscriber + for<'a> LookupSpan<'a>,
    N: for<'a> FormatFields<'a> + 'static,
{
    fn format_event(
        &self,
        ctx: &FmtContext<'_, S, N>,
        mut writer: format::Writer<'_>,
        event: &Event<'_>,
    ) -> std::fmt::Result {
        // Format values from the event's's metadata:
        let metadata = event.metadata();
        write!(&mut writer, "{} {}: ", metadata.level(), metadata.target())?;
        use std::fmt::Write;
        
        // Format all the spans in the event's span context.
        if let Some(scope) = ctx.event_scope() {
            for span in scope.from_root() {
                write!(writer, "{}", span.name())?;

                // `FormattedFields` is a formatted representation of the span's
                // fields, which is stored in its extensions by the `fmt` layer's
                // `new_span` method. The fields will have been formatted
                // by the same field formatter that's provided to the event
                // formatter in the `FmtContext`.
                let ext = span.extensions();
                let fields = &ext
                    .get::<fmt::FormattedFields<N>>()
                    .expect("will never be `None`");
                
                // Skip formatting the fields if the span had no fields.
                if !fields.is_empty() {
                    write!(writer, "{{{}}}", fields)?;
                }
                write!(writer, ": ")?;
            }
        }

        // Write fields on the event
        ctx.field_format().format_fields(writer.by_ref(), event)?;
        writeln!(writer)
        
    }
}

However, I now need to access the data in the writer, which is not possible. I initially thought I could just ignore the parameter and create my own writer, but the Writer::new(...) is private. I did there was a comment to(I believe) @hawkw about making this public and making the necessary considerations.

I am very new to tracing (and tokio for that matter) so I'm not sure this is the best way to accomplish my outlined goals.

Proposal

Remove the pub(crate) and just make it pub.
There comments about considerating creating an explicit from_string() constructor or some such.

Alternatives

I have no idea

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions