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

如何在tracing-rs的Subscriber/Layer中按swap_id输出JSON格式日志

解决方案:基于swap_id分文件的JSON日志记录(tracing-rs)

以下是修改后的完整代码,复用tracing-subscriber内置的JSON格式化器,实现按swap_id将日志写入对应文件:

use anyhow::Result;
use std::fmt::Debug;
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
use tracing::field::Field;
use tracing::span::Attributes;
use tracing::{Id, Level, Subscriber};
use tracing_subscriber::fmt::format::Json;
use tracing_subscriber::fmt::time::UtcTime;
use tracing_subscriber::layer::{Context, SubscriberExt};
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, FmtSubscriber, Layer, Registry};
use tracing_subscriber::fmt::FormatEvent;

struct SwapIdVisitor {
    swap_id: Option<String>,
}

pub struct FileLayer {
    dir: PathBuf,
    json_format: Json<UtcTime>,
}

impl FileLayer {
    pub fn new(dir: impl AsRef<Path>) -> Self {
        // 配置JSON格式化器,和控制台日志的时间格式、字段保持一致
        let json_format = Json::new()
            .with_timer(UtcTime::rfc_3339())
            .with_target(false);
        
        Self {
            dir: dir.as_ref().to_path_buf(),
            json_format,
        }
    }

    fn get_log_path(&self, swap_id: &str) -> PathBuf {
        self.dir.join(format!("swap-{}.log", swap_id))
    }
}

impl<S> Layer<S> for FileLayer
where
    S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
{
    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
        let mut visitor = SwapIdVisitor { swap_id: None };
        attrs.record(&mut visitor);

        if let Some(swap_id) = visitor.swap_id {
            if let Some(span) = ctx.span(id) {
                span.extensions_mut().insert(swap_id);
            }
        }
    }

    fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
        // 找到当前span并获取swap_id
        if let Some(current_span) = ctx.current_span().id().and_then(|id| ctx.span(id)) {
            if let Some(swap_id) = current_span.extensions().get::<String>() {
                // 打开对应swap_id的日志文件,追加模式
                let log_file = OpenOptions::new()
                    .append(true)
                    .create(true)
                    .open(self.get_log_path(swap_id))
                    .expect("Failed to open swap log file");
                
                // 使用内置JSON格式化器处理事件并写入文件
                self.json_format
                    .format_event(&ctx, event, &mut std::io::BufWriter::new(log_file))
                    .expect("Failed to format event to JSON");
            }
        }
    }
}

impl tracing::field::Visit for SwapIdVisitor {
    // 优先处理字符串类型的swap_id,避免额外引号
    fn record_str(&mut self, field: &Field, value: &str) {
        if field.name() == "swap_id" {
            self.swap_id = Some(value.to_string());
        }
    }

    // 处理非字符串类型的swap_id(如数字)
    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
        if field.name() == "swap_id" {
            self.swap_id = Some(format!("{}", value));
        }
    }
}

pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>) -> Result<()> {
    let level = if debug { Level::DEBUG } else { Level::INFO };
    let is_terminal = atty::is(atty::Stream::Stderr);

    let file_layer = FileLayer::new(dir.as_ref());

    // 构建控制台订阅器,根据参数选择是否用JSON格式
    let subscriber_builder = FmtSubscriber::builder()
        .with_env_filter(format!("swap={}", level))
        .with_writer(std::io::stderr)
        .with_ansi(is_terminal)
        .with_timer(UtcTime::rfc_3339())
        .with_target(false);

    let subscriber = if json {
        subscriber_builder.json().finish()
    } else {
        subscriber_builder.finish()
    };

    subscriber.with(file_layer).init();

    tracing::info!("Logging initialized to {}", dir.as_ref().display());
    Ok(())
}

关键改动说明

  • 集成内置JSON格式化器:在FileLayer中持有预先配置好的Json<UtcTime>实例,复用tracing-subscriber的成熟格式化逻辑,避免手动拼接JSON字符串的错误。
  • 动态文件写入:在on_event中根据swap_id打开对应日志文件,使用BufWriter优化写入性能,直接调用format_event完成格式化与写入。
  • 修正swap_id记录逻辑:新增record_str方法处理字符串类型的swap_id,避免{:?}带来的额外引号;非字符串类型用format!("{}", value)保证输出格式简洁。
  • 控制台日志可配置:利用init函数的json参数,让控制台日志也可以选择是否输出JSON格式,和文件日志保持配置一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 22:25:04