mod proxy;
mod visitor;
mod writer;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, OnceLock};
use tokio::sync::mpsc;
use tracing::{Event, Subscriber};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
pub use proxy::ProxyDatabaseLayer;
use proxy::{build_log_entry, record_span_fields, update_span_fields};
pub use writer::{LogWriterHandle, LogWriterShutdownError};
use self::writer::LogCommand;
use crate::models::{LogEntry, LogLevel};
use crate::repository::LoggingRepository;
use systemprompt_database::DbPool;
use systemprompt_identifiers::InstanceId;
const CHANNEL_CAPACITY: usize = 8192;
#[derive(Debug, Clone, Default)]
pub(crate) struct InstanceLabel(Arc<OnceLock<InstanceId>>);
impl InstanceLabel {
pub(crate) fn get(&self) -> Option<&InstanceId> {
self.0.get()
}
pub(crate) fn set(&self, instance_id: InstanceId) {
if let Err(rejected) = self.0.set(instance_id) {
tracing::debug!(instance_id = %rejected, "logging instance label already set; ignoring");
}
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct LayerShared {
pub(crate) instance: InstanceLabel,
dropped: Arc<AtomicU64>,
}
impl LayerShared {
pub(crate) fn dropped(&self) -> u64 {
self.dropped.load(Ordering::Relaxed)
}
pub(crate) fn record_drop(&self) {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
}
pub struct DatabaseLayer {
sender: mpsc::Sender<LogCommand>,
shared: LayerShared,
}
impl std::fmt::Debug for DatabaseLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DatabaseLayer")
.field("dropped", &self.dropped_entries())
.finish_non_exhaustive()
}
}
impl DatabaseLayer {
pub fn new(db_pool: &DbPool) -> (Self, LogWriterHandle) {
Self::with_shared(db_pool, LayerShared::default())
}
pub(crate) fn with_shared(db_pool: &DbPool, shared: LayerShared) -> (Self, LogWriterHandle) {
let (sender, receiver) = mpsc::channel(CHANNEL_CAPACITY);
let writer =
LogWriterHandle::spawn(LoggingRepository::new(db_pool), sender.clone(), receiver);
(Self { sender, shared }, writer)
}
#[must_use]
pub fn dropped_entries(&self) -> u64 {
self.shared.dropped()
}
pub fn enqueue(&self, mut entry: LogEntry) {
if entry.instance_id.is_none() {
entry.instance_id = self.shared.instance.get().cloned();
}
let is_error = entry.level == LogLevel::Error;
if self
.sender
.try_send(LogCommand::Entry(Box::new(entry)))
.is_err()
{
self.shared.record_drop();
return;
}
if is_error {
let _flush_requested = self.sender.try_send(LogCommand::FlushNow).is_ok();
}
}
}
impl<S> Layer<S> for DatabaseLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(
&self,
attrs: &tracing::span::Attributes<'_>,
id: &tracing::span::Id,
ctx: Context<'_, S>,
) {
record_span_fields(attrs, id, &ctx);
}
fn on_record(
&self,
id: &tracing::span::Id,
values: &tracing::span::Record<'_>,
ctx: Context<'_, S>,
) {
update_span_fields(id, values, &ctx);
}
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
if let Some(entry) = build_log_entry(event, &ctx) {
self.enqueue(entry);
}
}
}