systemprompt_logging/services/output/
mod.rs1use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::{Arc, Mutex};
15
16use systemprompt_manifest::profile::LogFormat;
17use tracing::Subscriber;
18use tracing_subscriber::Layer;
19
20#[derive(Debug, Clone)]
21pub struct BufferedNotice {
22 pub level: String,
23 pub text: String,
24}
25
26#[derive(Debug)]
27struct Inner {
28 startup: AtomicBool,
29 structured: AtomicBool,
30 structured_emitted: AtomicBool,
31 json_console: AtomicBool,
32 console_armed: AtomicBool,
33 notices: Mutex<Vec<BufferedNotice>>,
34}
35
36#[derive(Debug, Clone)]
38pub struct OutputState {
39 inner: Arc<Inner>,
40}
41
42impl Default for OutputState {
43 fn default() -> Self {
44 Self {
45 inner: Arc::new(Inner {
46 startup: AtomicBool::new(true),
47 structured: AtomicBool::new(false),
48 structured_emitted: AtomicBool::new(false),
49 json_console: AtomicBool::new(false),
50 console_armed: AtomicBool::new(true),
51 notices: Mutex::new(Vec::new()),
52 }),
53 }
54 }
55}
56
57impl<S: Subscriber> Layer<S> for OutputState {}
58
59impl OutputState {
60 #[must_use]
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 #[must_use]
66 pub fn current() -> Self {
67 tracing::dispatcher::get_default(|dispatch| dispatch.downcast_ref::<Self>().cloned())
68 .unwrap_or_default()
69 }
70
71 pub fn set_startup_mode(&self, enabled: bool) {
72 self.inner.startup.store(enabled, Ordering::SeqCst);
73 }
74
75 #[must_use]
76 pub fn is_startup_mode(&self) -> bool {
77 self.inner.startup.load(Ordering::SeqCst)
78 }
79
80 pub fn set_structured_output(&self, enabled: bool) {
81 self.inner.structured.store(enabled, Ordering::SeqCst);
82 }
83
84 #[must_use]
85 pub fn is_structured_output(&self) -> bool {
86 self.inner.structured.load(Ordering::SeqCst)
87 }
88
89 pub fn mark_structured_emitted(&self) {
90 self.inner.structured_emitted.store(true, Ordering::SeqCst);
91 }
92
93 pub fn reset_structured_emitted(&self) {
94 self.inner.structured_emitted.store(false, Ordering::SeqCst);
95 }
96
97 #[must_use]
98 pub fn structured_was_emitted(&self) -> bool {
99 self.inner.structured_emitted.load(Ordering::SeqCst)
100 }
101
102 pub fn set_console_format(&self, format: LogFormat) {
103 self.inner
104 .json_console
105 .store(matches!(format, LogFormat::Json), Ordering::Relaxed);
106 }
107
108 #[must_use]
109 pub fn console_format(&self) -> LogFormat {
110 if self.is_json_console() {
111 LogFormat::Json
112 } else {
113 LogFormat::Text
114 }
115 }
116
117 pub(crate) fn is_json_console(&self) -> bool {
118 self.inner.json_console.load(Ordering::Relaxed)
119 }
120
121 pub(crate) fn set_console_armed(&self, armed: bool) {
122 self.inner.console_armed.store(armed, Ordering::SeqCst);
123 }
124
125 pub(crate) fn is_console_armed(&self) -> bool {
126 self.inner.console_armed.load(Ordering::SeqCst)
127 }
128
129 pub fn buffer_notice(&self, level: &str, text: &str) {
130 if let Ok(mut buf) = self.inner.notices.lock() {
131 buf.push(BufferedNotice {
132 level: level.to_owned(),
133 text: text.to_owned(),
134 });
135 }
136 }
137
138 #[must_use]
139 pub fn drain_notices(&self) -> Vec<BufferedNotice> {
140 self.inner
141 .notices
142 .lock()
143 .map(|mut buf| std::mem::take(&mut *buf))
144 .unwrap_or_default()
145 }
146}