diff --git a/sqlx-core/src/any/options.rs b/sqlx-core/src/any/options.rs index 33a9845e..a28ca099 100644 --- a/sqlx-core/src/any/options.rs +++ b/sqlx-core/src/any/options.rs @@ -2,7 +2,9 @@ use crate::any::AnyConnection; use crate::connection::ConnectOptions; use crate::error::Error; use futures_core::future::BoxFuture; +use log::LevelFilter; use std::str::FromStr; +use std::time::Duration; #[cfg(feature = "postgres")] use crate::postgres::PgConnectOptions; @@ -120,4 +122,54 @@ impl ConnectOptions for AnyConnectOptions { fn connect(&self) -> BoxFuture<'_, Result> { Box::pin(AnyConnection::establish(self)) } + + fn log_statements(&mut self, level: LevelFilter) -> &mut Self { + match &mut self.0 { + #[cfg(feature = "postgres")] + AnyConnectOptionsKind::Postgres(o) => { + o.log_statements(level); + } + + #[cfg(feature = "mysql")] + AnyConnectOptionsKind::MySql(o) => { + o.log_statements(level); + } + + #[cfg(feature = "sqlite")] + AnyConnectOptionsKind::Sqlite(o) => { + o.log_statements(level); + } + + #[cfg(feature = "mssql")] + AnyConnectOptionsKind::Mssql(o) => { + o.log_statements(level); + } + }; + self + } + + fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { + match &mut self.0 { + #[cfg(feature = "postgres")] + AnyConnectOptionsKind::Postgres(o) => { + o.log_slow_statements(level, duration); + } + + #[cfg(feature = "mysql")] + AnyConnectOptionsKind::MySql(o) => { + o.log_slow_statements(level, duration); + } + + #[cfg(feature = "sqlite")] + AnyConnectOptionsKind::Sqlite(o) => { + o.log_slow_statements(level, duration); + } + + #[cfg(feature = "mssql")] + AnyConnectOptionsKind::Mssql(o) => { + o.log_slow_statements(level, duration); + } + }; + self + } } diff --git a/sqlx-core/src/connection.rs b/sqlx-core/src/connection.rs index 2fe8b7ca..d4b0a028 100644 --- a/sqlx-core/src/connection.rs +++ b/sqlx-core/src/connection.rs @@ -3,8 +3,10 @@ use crate::error::Error; use crate::transaction::Transaction; use futures_core::future::BoxFuture; use futures_core::Future; +use log::LevelFilter; use std::fmt::Debug; use std::str::FromStr; +use std::time::Duration; /// Represents a single database connection. pub trait Connection: Send { @@ -108,6 +110,33 @@ pub trait Connection: Send { } } +#[derive(Clone, Debug)] +pub(crate) struct LogSettings { + pub(crate) statements_level: LevelFilter, + pub(crate) slow_statements_level: LevelFilter, + pub(crate) slow_statements_duration: Duration, +} + +impl Default for LogSettings { + fn default() -> Self { + LogSettings { + statements_level: LevelFilter::Info, + slow_statements_level: LevelFilter::Warn, + slow_statements_duration: Duration::from_secs(1), + } + } +} + +impl LogSettings { + pub(crate) fn log_statements(&mut self, level: LevelFilter) { + self.statements_level = level; + } + pub(crate) fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) { + self.slow_statements_level = level; + self.slow_statements_duration = duration; + } +} + pub trait ConnectOptions: 'static + Send + Sync + FromStr + Debug { type Connection: Connection + ?Sized; @@ -115,4 +144,11 @@ pub trait ConnectOptions: 'static + Send + Sync + FromStr + Debug { fn connect(&self) -> BoxFuture<'_, Result> where Self::Connection: Sized; + + /// Log executed statements with the specified `level` + fn log_statements(&mut self, level: LevelFilter) -> &mut Self; + + /// Log executed statements with a duration above the specified `duration` + /// at the specified `level`. + fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self; } diff --git a/sqlx-core/src/logger.rs b/sqlx-core/src/logger.rs index 5cf76c81..0abbfc6e 100644 --- a/sqlx-core/src/logger.rs +++ b/sqlx-core/src/logger.rs @@ -1,20 +1,20 @@ -use log::Level; -use std::time::{Duration, Instant}; - -const SLOW_QUERY_THRESHOLD: Duration = Duration::from_secs(1); +use crate::connection::LogSettings; +use std::time::Instant; pub(crate) struct QueryLogger<'q> { sql: &'q str, rows: usize, start: Instant, + settings: LogSettings, } impl<'q> QueryLogger<'q> { - pub(crate) fn new(sql: &'q str) -> Self { + pub(crate) fn new(sql: &'q str, settings: LogSettings) -> Self { Self { sql, rows: 0, start: Instant::now(), + settings, } } @@ -25,42 +25,44 @@ impl<'q> QueryLogger<'q> { pub(crate) fn finish(&self) { let elapsed = self.start.elapsed(); - let lvl = if elapsed >= SLOW_QUERY_THRESHOLD { - Level::Warn + let lvl = if elapsed >= self.settings.slow_statements_duration { + self.settings.slow_statements_level } else { - Level::Info + self.settings.statements_level }; - if lvl <= log::STATIC_MAX_LEVEL && lvl <= log::max_level() { - let mut summary = parse_query_summary(&self.sql); + if let Some(lvl) = lvl.to_level() { + if lvl <= log::STATIC_MAX_LEVEL && lvl <= log::max_level() { + let mut summary = parse_query_summary(&self.sql); - let sql = if summary != self.sql { - summary.push_str(" …"); - format!( - "\n\n{}\n", - sqlformat::format( - &self.sql, - &sqlformat::QueryParams::None, - sqlformat::FormatOptions::default() + let sql = if summary != self.sql { + summary.push_str(" …"); + format!( + "\n\n{}\n", + sqlformat::format( + &self.sql, + &sqlformat::QueryParams::None, + sqlformat::FormatOptions::default() + ) ) - ) - } else { - String::new() - }; + } else { + String::new() + }; - let rows = self.rows; + let rows = self.rows; - log::logger().log( - &log::Record::builder() - .args(format_args!( - "{}; rows: {}, elapsed: {:.3?}{}", - summary, rows, elapsed, sql - )) - .level(lvl) - .module_path_static(Some("sqlx::query")) - .target("sqlx::query") - .build(), - ); + log::logger().log( + &log::Record::builder() + .args(format_args!( + "{}; rows: {}, elapsed: {:.3?}{}", + summary, rows, elapsed, sql + )) + .level(lvl) + .module_path_static(Some("sqlx::query")) + .target("sqlx::query") + .build(), + ); + } } } } diff --git a/sqlx-core/src/mssql/connection/establish.rs b/sqlx-core/src/mssql/connection/establish.rs index bff390cc..9b2d8261 100644 --- a/sqlx-core/src/mssql/connection/establish.rs +++ b/sqlx-core/src/mssql/connection/establish.rs @@ -82,6 +82,7 @@ impl MssqlConnection { Ok(Self { stream, cache_statement: StatementCache::new(1024), + log_settings: options.log_settings.clone(), }) } } diff --git a/sqlx-core/src/mssql/connection/executor.rs b/sqlx-core/src/mssql/connection/executor.rs index c4ceda88..3a53c4db 100644 --- a/sqlx-core/src/mssql/connection/executor.rs +++ b/sqlx-core/src/mssql/connection/executor.rs @@ -78,7 +78,7 @@ impl<'c> Executor<'c> for &'c mut MssqlConnection { { let sql = query.sql(); let arguments = query.take_arguments(); - let mut logger = QueryLogger::new(sql); + let mut logger = QueryLogger::new(sql, self.log_settings.clone()); Box::pin(try_stream! { self.run(sql, arguments).await?; diff --git a/sqlx-core/src/mssql/connection/mod.rs b/sqlx-core/src/mssql/connection/mod.rs index ec8532c3..143ed4c0 100644 --- a/sqlx-core/src/mssql/connection/mod.rs +++ b/sqlx-core/src/mssql/connection/mod.rs @@ -1,5 +1,5 @@ use crate::common::StatementCache; -use crate::connection::Connection; +use crate::connection::{Connection, LogSettings}; use crate::error::Error; use crate::executor::Executor; use crate::mssql::connection::stream::MssqlStream; @@ -20,6 +20,7 @@ mod stream; pub struct MssqlConnection { pub(crate) stream: MssqlStream, pub(crate) cache_statement: StatementCache>, + log_settings: LogSettings, } impl Debug for MssqlConnection { diff --git a/sqlx-core/src/mssql/options/connect.rs b/sqlx-core/src/mssql/options/connect.rs index 926bd7a4..991a8b86 100644 --- a/sqlx-core/src/mssql/options/connect.rs +++ b/sqlx-core/src/mssql/options/connect.rs @@ -2,6 +2,8 @@ use crate::connection::ConnectOptions; use crate::error::Error; use crate::mssql::{MssqlConnectOptions, MssqlConnection}; use futures_core::future::BoxFuture; +use log::LevelFilter; +use std::time::Duration; impl ConnectOptions for MssqlConnectOptions { type Connection = MssqlConnection; @@ -12,4 +14,14 @@ impl ConnectOptions for MssqlConnectOptions { { Box::pin(MssqlConnection::establish(self)) } + + fn log_statements(&mut self, level: LevelFilter) -> &mut Self { + self.log_settings.log_statements(level); + self + } + + fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { + self.log_settings.log_slow_statements(level, duration); + self + } } diff --git a/sqlx-core/src/mssql/options/mod.rs b/sqlx-core/src/mssql/options/mod.rs index 8c927456..8a68fdbf 100644 --- a/sqlx-core/src/mssql/options/mod.rs +++ b/sqlx-core/src/mssql/options/mod.rs @@ -1,3 +1,5 @@ +use crate::connection::LogSettings; + mod connect; mod parse; @@ -8,6 +10,7 @@ pub struct MssqlConnectOptions { pub(crate) username: String, pub(crate) database: String, pub(crate) password: Option, + pub(crate) log_settings: LogSettings, } impl Default for MssqlConnectOptions { @@ -24,6 +27,7 @@ impl MssqlConnectOptions { database: String::from("master"), username: String::from("sa"), password: None, + log_settings: Default::default(), } } diff --git a/sqlx-core/src/mysql/connection/establish.rs b/sqlx-core/src/mysql/connection/establish.rs index 33690f89..f5549b60 100644 --- a/sqlx-core/src/mysql/connection/establish.rs +++ b/sqlx-core/src/mysql/connection/establish.rs @@ -129,6 +129,7 @@ impl MySqlConnection { stream, transaction_depth: 0, cache_statement: StatementCache::new(options.statement_cache_capacity), + log_settings: options.log_settings.clone(), }) } } diff --git a/sqlx-core/src/mysql/connection/executor.rs b/sqlx-core/src/mysql/connection/executor.rs index 8de32e7c..bedf1f8c 100644 --- a/sqlx-core/src/mysql/connection/executor.rs +++ b/sqlx-core/src/mysql/connection/executor.rs @@ -89,7 +89,7 @@ impl MySqlConnection { arguments: Option, persistent: bool, ) -> Result, Error>> + 'e, Error> { - let mut logger = QueryLogger::new(sql); + let mut logger = QueryLogger::new(sql, self.log_settings.clone()); self.stream.wait_until_ready().await?; self.stream.busy = Busy::Result; diff --git a/sqlx-core/src/mysql/connection/mod.rs b/sqlx-core/src/mysql/connection/mod.rs index 5f08dd54..7290bbfb 100644 --- a/sqlx-core/src/mysql/connection/mod.rs +++ b/sqlx-core/src/mysql/connection/mod.rs @@ -1,5 +1,5 @@ use crate::common::StatementCache; -use crate::connection::Connection; +use crate::connection::{Connection, LogSettings}; use crate::error::Error; use crate::mysql::protocol::statement::StmtClose; use crate::mysql::protocol::text::{Ping, Quit}; @@ -32,6 +32,8 @@ pub struct MySqlConnection { // cache by query string to the statement id and metadata cache_statement: StatementCache<(u32, MySqlStatementMetadata)>, + + log_settings: LogSettings, } impl Debug for MySqlConnection { diff --git a/sqlx-core/src/mysql/options/connect.rs b/sqlx-core/src/mysql/options/connect.rs index 2b6703ac..e722924c 100644 --- a/sqlx-core/src/mysql/options/connect.rs +++ b/sqlx-core/src/mysql/options/connect.rs @@ -3,6 +3,8 @@ use crate::error::Error; use crate::executor::Executor; use crate::mysql::{MySqlConnectOptions, MySqlConnection}; use futures_core::future::BoxFuture; +use log::LevelFilter; +use std::time::Duration; impl ConnectOptions for MySqlConnectOptions { type Connection = MySqlConnection; @@ -53,4 +55,14 @@ impl ConnectOptions for MySqlConnectOptions { Ok(conn) }) } + + fn log_statements(&mut self, level: LevelFilter) -> &mut Self { + self.log_settings.log_statements(level); + self + } + + fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { + self.log_settings.log_slow_statements(level, duration); + self + } } diff --git a/sqlx-core/src/mysql/options/mod.rs b/sqlx-core/src/mysql/options/mod.rs index ea7862fd..7a106e17 100644 --- a/sqlx-core/src/mysql/options/mod.rs +++ b/sqlx-core/src/mysql/options/mod.rs @@ -4,6 +4,7 @@ mod connect; mod parse; mod ssl_mode; +use crate::connection::LogSettings; pub use ssl_mode::MySqlSslMode; /// Options and flags which can be used to configure a MySQL connection. @@ -65,6 +66,7 @@ pub struct MySqlConnectOptions { pub(crate) statement_cache_capacity: usize, pub(crate) charset: String, pub(crate) collation: Option, + pub(crate) log_settings: LogSettings, } impl Default for MySqlConnectOptions { @@ -88,6 +90,7 @@ impl MySqlConnectOptions { ssl_mode: MySqlSslMode::Preferred, ssl_ca: None, statement_cache_capacity: 100, + log_settings: Default::default(), } } diff --git a/sqlx-core/src/postgres/connection/establish.rs b/sqlx-core/src/postgres/connection/establish.rs index e3de0c6d..59e3727c 100644 --- a/sqlx-core/src/postgres/connection/establish.rs +++ b/sqlx-core/src/postgres/connection/establish.rs @@ -142,6 +142,7 @@ impl PgConnection { cache_statement: StatementCache::new(options.statement_cache_capacity), cache_type_oid: HashMap::new(), cache_type_info: HashMap::new(), + log_settings: options.log_settings.clone(), }) } } diff --git a/sqlx-core/src/postgres/connection/executor.rs b/sqlx-core/src/postgres/connection/executor.rs index d3997bcf..fe0a5eb2 100644 --- a/sqlx-core/src/postgres/connection/executor.rs +++ b/sqlx-core/src/postgres/connection/executor.rs @@ -199,7 +199,7 @@ impl PgConnection { persistent: bool, metadata_opt: Option>, ) -> Result, Error>> + 'e, Error> { - let mut logger = QueryLogger::new(query); + let mut logger = QueryLogger::new(query, self.log_settings.clone()); // before we continue, wait until we are "ready" to accept more queries self.wait_until_ready().await?; diff --git a/sqlx-core/src/postgres/connection/mod.rs b/sqlx-core/src/postgres/connection/mod.rs index b8b75ea3..2688727a 100644 --- a/sqlx-core/src/postgres/connection/mod.rs +++ b/sqlx-core/src/postgres/connection/mod.rs @@ -6,7 +6,7 @@ use futures_core::future::BoxFuture; use futures_util::{FutureExt, TryFutureExt}; use crate::common::StatementCache; -use crate::connection::Connection; +use crate::connection::{Connection, LogSettings}; use crate::error::Error; use crate::executor::Executor; use crate::ext::ustr::UStr; @@ -60,6 +60,8 @@ pub struct PgConnection { // current transaction status transaction_status: TransactionStatus, pub(crate) transaction_depth: usize, + + log_settings: LogSettings, } impl PgConnection { diff --git a/sqlx-core/src/postgres/options/connect.rs b/sqlx-core/src/postgres/options/connect.rs index 6b6a8e3b..5c98598d 100644 --- a/sqlx-core/src/postgres/options/connect.rs +++ b/sqlx-core/src/postgres/options/connect.rs @@ -2,6 +2,8 @@ use crate::connection::ConnectOptions; use crate::error::Error; use crate::postgres::{PgConnectOptions, PgConnection}; use futures_core::future::BoxFuture; +use log::LevelFilter; +use std::time::Duration; impl ConnectOptions for PgConnectOptions { type Connection = PgConnection; @@ -12,4 +14,14 @@ impl ConnectOptions for PgConnectOptions { { Box::pin(PgConnection::establish(self)) } + + fn log_statements(&mut self, level: LevelFilter) -> &mut Self { + self.log_settings.log_statements(level); + self + } + + fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { + self.log_settings.log_slow_statements(level, duration); + self + } } diff --git a/sqlx-core/src/postgres/options/mod.rs b/sqlx-core/src/postgres/options/mod.rs index 6a3e21d0..b92230eb 100644 --- a/sqlx-core/src/postgres/options/mod.rs +++ b/sqlx-core/src/postgres/options/mod.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; mod connect; mod parse; mod ssl_mode; - +use crate::connection::LogSettings; pub use ssl_mode::PgSslMode; /// Options and flags which can be used to configure a PostgreSQL connection. @@ -85,6 +85,7 @@ pub struct PgConnectOptions { pub(crate) ssl_root_cert: Option, pub(crate) statement_cache_capacity: usize, pub(crate) application_name: Option, + pub(crate) log_settings: LogSettings, } impl Default for PgConnectOptions { @@ -136,6 +137,7 @@ impl PgConnectOptions { .unwrap_or_default(), statement_cache_capacity: 100, application_name: var("PGAPPNAME").ok(), + log_settings: Default::default(), } } diff --git a/sqlx-core/src/sqlite/connection/establish.rs b/sqlx-core/src/sqlite/connection/establish.rs index 3311019c..20206a43 100644 --- a/sqlx-core/src/sqlite/connection/establish.rs +++ b/sqlx-core/src/sqlite/connection/establish.rs @@ -114,5 +114,6 @@ pub(crate) async fn establish(options: &SqliteConnectOptions) -> Result Executor<'c> for &'c mut SqliteConnection { E: Execute<'q, Self::Database>, { let sql = query.sql(); - let mut logger = QueryLogger::new(sql); + let mut logger = QueryLogger::new(sql, self.log_settings.clone()); let arguments = query.take_arguments(); let persistent = query.persistent() && arguments.is_some(); diff --git a/sqlx-core/src/sqlite/connection/mod.rs b/sqlx-core/src/sqlite/connection/mod.rs index e4c04f46..92926bee 100644 --- a/sqlx-core/src/sqlite/connection/mod.rs +++ b/sqlx-core/src/sqlite/connection/mod.rs @@ -1,5 +1,5 @@ use crate::common::StatementCache; -use crate::connection::Connection; +use crate::connection::{Connection, LogSettings}; use crate::error::Error; use crate::sqlite::statement::{StatementWorker, VirtualStatement}; use crate::sqlite::{Sqlite, SqliteConnectOptions}; @@ -32,6 +32,8 @@ pub struct SqliteConnection { // most recent non-persistent statement pub(crate) statement: Option, + + log_settings: LogSettings, } impl SqliteConnection { diff --git a/sqlx-core/src/sqlite/options/connect.rs b/sqlx-core/src/sqlite/options/connect.rs index 3f0ab469..6fe26fb1 100644 --- a/sqlx-core/src/sqlite/options/connect.rs +++ b/sqlx-core/src/sqlite/options/connect.rs @@ -4,6 +4,8 @@ use crate::executor::Executor; use crate::sqlite::connection::establish::establish; use crate::sqlite::{SqliteConnectOptions, SqliteConnection}; use futures_core::future::BoxFuture; +use log::LevelFilter; +use std::time::Duration; impl ConnectOptions for SqliteConnectOptions { type Connection = SqliteConnection; @@ -27,4 +29,14 @@ impl ConnectOptions for SqliteConnectOptions { Ok(conn) }) } + + fn log_statements(&mut self, level: LevelFilter) -> &mut Self { + self.log_settings.log_statements(level); + self + } + + fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { + self.log_settings.log_slow_statements(level, duration); + self + } } diff --git a/sqlx-core/src/sqlite/options/mod.rs b/sqlx-core/src/sqlite/options/mod.rs index 824e0bcb..8686dd74 100644 --- a/sqlx-core/src/sqlite/options/mod.rs +++ b/sqlx-core/src/sqlite/options/mod.rs @@ -4,6 +4,7 @@ mod connect; mod journal_mode; mod parse; +use crate::connection::LogSettings; pub use journal_mode::SqliteJournalMode; use std::{borrow::Cow, time::Duration}; @@ -51,6 +52,7 @@ pub struct SqliteConnectOptions { pub(crate) shared_cache: bool, pub(crate) statement_cache_capacity: usize, pub(crate) busy_timeout: Duration, + pub(crate) log_settings: LogSettings, } impl Default for SqliteConnectOptions { @@ -71,6 +73,7 @@ impl SqliteConnectOptions { statement_cache_capacity: 100, journal_mode: SqliteJournalMode::Wal, busy_timeout: Duration::from_secs(5), + log_settings: Default::default(), } }