systemprompt_logging/
lib.rs1pub mod extension;
44pub mod layer;
45pub mod models;
46pub mod repository;
47pub mod runtime;
48pub mod sanitize;
49pub mod services;
50
51pub use extension::LoggingExtension;
52
53pub use layer::{DatabaseLayer, LogWriterHandle, LogWriterShutdownError};
54pub use models::{LogActor, LogEntry, LogFilter, LogLevel};
55pub use repository::{
56 AnalyticsEvent, AnalyticsRepository, LoggingOwnerReassignment, LoggingRepository,
57};
58pub use runtime::{LogSink, LoggingRuntime};
59#[cfg(feature = "cli")]
60pub use services::CliService;
61pub use services::{
62 BufferedNotice, ConsoleEventFormat, DatabaseLogService, FilterSystemFields, JsonEventFormat,
63 LogThrottle, LoggingMaintenanceService, OutputState, SYSTEM_LOG_OWNER, SystemSpan,
64};
65
66use std::sync::OnceLock;
67
68use layer::{InstanceLabel, ProxyDatabaseLayer};
69use systemprompt_database::DbPool;
70use systemprompt_identifiers::InstanceId;
71use tracing::Level;
72use tracing_subscriber::filter::FilterFn;
73use tracing_subscriber::layer::SubscriberExt;
74use tracing_subscriber::util::SubscriberInitExt;
75use tracing_subscriber::{EnvFilter, Layer};
76
77static SUBSCRIBER_INITIALIZED: OnceLock<()> = OnceLock::new();
80
81const NOISE_FILTERS: &[&str] = &[
82 "tokio_cron_scheduler=warn",
83 "sqlx::postgres::notice=warn",
84 "sqlx::query=warn",
85 "handlebars=warn",
86 "systemprompt_database::lifecycle=info",
87 "systemprompt_templates=info",
88 "systemprompt_extension::registry=info",
89 "systemprompt_api::services::middleware::session=info",
90 "rmcp=warn",
91 "rmcp::transport=warn",
92];
93
94fn build_filter(base: &str) -> EnvFilter {
95 let filter_str = std::iter::once(base.to_owned())
96 .chain(NOISE_FILTERS.iter().map(ToString::to_string))
97 .collect::<Vec<_>>()
98 .join(",");
99 EnvFilter::new(filter_str)
100}
101
102fn installed_proxy() -> Option<ProxyDatabaseLayer> {
103 tracing::dispatcher::get_default(|dispatch| {
104 dispatch.downcast_ref::<ProxyDatabaseLayer>().cloned()
105 })
106}
107
108fn ensure_subscriber(level_override: Option<&str>, output: OutputState) -> LogSink {
109 if SUBSCRIBER_INITIALIZED.set(()).is_err() {
110 return LogSink::from_proxy(installed_proxy().unwrap_or_default());
111 }
112
113 let proxy = ProxyDatabaseLayer::new();
114 let instance: InstanceLabel = proxy.shared().instance.clone();
115
116 let base_filter = level_override.map_or_else(
117 || EnvFilter::try_from_default_env().unwrap_or_else(|_| build_filter("info")),
118 |level| EnvFilter::try_from_default_env().unwrap_or_else(|_| build_filter(level)),
119 );
120
121 let gate_active = level_override.is_none();
122 let startup_output = output.clone();
123 let armed_output = output.clone();
124 let armed_gate = FilterFn::new(move |_| armed_output.is_console_armed());
125 let startup_gate = FilterFn::new(move |meta| {
126 !(gate_active && startup_output.is_startup_mode()) || *meta.level() <= Level::WARN
127 });
128
129 let fmt_layer = tracing_subscriber::fmt::layer()
130 .fmt_fields(FilterSystemFields::labelled(instance.clone()))
131 .event_format(ConsoleEventFormat::labelled(instance, output.clone()))
132 .with_writer(std::io::stderr)
133 .log_internal_errors(true)
134 .with_filter(base_filter)
135 .with_filter(startup_gate)
136 .with_filter(armed_gate);
137
138 let db_layer = proxy.clone().with_filter(build_filter("info"));
139
140 tracing_subscriber::registry()
141 .with(output)
142 .with(fmt_layer)
143 .with(db_layer)
144 .init();
145
146 LogSink::from_proxy(proxy)
147}
148
149pub fn init_logging(db_pool: &DbPool, instance_id: Option<InstanceId>) -> LogSink {
150 let output = OutputState::current();
151 let sink = ensure_subscriber(None, output.clone());
152 output.set_console_armed(true);
153 sink.attach(db_pool, instance_id);
154 sink
155}
156
157pub fn init_console_logging() -> LogSink {
158 init_console_logging_with_level(None, &OutputState::current())
159}
160
161pub fn init_console_logging_with_level(level: Option<&str>, output: &OutputState) -> LogSink {
162 let sink = ensure_subscriber(level, output.clone());
163 output.set_console_armed(true);
164 sink
165}
166
167pub fn init_deferred_console_logging(output: OutputState) -> LogSink {
168 output.set_console_armed(false);
169 ensure_subscriber(None, output)
170}