1use crate::{
8 field::Visit,
9 fmt::{format::Writer, FormattedFields},
10 layer::{Context, Layer},
11 registry::LookupSpan,
12};
13use std::{fmt, marker::PhantomData};
14use tracing_core::{
15 field::Field,
16 span::{Attributes, Id, Record},
17 Subscriber,
18};
19
20#[derive(Debug)]
46pub struct FieldTransformLayer<N = ()> {
47 transforms: N,
48 _phantom: PhantomData<fn()>,
49}
50
51#[derive(Debug)]
53pub struct TransformConfig {
54 target_rules: Vec<TargetRule>,
55}
56
57#[derive(Debug)]
59pub struct TargetRule {
60 target_pattern: &'static str,
61 field_renames: Vec<(&'static str, &'static str)>,
62 hidden_fields: Vec<&'static str>,
63 field_transforms: Vec<FieldTransform>,
64}
65
66#[derive(Debug)]
68pub struct FieldTransform {
69 field_name: &'static str,
70 transform_type: TransformType,
71}
72
73#[derive(Debug)]
75pub enum TransformType {
76 Truncate(usize),
78 Prefix(&'static str),
80 Custom(fn(&str) -> String),
82}
83
84impl FieldTransformLayer<()> {
85 pub fn new() -> Self {
89 Self {
90 transforms: (),
91 _phantom: PhantomData,
92 }
93 }
94
95 pub fn with_target_transform<F>(
97 self,
98 target_pattern: &'static str,
99 builder: F,
100 ) -> FieldTransformLayer<TransformConfig>
101 where
102 F: FnOnce(TargetRuleBuilder) -> TargetRuleBuilder,
103 {
104 let rule = builder(TargetRuleBuilder::new(target_pattern)).build();
105
106 FieldTransformLayer {
107 transforms: TransformConfig {
108 target_rules: vec![rule],
109 },
110 _phantom: PhantomData,
111 }
112 }
113}
114
115impl FieldTransformLayer<TransformConfig> {
116 pub fn with_target_transform<F>(mut self, target_pattern: &'static str, builder: F) -> Self
118 where
119 F: FnOnce(TargetRuleBuilder) -> TargetRuleBuilder,
120 {
121 let rule = builder(TargetRuleBuilder::new(target_pattern)).build();
122 self.transforms.target_rules.push(rule);
123 self
124 }
125}
126
127#[derive(Debug)]
129pub struct TargetRuleBuilder {
130 target_pattern: &'static str,
131 field_renames: Vec<(&'static str, &'static str)>,
132 hidden_fields: Vec<&'static str>,
133 field_transforms: Vec<FieldTransform>,
134}
135
136impl TargetRuleBuilder {
137 fn new(target_pattern: &'static str) -> Self {
138 Self {
139 target_pattern,
140 field_renames: Vec::new(),
141 hidden_fields: Vec::new(),
142 field_transforms: Vec::new(),
143 }
144 }
145
146 pub fn rename_field(mut self, from: &'static str, to: &'static str) -> Self {
148 self.field_renames.push((from, to));
149 self
150 }
151
152 pub fn hide_field(mut self, field: &'static str) -> Self {
154 self.hidden_fields.push(field);
155 self
156 }
157
158 pub fn truncate_field(mut self, field: &'static str, max_len: usize) -> Self {
160 self.field_transforms.push(FieldTransform {
161 field_name: field,
162 transform_type: TransformType::Truncate(max_len),
163 });
164 self
165 }
166
167 pub fn prefix_field(mut self, field: &'static str, prefix: &'static str) -> Self {
169 self.field_transforms.push(FieldTransform {
170 field_name: field,
171 transform_type: TransformType::Prefix(prefix),
172 });
173 self
174 }
175
176 pub fn transform_field(mut self, field: &'static str, transform: fn(&str) -> String) -> Self {
178 self.field_transforms.push(FieldTransform {
179 field_name: field,
180 transform_type: TransformType::Custom(transform),
181 });
182 self
183 }
184
185 pub fn build(self) -> TargetRule {
187 TargetRule {
188 target_pattern: self.target_pattern,
189 field_renames: self.field_renames,
190 hidden_fields: self.hidden_fields,
191 field_transforms: self.field_transforms,
192 }
193 }
194}
195
196struct TransformingVisitor<'a> {
198 writer: Writer<'a>,
199 rule: &'a TargetRule,
200}
201
202impl<'a> TransformingVisitor<'a> {
203 fn new(writer: Writer<'a>, rule: &'a TargetRule) -> Self {
204 Self { writer, rule }
205 }
206}
207
208impl Visit for TransformingVisitor<'_> {
209 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
210 let field_name = field.name();
211
212 if self
214 .rule
215 .hidden_fields
216 .iter()
217 .any(|&hidden| hidden == field_name)
218 {
219 return;
220 }
221
222 let display_name = self
224 .rule
225 .field_renames
226 .iter()
227 .find(|(from, _)| *from == field_name)
228 .map(|(_, to)| *to)
229 .unwrap_or(field_name);
230
231 if let Some(transform) = self
233 .rule
234 .field_transforms
235 .iter()
236 .find(|t| t.field_name == field_name)
237 {
238 let value_str = format!("{:?}", value);
239 let transformed_value = match &transform.transform_type {
240 TransformType::Truncate(max_len) => {
241 if value_str.len() > *max_len {
242 format!("{}...", &value_str[..*max_len])
243 } else {
244 value_str
245 }
246 }
247 TransformType::Prefix(prefix) => {
248 format!("{} {}", prefix, value_str)
249 }
250 TransformType::Custom(func) => func(&value_str),
251 };
252 let _ = write!(self.writer, "{}={}", display_name, transformed_value);
253 } else {
254 let _ = write!(self.writer, "{}={:?}", display_name, value);
255 }
256 }
257
258 fn record_str(&mut self, field: &Field, value: &str) {
259 let field_name = field.name();
260
261 if self
263 .rule
264 .hidden_fields
265 .iter()
266 .any(|&hidden| hidden == field_name)
267 {
268 return;
269 }
270
271 let display_name = self
273 .rule
274 .field_renames
275 .iter()
276 .find(|(from, _)| *from == field_name)
277 .map(|(_, to)| *to)
278 .unwrap_or(field_name);
279
280 if let Some(transform) = self
282 .rule
283 .field_transforms
284 .iter()
285 .find(|t| t.field_name == field_name)
286 {
287 let transformed_value = match &transform.transform_type {
288 TransformType::Truncate(max_len) => {
289 if value.len() > *max_len {
290 format!("{}...", &value[..*max_len])
291 } else {
292 value.to_string()
293 }
294 }
295 TransformType::Prefix(prefix) => {
296 format!("{} {}", prefix, value)
297 }
298 TransformType::Custom(func) => func(value),
299 };
300 let _ = write!(self.writer, "{}={}", display_name, transformed_value);
301 } else {
302 let _ = write!(self.writer, "{}={}", display_name, value);
303 }
304 }
305
306 fn record_i64(&mut self, field: &Field, value: i64) {
307 let field_name = field.name();
308 if !self
309 .rule
310 .hidden_fields
311 .iter()
312 .any(|&hidden| hidden == field_name)
313 {
314 let display_name = self
315 .rule
316 .field_renames
317 .iter()
318 .find(|(from, _)| *from == field_name)
319 .map(|(_, to)| *to)
320 .unwrap_or(field_name);
321 let _ = write!(self.writer, "{}={}", display_name, value);
322 }
323 }
324
325 fn record_u64(&mut self, field: &Field, value: u64) {
326 let field_name = field.name();
327 if !self
328 .rule
329 .hidden_fields
330 .iter()
331 .any(|&hidden| hidden == field_name)
332 {
333 let display_name = self
334 .rule
335 .field_renames
336 .iter()
337 .find(|(from, _)| *from == field_name)
338 .map(|(_, to)| *to)
339 .unwrap_or(field_name);
340 let _ = write!(self.writer, "{}={}", display_name, value);
341 }
342 }
343
344 fn record_f64(&mut self, field: &Field, value: f64) {
345 let field_name = field.name();
346 if !self
347 .rule
348 .hidden_fields
349 .iter()
350 .any(|&hidden| hidden == field_name)
351 {
352 let display_name = self
353 .rule
354 .field_renames
355 .iter()
356 .find(|(from, _)| *from == field_name)
357 .map(|(_, to)| *to)
358 .unwrap_or(field_name);
359 let _ = write!(self.writer, "{}={}", display_name, value);
360 }
361 }
362
363 fn record_bool(&mut self, field: &Field, value: bool) {
364 let field_name = field.name();
365 if !self
366 .rule
367 .hidden_fields
368 .iter()
369 .any(|&hidden| hidden == field_name)
370 {
371 let display_name = self
372 .rule
373 .field_renames
374 .iter()
375 .find(|(from, _)| *from == field_name)
376 .map(|(_, to)| *to)
377 .unwrap_or(field_name);
378 let _ = write!(self.writer, "{}={}", display_name, value);
379 }
380 }
381}
382
383impl<S> Layer<S> for FieldTransformLayer<()>
385where
386 S: Subscriber + for<'a> LookupSpan<'a>,
387{
388 }
390
391impl<S> Layer<S> for FieldTransformLayer<TransformConfig>
392where
393 S: Subscriber + for<'a> LookupSpan<'a>,
394{
395 fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
396 let target = attrs.metadata().target();
398
399 if let Some(rule) = self
400 .transforms
401 .target_rules
402 .iter()
403 .find(|rule| target.contains(rule.target_pattern))
404 {
405 if let Some(span) = ctx.span(id) {
407 let mut extensions = span.extensions_mut();
408
409 let mut fields = FormattedFields::<TransformConfig>::new(String::new());
411 let mut visitor = TransformingVisitor::new(fields.as_writer(), rule);
412 attrs.record(&mut visitor);
413
414 extensions.insert(fields);
416 }
417 }
418 }
419
420 fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
421 if let Some(span) = ctx.span(id) {
422 let target = span.metadata().target();
423
424 if let Some(rule) = self
425 .transforms
426 .target_rules
427 .iter()
428 .find(|rule| target.contains(rule.target_pattern))
429 {
430 let mut extensions = span.extensions_mut();
431
432 if let Some(fields) = extensions.get_mut::<FormattedFields<TransformConfig>>() {
433 if !fields.fields.is_empty() {
435 fields.fields.push(' ');
436 }
437 let mut visitor = TransformingVisitor::new(fields.as_writer(), rule);
438 values.record(&mut visitor);
439 } else {
440 let mut fields = FormattedFields::<TransformConfig>::new(String::new());
442 let mut visitor = TransformingVisitor::new(fields.as_writer(), rule);
443 values.record(&mut visitor);
444 extensions.insert(fields);
445 }
446 }
447 }
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use crate::{layer::SubscriberExt, registry::Registry};
455 use tracing::{span, Level};
456
457 #[test]
458 fn test_zero_cost_when_no_transforms() {
459 let layer = FieldTransformLayer::new();
461
462 let subscriber = Registry::default().with(layer);
464
465 tracing::subscriber::with_default(subscriber, || {
467 let span = span!(Level::INFO, "test_span", field1 = "value1");
468 let _guard = span.enter();
469 });
470 }
471
472 #[test]
473 fn test_layer_creation_and_configuration() {
474 let layer = FieldTransformLayer::new()
476 .with_target_transform("kube", |builder| {
477 builder
478 .rename_field("resource_name", "k8s_resource")
479 .hide_field("internal_token")
480 .truncate_field("uid", 8)
481 .prefix_field("status", "🎯")
482 .transform_field("phase", |value| match value {
483 "\"Running\"" => "✅ Running".to_string(),
484 "\"Failed\"" => "❌ Failed".to_string(),
485 other => other.to_string(),
486 })
487 })
488 .with_target_transform("http", |builder| {
489 builder
490 .rename_field("method", "http_method")
491 .truncate_field("url", 50)
492 });
493
494 assert_eq!(layer.transforms.target_rules.len(), 2);
496
497 let kube_rule = &layer.transforms.target_rules[0];
498 assert_eq!(kube_rule.target_pattern, "kube");
499 assert_eq!(kube_rule.field_renames.len(), 1);
500 assert_eq!(
501 kube_rule.field_renames[0],
502 ("resource_name", "k8s_resource")
503 );
504 assert_eq!(kube_rule.hidden_fields.len(), 1);
505 assert_eq!(kube_rule.hidden_fields[0], "internal_token");
506 assert_eq!(kube_rule.field_transforms.len(), 3);
507
508 let http_rule = &layer.transforms.target_rules[1];
509 assert_eq!(http_rule.target_pattern, "http");
510 assert_eq!(http_rule.field_renames.len(), 1);
511 assert_eq!(http_rule.field_renames[0], ("method", "http_method"));
512 }
513
514 #[test]
515 fn test_target_rule_builder() {
516 let builder = TargetRuleBuilder::new("test_target");
518 let rule = builder
519 .rename_field("old", "new")
520 .hide_field("secret")
521 .truncate_field("long", 10)
522 .prefix_field("status", "🎯")
523 .transform_field("custom", |v| v.to_uppercase())
524 .build();
525
526 assert_eq!(rule.target_pattern, "test_target");
527 assert_eq!(rule.field_renames.len(), 1);
528 assert_eq!(rule.field_renames[0], ("old", "new"));
529 assert_eq!(rule.hidden_fields.len(), 1);
530 assert_eq!(rule.hidden_fields[0], "secret");
531 assert_eq!(rule.field_transforms.len(), 3);
532
533 assert_eq!(rule.field_transforms[0].field_name, "long");
535 assert_eq!(rule.field_transforms[1].field_name, "status");
536 assert_eq!(rule.field_transforms[2].field_name, "custom");
537
538 match &rule.field_transforms[0].transform_type {
539 TransformType::Truncate(n) => assert_eq!(*n, 10),
540 _ => panic!("Expected Truncate transform"),
541 }
542
543 match &rule.field_transforms[1].transform_type {
544 TransformType::Prefix(p) => assert_eq!(*p, "🎯"),
545 _ => panic!("Expected Prefix transform"),
546 }
547
548 match &rule.field_transforms[2].transform_type {
549 TransformType::Custom(_) => {} _ => panic!("Expected Custom transform"),
551 }
552 }
553
554 #[test]
555 fn test_transform_types() {
556 let value = "this_is_a_very_long_string";
558 let truncated = if value.len() > 10 {
559 format!("{}...", &value[..10])
560 } else {
561 value.to_string()
562 };
563 assert_eq!(truncated, "this_is_a_...");
564
565 let prefixed = format!("🎯 {}", "test_value");
567 assert_eq!(prefixed, "🎯 test_value");
568
569 let custom_transform = |value: &str| match value {
571 "running" => "✅ Running".to_string(),
572 "failed" => "❌ Failed".to_string(),
573 other => other.to_string(),
574 };
575 assert_eq!(custom_transform("running"), "✅ Running");
576 assert_eq!(custom_transform("failed"), "❌ Failed");
577 assert_eq!(custom_transform("other"), "other");
578 }
579
580 #[test]
581 fn test_integration_with_registry() {
582 let layer = FieldTransformLayer::new().with_target_transform("test_target", |builder| {
584 builder
585 .rename_field("field1", "renamed_field1")
586 .hide_field("secret")
587 });
588
589 let subscriber = Registry::default().with(layer);
590
591 tracing::subscriber::with_default(subscriber, || {
593 let span = tracing::span!(
594 target: "test_target",
595 Level::INFO,
596 "test_span",
597 field1 = "value1",
598 secret = "hidden_value",
599 visible = "visible_value"
600 );
601 let _guard = span.enter();
602
603 span.record("field2", &"value2");
605 });
606 }
607
608 #[test]
609 fn test_multiple_layer_composition() {
610 let transform_layer = FieldTransformLayer::new().with_target_transform("app", |builder| {
612 builder
613 .rename_field("user_id", "uid")
614 .hide_field("password")
615 });
616
617 let fmt_layer = crate::fmt::layer().with_target(true).with_level(true);
618
619 let subscriber = Registry::default().with(transform_layer).with(fmt_layer);
620
621 tracing::subscriber::with_default(subscriber, || {
623 let span = tracing::span!(
624 target: "app::auth",
625 Level::INFO,
626 "login",
627 user_id = 12345,
628 password = "secret123",
629 method = "oauth"
630 );
631 let _guard = span.enter();
632 });
633 }
634
635 #[test]
636 fn test_no_allocation_when_no_match() {
637 let layer = FieldTransformLayer::new()
639 .with_target_transform("specific_target", |builder| {
640 builder.rename_field("field", "renamed")
641 });
642
643 let subscriber = Registry::default().with(layer);
644
645 tracing::subscriber::with_default(subscriber, || {
646 let span = tracing::span!(
648 target: "different_target",
649 Level::INFO,
650 "test_span",
651 field = "value"
652 );
653 let _guard = span.enter();
654 });
655 }
656}