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

如何在tracing_subscriber中自定义JSON字段值的格式化?

解决方案:在tracing_subscriber JSON层中按需求假名化字段

问题核心原因

tracing_subscriber的JsonLayer内部依赖自身的Json格式器实现字段序列化,调用json().fmt_fields()不会覆盖其默认逻辑,所以自定义的FormatFields不会生效。要实现按层区分的字段假名化,需要自定义JSON格式化逻辑,在序列化阶段拦截并修改字段值。

可行实现方案

通过自定义FormatEvent和JSON编码器,在事件序列化时根据层的配置修改指定字段,同时保留原事件的真实数据,由不同层决定最终输出格式。

1. 实现假名化JSON编码器

自定义编码器包装serde_json的默认序列化器,拦截结构体字段的序列化流程,对指定字段进行假名化:

use serde::ser::{SerializeStruct, Serializer};
use serde_json::ser::Serializer as JsonSerializer;
use tracing_subscriber::fmt::Writer;

// 自定义编码器,包含原序列化器和需要假名化的字段列表
struct PiiRedactingEncoder<W> {
    inner: JsonSerializer<W>,
    redact_fields: &'static [&'static str],
}

impl<W: Writer> PiiRedactingEncoder<W> {
    fn new(inner: JsonSerializer<W>, redact_fields: &'static [&'static str]) -> Self {
        Self { inner, redact_fields }
    }
}

// 委托大部分序列化逻辑给原编码器,仅重写结构体字段处理
impl<W: Writer> Serializer for PiiRedactingEncoder<W> {
    type Ok = ();
    type Error = serde_json::Error;
    type SerializeSeq = <JsonSerializer<W> as Serializer>::SerializeSeq;
    type SerializeTuple = <JsonSerializer<W> as Serializer>::SerializeTuple;
    type SerializeTupleStruct = <JsonSerializer<W> as Serializer>::SerializeTupleStruct;
    type SerializeTupleVariant = <JsonSerializer<W> as Serializer>::SerializeTupleVariant;
    type SerializeMap = <JsonSerializer<W> as Serializer>::SerializeMap;
    type SerializeStruct = RedactingStructSerializer<W>;
    type SerializeStructVariant = <JsonSerializer<W> as Serializer>::SerializeStructVariant;

    // 重写结构体序列化方法,使用自定义字段处理器
    fn serialize_struct(
        self,
        name: &'static str,
        len: usize,
    ) -> Result<Self::SerializeStruct, Self::Error> {
        let inner_struct = self.inner.serialize_struct(name, len)?;
        Ok(RedactingStructSerializer {
            inner: inner_struct,
            redact_fields: self.redact_fields,
        })
    }

    // 其余序列化方法直接委托给原编码器(示例省略,可手动复制或用宏生成)
    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
        self.inner.serialize_bool(v)
    }

    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
        self.inner.serialize_str(v)
    }

    // ... 补充其他基本类型的序列化委托
}

// 自定义结构体字段处理器,判断并假名化指定字段
struct RedactingStructSerializer<W> {
    inner: <JsonSerializer<W> as Serializer>::SerializeStruct,
    redact_fields: &'static [&'static str],
}

impl<W: Writer> SerializeStruct for RedactingStructSerializer<W> {
    type Ok = ();
    type Error = serde_json::Error;

    fn serialize_field<T: ?Sized>(
        &mut self,
        key: &'static str,
        value: &T,
    ) -> Result<(), Self::Error>
    where
        T: serde::Serialize,
    {
        if self.redact_fields.contains(&key) {
            // 执行假名化:这里示例替换为***,可替换为哈希/脱敏逻辑
            self.inner.serialize_field(key, &"***")
        } else {
            self.inner.serialize_field(key, value)
        }
    }

    fn end(self) -> Result<Self::Ok, Self::Error> {
        self.inner.end()
    }
}

2. 自定义JSON格式器

基于tracing_subscriber的FormatEvent trait,将自定义编码器集成到事件格式化流程中:

use tracing_core::Event;
use tracing_subscriber::fmt::{format::FormatEvent, FmtContext, Writer};
use tracing_subscriber::fmt::format::Json;

struct RedactingJsonFormat {
    redact_fields: &'static [&'static str],
}

impl RedactingJsonFormat {
    fn new(redact_fields: &'static [&'static str]) -> Self {
        Self { redact_fields }
    }
}

impl<'a, W: Writer> FormatEvent<'a, W> for RedactingJsonFormat {
    fn format_event(
        &self,
        ctx: &FmtContext<'a>,
        mut writer: W,
        event: &Event<'_>,
    ) -> Result<(), std::fmt::Error> {
        let serializer = JsonSerializer::new(&mut writer);
        let encoder = PiiRedactingEncoder::new(serializer, self.redact_fields);
        
        // 复用默认JSON事件格式化逻辑,替换为自定义编码器
        Json::default().format_event(ctx, encoder, event)?;
        writeln!(writer)?;
        Ok(())
    }
}

3. 配置分层订阅器

创建两个不同的JSON层:一个输出原始数据,一个输出假名化数据,通过目标过滤器区分应用场景:

use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

fn main() {
    // 输出原始数据的层:匹配目标app::full
    let full_layer = tracing_subscriber::fmt::layer()
        .json()
        .with_target(true)
        .with_filter(EnvFilter::new("app::full=info"));

    // 输出假名化数据的层:匹配目标app::redacted,假名化user_id和email
    let redacted_layer = tracing_subscriber::fmt::layer()
        .event_format(RedactingJsonFormat::new(&["user_id", "email"]))
        .with_target(true)
        .with_filter(EnvFilter::new("app::redacted=info"));

    // 注册分层订阅器
    tracing_subscriber::registry()
        .with(full_layer)
        .with(redacted_layer)
        .init();

    // 测试事件:原事件保留真实数据,不同层输出不同结果
    tracing::info!(target: "app::full", user_id = "12345", email = "user@example.com", "原始数据日志");
    tracing::info!(target: "app::redacted", user_id = "12345", email = "user@example.com", "假名化数据日志");
}

关键注意事项

  • 原事件始终保留真实数据,仅在不同层的格式化阶段修改输出内容,满足分层需求。
  • 可根据业务需求扩展假名化逻辑(如哈希、部分脱敏等),替换示例中的***占位符。
  • 过滤器规则可灵活调整,支持按目标、级别、字段等维度区分层的应用范围。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 08:47:39