Skip to main content

systemprompt_logging/services/
format.rs

1//! Console formatter that filters system fields and renders structured values.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use std::fmt::{self, Write};
7use tracing::field::{Field, Visit};
8use tracing_subscriber::field::{MakeVisitor, VisitFmt, VisitOutput};
9use tracing_subscriber::fmt::format::Writer;
10
11use crate::sanitize::{REDACTION_PLACEHOLDER, escape_control, is_redacted, is_system_sentinel};
12
13#[derive(Debug, Clone, Copy, Default)]
14pub struct FilterSystemFields;
15
16impl FilterSystemFields {
17    pub const fn new() -> Self {
18        Self
19    }
20}
21
22#[derive(Debug)]
23pub struct FilteringVisitor<'a> {
24    writer: Writer<'a>,
25    is_first: bool,
26    result: fmt::Result,
27}
28
29impl<'a> FilteringVisitor<'a> {
30    const fn new(writer: Writer<'a>) -> Self {
31        Self {
32            writer,
33            is_first: true,
34            result: Ok(()),
35        }
36    }
37
38    fn record_filtered(&mut self, field: &Field, value: &dyn fmt::Debug) {
39        if self.result.is_err() {
40            return;
41        }
42
43        let debug_str = format!("{:?}", value);
44        if is_system_sentinel(&debug_str) {
45            return;
46        }
47
48        self.write_value(field.name(), &debug_str);
49    }
50
51    /// Writes a primitive field verbatim, bypassing the name-based redaction in
52    /// [`Self::write_value`]. A number or bool cannot carry a secret, and
53    /// blanking one only destroys operational data — a delete count named
54    /// `oauth_tokens` matched the `token` substring and rendered as
55    /// `[REDACTED]`.
56    fn write_scalar(&mut self, field: &Field, value: impl fmt::Display) {
57        if self.result.is_err() {
58            return;
59        }
60        self.result = self.write_field(field.name(), &value.to_string());
61    }
62
63    fn write_value(&mut self, name: &str, rendered: &str) {
64        let safe = if is_redacted(name) {
65            REDACTION_PLACEHOLDER.to_owned()
66        } else {
67            escape_control(rendered)
68        };
69        self.result = self.write_field(name, &safe);
70    }
71
72    fn write_field(&mut self, name: &str, value: &str) -> fmt::Result {
73        if self.is_first {
74            self.is_first = false;
75        } else {
76            self.writer.write_char(' ')?;
77        }
78        write!(self.writer, "{}={}", name, value)
79    }
80}
81
82impl Visit for FilteringVisitor<'_> {
83    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
84        self.record_filtered(field, value);
85    }
86
87    fn record_str(&mut self, field: &Field, value: &str) {
88        if self.result.is_err() {
89            return;
90        }
91        if is_system_sentinel(value) {
92            return;
93        }
94        self.write_value(field.name(), &format!("{:?}", value));
95    }
96
97    fn record_i64(&mut self, field: &Field, value: i64) {
98        self.write_scalar(field, value);
99    }
100
101    fn record_u64(&mut self, field: &Field, value: u64) {
102        self.write_scalar(field, value);
103    }
104
105    fn record_i128(&mut self, field: &Field, value: i128) {
106        self.write_scalar(field, value);
107    }
108
109    fn record_u128(&mut self, field: &Field, value: u128) {
110        self.write_scalar(field, value);
111    }
112
113    fn record_f64(&mut self, field: &Field, value: f64) {
114        self.write_scalar(field, value);
115    }
116
117    fn record_bool(&mut self, field: &Field, value: bool) {
118        self.write_scalar(field, value);
119    }
120}
121
122impl VisitOutput<fmt::Result> for FilteringVisitor<'_> {
123    fn finish(self) -> fmt::Result {
124        self.result
125    }
126}
127
128impl VisitFmt for FilteringVisitor<'_> {
129    fn writer(&mut self) -> &mut dyn Write {
130        &mut self.writer
131    }
132}
133
134impl<'a> MakeVisitor<Writer<'a>> for FilterSystemFields {
135    type Visitor = FilteringVisitor<'a>;
136
137    fn make_visitor(&self, target: Writer<'a>) -> Self::Visitor {
138        FilteringVisitor::new(target)
139    }
140}