Skip to main content

systemprompt_logging/services/output/
mod.rs

1//! Process-global log-event publishing.
2//!
3//! Holds the once-initialised [`LogEventPublisher`] and a startup-mode flag,
4//! letting any crate emit a [`LogEventData`] via [`publish_log`] without
5//! threading the publisher through call sites. Before a publisher is installed
6//! (e.g. early boot), `publish_log` is a no-op.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::{Arc, Mutex, OnceLock};
13
14use chrono::Utc;
15use systemprompt_traits::{LogEventData, LogEventLevel, LogEventPublisher};
16
17static STARTUP_MODE: AtomicBool = AtomicBool::new(true);
18
19static LOG_PUBLISHER: OnceLock<Arc<dyn LogEventPublisher>> = OnceLock::new();
20
21static STRUCTURED_MODE: AtomicBool = AtomicBool::new(false);
22
23static STRUCTURED_EMITTED: AtomicBool = AtomicBool::new(false);
24
25static NOTICE_BUFFER: OnceLock<Mutex<Vec<BufferedNotice>>> = OnceLock::new();
26
27#[derive(Debug, Clone)]
28pub struct BufferedNotice {
29    pub level: String,
30    pub text: String,
31}
32
33pub fn set_startup_mode(enabled: bool) {
34    STARTUP_MODE.store(enabled, Ordering::SeqCst);
35}
36
37#[must_use]
38pub fn is_startup_mode() -> bool {
39    STARTUP_MODE.load(Ordering::SeqCst)
40}
41
42pub fn set_structured_output(enabled: bool) {
43    STRUCTURED_MODE.store(enabled, Ordering::SeqCst);
44}
45
46#[must_use]
47pub fn is_structured_output() -> bool {
48    STRUCTURED_MODE.load(Ordering::SeqCst)
49}
50
51pub fn mark_structured_emitted() {
52    STRUCTURED_EMITTED.store(true, Ordering::SeqCst);
53}
54
55#[must_use]
56pub fn structured_was_emitted() -> bool {
57    STRUCTURED_EMITTED.load(Ordering::SeqCst)
58}
59
60fn notice_buffer() -> &'static Mutex<Vec<BufferedNotice>> {
61    NOTICE_BUFFER.get_or_init(|| Mutex::new(Vec::new()))
62}
63
64pub fn buffer_notice(level: &str, text: &str) {
65    if let Ok(mut buf) = notice_buffer().lock() {
66        buf.push(BufferedNotice {
67            level: level.to_owned(),
68            text: text.to_owned(),
69        });
70    }
71}
72
73#[must_use]
74pub fn drain_notices() -> Vec<BufferedNotice> {
75    notice_buffer()
76        .lock()
77        .map(|mut buf| std::mem::take(&mut *buf))
78        .unwrap_or_default()
79}
80
81pub fn set_log_publisher(publisher: Arc<dyn LogEventPublisher>) {
82    if LOG_PUBLISHER.set(publisher).is_err() {
83        tracing::warn!("Log publisher already initialized, ignoring duplicate registration");
84    }
85}
86
87#[must_use]
88pub fn get_log_publisher() -> Option<&'static Arc<dyn LogEventPublisher>> {
89    LOG_PUBLISHER.get()
90}
91
92pub fn publish_log(level: LogEventLevel, module: &str, message: &str) {
93    if let Some(publisher) = LOG_PUBLISHER.get() {
94        publisher.publish_log(LogEventData::new(Utc::now(), level, module, message));
95    }
96}