systemprompt_logging/services/
json_format.rs1use std::fmt;
17use std::sync::atomic::{AtomicBool, Ordering};
18
19use serde_json::{Map, Value};
20use systemprompt_manifest::profile::LogFormat;
21use tracing::field::{Field, Visit};
22use tracing::{Event, Subscriber};
23use tracing_subscriber::fmt::format::{Format, Full, Writer};
24use tracing_subscriber::fmt::time::SystemTime;
25use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields, FormattedFields};
26use tracing_subscriber::registry::LookupSpan;
27
28use crate::sanitize::{REDACTION_PLACEHOLDER, is_redacted, is_system_sentinel};
29
30static JSON_CONSOLE: AtomicBool = AtomicBool::new(false);
31
32pub fn set_console_format(format: LogFormat) {
33 JSON_CONSOLE.store(matches!(format, LogFormat::Json), Ordering::Relaxed);
34}
35
36#[must_use]
37pub fn console_format() -> LogFormat {
38 if JSON_CONSOLE.load(Ordering::Relaxed) {
39 LogFormat::Json
40 } else {
41 LogFormat::Text
42 }
43}
44
45#[derive(Debug, Clone, Copy, Default)]
47pub struct JsonEventFormat;
48
49#[derive(Debug, Clone, Default)]
51pub struct ConsoleEventFormat {
52 text: Format<Full, SystemTime>,
53 json: JsonEventFormat,
54}
55
56impl<S, N> FormatEvent<S, N> for ConsoleEventFormat
57where
58 S: Subscriber + for<'a> LookupSpan<'a>,
59 N: for<'a> FormatFields<'a> + 'static,
60{
61 fn format_event(
62 &self,
63 ctx: &FmtContext<'_, S, N>,
64 writer: Writer<'_>,
65 event: &Event<'_>,
66 ) -> fmt::Result {
67 if JSON_CONSOLE.load(Ordering::Relaxed) {
68 self.json.format_event(ctx, writer, event)
69 } else {
70 self.text.format_event(ctx, writer, event)
71 }
72 }
73}
74
75impl<S, N> FormatEvent<S, N> for JsonEventFormat
76where
77 S: Subscriber + for<'a> LookupSpan<'a>,
78 N: for<'a> FormatFields<'a> + 'static,
79{
80 fn format_event(
81 &self,
82 ctx: &FmtContext<'_, S, N>,
83 mut writer: Writer<'_>,
84 event: &Event<'_>,
85 ) -> fmt::Result {
86 let meta = event.metadata();
87 let mut fields = JsonFieldVisitor::default();
88 event.record(&mut fields);
89
90 let mut object = Map::new();
91 object.insert(
92 "timestamp".to_owned(),
93 Value::String(chrono::Utc::now().to_rfc3339()),
94 );
95 object.insert("level".to_owned(), Value::String(meta.level().to_string()));
96 object.insert("target".to_owned(), Value::String(meta.target().to_owned()));
97 object.insert("fields".to_owned(), Value::Object(fields.fields));
98
99 let spans: Vec<Value> = ctx
100 .event_scope()
101 .into_iter()
102 .flat_map(tracing_subscriber::registry::Scope::from_root)
103 .map(|span| {
104 let mut entry = Map::new();
105 entry.insert("name".to_owned(), Value::String(span.name().to_owned()));
106 if let Some(rendered) = span.extensions().get::<FormattedFields<N>>()
107 && !rendered.is_empty()
108 {
109 entry.insert("fields".to_owned(), Value::String(rendered.to_string()));
110 }
111 Value::Object(entry)
112 })
113 .collect();
114 if !spans.is_empty() {
115 object.insert("spans".to_owned(), Value::Array(spans));
116 }
117 if let Some(instance_id) = crate::instance_id() {
118 object.insert(
119 "instance".to_owned(),
120 Value::String(instance_id.as_str().to_owned()),
121 );
122 }
123
124 let line = serde_json::to_string(&Value::Object(object)).map_err(|_encode| fmt::Error)?;
125 writeln!(writer, "{line}")
126 }
127}
128
129#[derive(Debug, Default)]
130struct JsonFieldVisitor {
131 fields: Map<String, Value>,
134}
135
136impl JsonFieldVisitor {
137 fn insert(&mut self, field: &Field, value: Value) {
139 let value = if is_redacted(field.name()) {
140 Value::String(REDACTION_PLACEHOLDER.to_owned())
141 } else {
142 value
143 };
144 self.fields.insert(field.name().to_owned(), value);
145 }
146}
147
148impl Visit for JsonFieldVisitor {
149 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
150 let rendered = format!("{value:?}");
151 if is_system_sentinel(&rendered) {
152 return;
153 }
154 self.insert(field, Value::String(rendered));
155 }
156
157 fn record_str(&mut self, field: &Field, value: &str) {
158 if is_system_sentinel(value) {
159 return;
160 }
161 self.insert(field, Value::String(value.to_owned()));
162 }
163
164 fn record_i64(&mut self, field: &Field, value: i64) {
165 self.insert(field, Value::from(value));
166 }
167
168 fn record_u64(&mut self, field: &Field, value: u64) {
169 self.insert(field, Value::from(value));
170 }
171
172 fn record_f64(&mut self, field: &Field, value: f64) {
173 self.insert(field, Value::from(value));
174 }
175
176 fn record_bool(&mut self, field: &Field, value: bool) {
177 self.insert(field, Value::from(value));
178 }
179}