You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改Rust tracing_subscriber配置实现日志定向到stdout?

问题描述

我已经将Rust的tracing_subscriber配置为默认将所有日志消息输出到stderr(自定义配置如下),符合常规需求。现在希望在部分场景中,通过类似tracing::info!(to: "stdout", "This message goes to stdout");的写法将日志输出到stdout,请问如何修改setup_logging函数实现该需求?

原配置代码:

use std::fmt;
use std::io;

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

/// A [tracing_subscriber] event formatter that suppresses everything but the
/// log message.
///
/// Level/target are displayed for non-INFO messages.
struct BareFormatter;

impl<S, N> FormatEvent<S, N> for BareFormatter
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<'_>,
    ) -> fmt::Result {
        let metadata = event.metadata();
        if metadata.level() != &Level::INFO {
            write!(&mut writer, "{} {}: ", metadata.level(), metadata.target())?;
        }
        ctx.field_format().format_fields(writer.by_ref(), event)?;
        writeln!(writer)
    }
}

pub fn setup_logging(verbose: bool) {
    let env_filter = if verbose {
        "nixci=debug,nix_rs=debug"
    } else {
        "nixci=info,nix_rs=info"
    };
    let builder = tracing_subscriber::fmt()
        .with_writer(io::stderr)
        .with_max_level(Level::INFO)
        .with_env_filter(env_filter);

    if !verbose {
        builder.event_format(BareFormatter).init();
    } else {
        builder.init()
    }
}
解决方案

要实现按日志字段动态选择输出流,需完成以下修改:

  1. 自定义动态Writer:创建能根据日志事件的to字段,自动切换stdout/stderr输出的Writer类型,默认输出到stderr。
  2. 调整日志格式化逻辑:确保自定义的BareFormatter不会打印to字段本身,避免日志冗余。
  3. 替换默认Writer:在setup_logging中用自定义Writer替代原有固定的io::stderr。

修改后的完整代码如下:

use std::fmt;
use std::io::{self, Stderr, Stdout};
use std::sync::Arc;

use tracing::{Event, Level, Subscriber, field::Visit};
use tracing_subscriber::fmt::format;
use tracing_subscriber::{
    fmt::{FmtContext, FormatEvent, FormatFields},
    registry::LookupSpan,
};

/// 自定义Writer,根据日志事件的`to`字段选择输出到stdout或stderr
struct DynamicWriter {
    stdout: Arc<Stdout>,
    stderr: Arc<Stderr>,
}

impl DynamicWriter {
    fn new() -> Self {
        Self {
            stdout: Arc::new(io::stdout()),
            stderr: Arc::new(io::stderr()),
        }
    }

    // 从事件中提取`to`字段的值,判断输出流
    fn get_writer(&self, event: &Event<'_>) -> Arc<dyn io::Write + Send + Sync + 'static> {
        let mut visitor = ToFieldVisitor { target: None };
        event.record(&mut visitor);
        
        match visitor.target.as_deref() {
            Some("stdout") => Arc::clone(&self.stdout) as _,
            _ => Arc::clone(&self.stderr) as _,
        }
    }
}

// 用于提取`to`字段的Visitor
struct ToFieldVisitor {
    target: Option<String>,
}

impl Visit for ToFieldVisitor {
    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
        if field.name() == "to" {
            self.target = Some(value.to_string());
        }
    }
}

// 实现tracing_subscriber的MakeWriter trait,支持按事件选择Writer
impl tracing_subscriber::fmt::MakeWriter<'_> for DynamicWriter {
    type Writer = Arc<dyn io::Write + Send + Sync + 'static>;

    fn make_writer(&self) -> Self::Writer {
        Arc::clone(&self.stderr)
    }

    // 关键:根据事件动态返回Writer
    fn make_writer_for(&self, event: &Event<'_>) -> Self::Writer {
        self.get_writer(event)
    }
}

/// A [tracing_subscriber] event formatter that suppresses everything but the
/// log message.
///
/// Level/target are displayed for non-INFO messages.
struct BareFormatter;

impl<S, N> FormatEvent<S, N> for BareFormatter
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<'_>,
    ) -> fmt::Result {
        let metadata = event.metadata();
        if metadata.level() != &Level::INFO {
            write!(&mut writer, "{} {}: ", metadata.level(), metadata.target())?;
        }
        
        // 自定义字段格式化,跳过`to`字段
        let mut visitor = SkipToFieldVisitor { writer: &mut writer, formatter: ctx.field_format() };
        event.record(&mut visitor);
        
        writeln!(writer)
    }
}

// 用于跳过`to`字段的Visitor
struct SkipToFieldVisitor<'a, N> {
    writer: format::Writer<'a>,
    formatter: &'a N,
}

impl<'a, N> Visit for SkipToFieldVisitor<'a, N>
where
    N: FormatFields<'a>,
{
    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        if field.name() != "to" {
            let _ = self.formatter.format_field(self.writer.by_ref(), field, value);
        }
    }

    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
        if field.name() != "to" {
            let _ = self.formatter.format_field(self.writer.by_ref(), field, value);
        }
    }
}

pub fn setup_logging(verbose: bool) {
    let env_filter = if verbose {
        "nixci=debug,nix_rs=debug"
    } else {
        "nixci=info,nix_rs=info"
    };
    let builder = tracing_subscriber::fmt()
        .with_writer(DynamicWriter::new()) // 使用自定义动态Writer
        .with_max_level(Level::INFO)
        .with_env_filter(env_filter);

    if !verbose {
        builder.event_format(BareFormatter).init();
    } else {
        builder.init()
    }
}

代码说明

  • DynamicWriter:实现MakeWriter trait,通过make_writer_for方法解析日志事件的to字段,动态选择输出流,默认输出到stderr。
  • ToFieldVisitor:负责从日志事件中提取to字段的值,供DynamicWriter判断输出目标。
  • SkipToFieldVisitor:在格式化日志时跳过to字段,避免日志内容中出现多余的to="stdout"标识。
  • setup_logging修改:将原本固定的io::stderr替换为自定义的DynamicWriter::new(),实现动态输出逻辑。

修改完成后,即可通过tracing::info!(to: "stdout", "This message goes to stdout");指定日志输出到stdout,未指定to字段的日志默认输出到stderr。

内容的提问来源于stack exchange,提问作者Sridhar Ratnakumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 04:15:20