systemprompt_logging/
lib.rs1pub mod attribution;
31pub mod extension;
32pub mod layer;
33pub mod models;
34pub mod repository;
35pub mod sanitize;
36pub mod services;
37
38pub use attribution::{LogAttributionUnset, install_log_attribution, platform_attribution};
39pub use extension::LoggingExtension;
40
41pub use layer::{DatabaseLayer, LogWriterHandle, LogWriterShutdownError, enqueue_background};
42pub use models::{LogActor, LogEntry, LogFilter, LogLevel};
43pub use repository::{
44 AnalyticsEvent, AnalyticsRepository, LoggingOwnerReassignment, LoggingRepository,
45};
46#[cfg(feature = "cli")]
47pub use services::CliService;
48pub use services::{
49 BufferedNotice, DatabaseLogService, FilterSystemFields, LogThrottle, LoggingMaintenanceService,
50 SystemSpan, buffer_notice, drain_notices, is_startup_mode, is_structured_output,
51 mark_structured_emitted, reset_structured_emitted, set_startup_mode, set_structured_output,
52 structured_was_emitted,
53};
54
55use std::sync::{Mutex, OnceLock, PoisonError};
56
57use layer::ProxyDatabaseLayer;
58use systemprompt_database::DbPool;
59use systemprompt_identifiers::InstanceId;
60use tracing::Level;
61use tracing_subscriber::filter::FilterFn;
62use tracing_subscriber::layer::SubscriberExt;
63use tracing_subscriber::util::SubscriberInitExt;
64use tracing_subscriber::{EnvFilter, Layer};
65
66static SUBSCRIBER_INITIALIZED: OnceLock<()> = OnceLock::new();
67static INSTANCE_ID: OnceLock<InstanceId> = OnceLock::new();
68
69pub fn set_instance_id(instance_id: InstanceId) {
70 if INSTANCE_ID.set(instance_id).is_err() {
71 tracing::debug!("logging instance id already set; ignoring");
72 }
73}
74
75#[must_use]
76pub fn instance_id() -> Option<&'static InstanceId> {
77 INSTANCE_ID.get()
78}
79static DB_PROXY: OnceLock<ProxyDatabaseLayer> = OnceLock::new();
80static DB_WRITER: Mutex<Option<LogWriterHandle>> = Mutex::new(None);
81
82const NOISE_FILTERS: &[&str] = &[
83 "tokio_cron_scheduler=warn",
84 "sqlx::postgres::notice=warn",
85 "sqlx::query=warn",
86 "handlebars=warn",
87 "systemprompt_database::lifecycle=info",
88 "systemprompt_templates=info",
89 "systemprompt_extension::registry=info",
90 "systemprompt_api::services::middleware::session=info",
91 "rmcp=warn",
92 "rmcp::transport=warn",
93];
94
95fn build_filter(base: &str) -> EnvFilter {
96 let filter_str = std::iter::once(base.to_owned())
97 .chain(NOISE_FILTERS.iter().map(ToString::to_string))
98 .collect::<Vec<_>>()
99 .join(",");
100 EnvFilter::new(filter_str)
101}
102
103fn ensure_subscriber(level_override: Option<&str>) {
104 if SUBSCRIBER_INITIALIZED.set(()).is_err() {
105 return;
106 }
107
108 let base_filter = level_override.map_or_else(
109 || EnvFilter::try_from_default_env().unwrap_or_else(|_| build_filter("info")),
110 |level| EnvFilter::try_from_default_env().unwrap_or_else(|_| build_filter(level)),
111 );
112
113 let gate_active = level_override.is_none();
114 let startup_gate = FilterFn::new(move |meta| {
115 !(gate_active && is_startup_mode()) || *meta.level() <= Level::WARN
116 });
117
118 let fmt_layer = tracing_subscriber::fmt::layer()
119 .fmt_fields(FilterSystemFields::new())
120 .with_target(true)
121 .with_writer(std::io::stderr)
122 .log_internal_errors(true)
123 .with_filter(base_filter)
124 .with_filter(startup_gate);
125
126 let proxy = DB_PROXY.get_or_init(ProxyDatabaseLayer::new).clone();
127 let db_layer = proxy.with_filter(build_filter("info"));
128
129 tracing_subscriber::registry()
130 .with(fmt_layer)
131 .with(db_layer)
132 .init();
133}
134
135pub fn init_logging(db_pool: &DbPool) {
136 ensure_subscriber(None);
137
138 let proxy = DB_PROXY.get_or_init(ProxyDatabaseLayer::new);
139 if let Some(writer) = proxy.attach(db_pool) {
140 *DB_WRITER.lock().unwrap_or_else(PoisonError::into_inner) = Some(writer);
141 }
142}
143
144pub async fn shutdown_database_logging() -> Result<(), LogWriterShutdownError> {
145 let writer = DB_WRITER
146 .lock()
147 .unwrap_or_else(PoisonError::into_inner)
148 .take();
149 let Some(writer) = writer else {
150 return Ok(());
151 };
152 writer.shutdown().await
153}
154
155pub fn init_console_logging() {
156 init_console_logging_with_level(None);
157}
158
159pub fn init_console_logging_with_level(level: Option<&str>) {
160 ensure_subscriber(level);
161}