如何在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
相关产品推荐
相关产品推荐

