systemprompt_logging/layer/
mod.rs1mod proxy;
14mod visitor;
15mod writer;
16
17use std::sync::atomic::{AtomicU64, Ordering};
18use std::sync::{Arc, OnceLock};
19
20use tokio::sync::mpsc;
21use tracing::{Event, Subscriber};
22use tracing_subscriber::Layer;
23use tracing_subscriber::layer::Context;
24use tracing_subscriber::registry::LookupSpan;
25
26pub use proxy::ProxyDatabaseLayer;
27use proxy::{build_log_entry, record_span_fields, update_span_fields};
28pub use writer::{LogWriterHandle, LogWriterShutdownError};
29
30use self::writer::LogCommand;
31use crate::models::{LogEntry, LogLevel};
32use crate::repository::LoggingRepository;
33use systemprompt_database::DbPool;
34
35const CHANNEL_CAPACITY: usize = 8192;
36
37static BACKGROUND_SENDER: OnceLock<mpsc::Sender<LogCommand>> = OnceLock::new();
38static BACKGROUND_DROPPED: AtomicU64 = AtomicU64::new(0);
39
40pub fn enqueue_background(entry: LogEntry) {
41 let Some(sender) = BACKGROUND_SENDER.get() else {
42 BACKGROUND_DROPPED.fetch_add(1, Ordering::Relaxed);
43 return;
44 };
45 let is_error = entry.level == LogLevel::Error;
46 if sender.try_send(LogCommand::Entry(Box::new(entry))).is_err() {
47 BACKGROUND_DROPPED.fetch_add(1, Ordering::Relaxed);
48 return;
49 }
50 if is_error {
51 let _flush_requested = sender.try_send(LogCommand::FlushNow).is_ok();
54 }
55}
56
57struct LogChannel {
58 sender: mpsc::Sender<LogCommand>,
59 dropped: Arc<AtomicU64>,
60}
61
62impl LogChannel {
63 fn new(capacity: usize) -> (Self, mpsc::Receiver<LogCommand>) {
64 let (sender, receiver) = mpsc::channel(capacity);
65 let channel = Self {
66 sender,
67 dropped: Arc::new(AtomicU64::new(0)),
68 };
69 (channel, receiver)
70 }
71
72 fn send(&self, command: LogCommand) {
73 if let Err(mpsc::error::TrySendError::Full(_)) = self.sender.try_send(command) {
74 self.dropped.fetch_add(1, Ordering::Relaxed);
75 }
76 }
77
78 fn dropped(&self) -> u64 {
79 self.dropped.load(Ordering::Relaxed)
80 }
81}
82
83pub struct DatabaseLayer {
84 channel: LogChannel,
85}
86
87impl std::fmt::Debug for DatabaseLayer {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("DatabaseLayer")
90 .field("dropped", &self.channel.dropped())
91 .finish_non_exhaustive()
92 }
93}
94
95impl DatabaseLayer {
96 pub fn new(db_pool: &DbPool) -> (Self, LogWriterHandle) {
97 let (channel, receiver) = LogChannel::new(CHANNEL_CAPACITY);
98
99 BACKGROUND_SENDER.get_or_init(|| channel.sender.clone());
100
101 let writer = LogWriterHandle::spawn(
102 LoggingRepository::new(db_pool),
103 channel.sender.clone(),
104 receiver,
105 );
106
107 (Self { channel }, writer)
108 }
109}
110
111impl DatabaseLayer {
112 fn send_entry(&self, entry: LogEntry) {
113 let is_error = entry.level == LogLevel::Error;
114 self.channel.send(LogCommand::Entry(Box::new(entry)));
115 if is_error {
116 self.channel.send(LogCommand::FlushNow);
117 }
118 }
119}
120
121impl<S> Layer<S> for DatabaseLayer
122where
123 S: Subscriber + for<'a> LookupSpan<'a>,
124{
125 fn on_new_span(
126 &self,
127 attrs: &tracing::span::Attributes<'_>,
128 id: &tracing::span::Id,
129 ctx: Context<'_, S>,
130 ) {
131 record_span_fields(attrs, id, &ctx);
132 }
133
134 fn on_record(
135 &self,
136 id: &tracing::span::Id,
137 values: &tracing::span::Record<'_>,
138 ctx: Context<'_, S>,
139 ) {
140 update_span_fields(id, values, &ctx);
141 }
142
143 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
144 if let Some(entry) = build_log_entry(event, &ctx) {
145 self.send_entry(entry);
146 }
147 }
148}