Skip to content

Commit b822584

Browse files
authored
Merge pull request #6 from 1Cor125/use_tracing_for_logs
Use "tracing" for logs and set SYNCHRONOUS to NORMAL for writes
2 parents 92f9c10 + 5da0d5f commit b822584

6 files changed

Lines changed: 111 additions & 13 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ Run Rust tests:
8181
cargo test
8282
```
8383

84-
### Linting and standards checks
84+
### Linting and Standards Checks
8585

8686
```bash
8787
npm run standards
@@ -109,6 +109,51 @@ fn main() {
109109
}
110110
```
111111

112+
### Tracing and Logging
113+
114+
This plugin and its connection manager crate use the
115+
[`tracing`](https://crates.io/crates/tracing) ecosystem for internal logging. They are
116+
configured with the `release_max_level_off` feature so that **all log statements are
117+
compiled out of release builds**. This guarantees that logging from this plugin will never
118+
reach production binaries unless you explicitly change that configuration.
119+
120+
To see logs during development, initialize a `tracing-subscriber` in your Tauri
121+
application crate and keep it behind a `debug_assertions` guard, for example:
122+
123+
```toml
124+
[dependencies]
125+
tracing = { version = "0.1.41", default-features = false, features = ["std", "release_max_level_off"] }
126+
tracing-subscriber = { version = "0.3.20", features = ["fmt", "env-filter"] }
127+
```
128+
129+
```rust
130+
#[cfg(debug_assertions)]
131+
fn init_tracing() {
132+
use tracing_subscriber::{fmt, EnvFilter};
133+
134+
let filter = EnvFilter::try_from_default_env()
135+
.unwrap_or_else(|_| EnvFilter::new("trace"));
136+
137+
fmt().with_env_filter(filter).compact().init();
138+
}
139+
140+
#[cfg(not(debug_assertions))]
141+
fn init_tracing() {}
142+
143+
fn main() {
144+
init_tracing();
145+
146+
tauri::Builder::default()
147+
.plugin(tauri_plugin_sqlite::init())
148+
.run(tauri::generate_context!())
149+
.expect("error while running tauri application");
150+
}
151+
```
152+
153+
With this setup, `tauri dev` shows all plugin and app logs, while `tauri build` produces
154+
a release binary that contains no logging from this plugin or your app-level `tracing`
155+
calls.
156+
112157
### JavaScript/TypeScript API
113158

114159
Install the JavaScript package in your frontend:

crates/sqlx-sqlite-conn-mgr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ rust-version = "1.89"
1010
sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
1111
thiserror = "2.0.17"
1212
tokio = { version = "1.48.0", features = ["full"] }
13+
tracing = { version = "0.1.41", default-features = false, features = ["std", "release_max_level_off"] }

crates/sqlx-sqlite-conn-mgr/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,32 @@ queries.
142142
The operation is idempotent and safe to call across multiple sessions,
143143
allowing concurrent reads during writes.
144144

145+
**Synchronous Mode: NORMAL vs FULL**
146+
147+
When WAL mode is enabled, this library sets `PRAGMA synchronous = NORMAL`
148+
instead of `FULL` for the following reasons:
149+
150+
* **Performance**: `NORMAL` provides significantly better write performance
151+
(up to 2-3x faster) by reducing the number of fsync operations. With `FULL`,
152+
SQLite syncs after every checkpoint; with `NORMAL`, it syncs only the WAL file.
153+
154+
* **Safety in WAL mode**: `NORMAL` is safe in WAL mode because:
155+
* WAL transactions are atomic and durable at the WAL file level
156+
* The database file itself can be checkpointed asynchronously
157+
* A crash may corrupt the database file, but the WAL file remains intact
158+
and will be used to recover on next open
159+
* This is different from rollback journal mode where `NORMAL` could cause
160+
corruption
161+
162+
* **Mobile/Desktop Context**: For typical desktop and mobile applications,
163+
`NORMAL` provides the best balance of performance and safety. `FULL` is
164+
primarily needed for scenarios with unreliable storage hardware or when
165+
power loss can occur mid-fsync operation.
166+
167+
See [SQLite WAL Performance Considerations][wal-perf] for more details.
168+
169+
[wal-perf]: https://www.sqlite.org/wal.html#performance_considerations
170+
145171
4. **Exclusive Writes**: The write pool has `max_connections=1`, ensuring
146172
only one writer can exist at a time. Other callers to
147173
`acquire_writer()` will block (asynchronously) until the current writer
@@ -154,6 +180,14 @@ queries.
154180
* Idle timeout: 30 seconds by default (configurable via `custom_config`)
155181
* Only customize `SqliteDatabaseConfig` when defaults don't meet your needs
156182

183+
## Tracing and Logging
184+
185+
This crate uses the [`tracing`](https://crates.io/crates/tracing) ecosystem for internal
186+
instrumentation. It is built with the `release_max_level_off` feature so that all
187+
`tracing` log statements are compiled out of release builds. To see its logs during
188+
development, the host application must install a `tracing-subscriber` and enable the
189+
desired log level; no extra configuration is required in this crate.
190+
157191
## Error Handling
158192

159193
```rust

crates/sqlx-sqlite-conn-mgr/src/database.rs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ use sqlx::{ConnectOptions, Pool, Sqlite};
1010
use std::path::{Path, PathBuf};
1111
use std::sync::Arc;
1212
use std::sync::atomic::{AtomicBool, Ordering};
13+
use tracing::error;
1314

1415
/// SQLite database with connection pooling for concurrent reads and optional exclusive writes.
1516
///
16-
/// The database is opened in read-write mode but can be used for read-only operations
17-
/// by calling `read_pool()`. Write operations are available by calling `acquire_writer()`
18-
/// which lazily initializes WAL mode on first use.
17+
/// Once the database is opened it can be used for read-only operations by calling `read_pool()`.
18+
/// Write operations are available by calling `acquire_writer()` which lazily initializes WAL mode
19+
/// on first use.
1920
///
2021
/// # Example
2122
///
@@ -66,7 +67,7 @@ impl SqliteDatabase {
6667
/// If the database is already connected, returns the existing connection.
6768
/// Multiple calls with the same path will return the same database instance.
6869
///
69-
/// The database is created if it doesn't exist. WAL mode is optionally enabled when
70+
/// The database is created if it doesn't exist. WAL mode is enabled when
7071
/// `acquire_writer()` is first called.
7172
///
7273
/// # Arguments
@@ -131,7 +132,8 @@ impl SqliteDatabase {
131132
// Why do we need to manually create the database file? We could just let the connection
132133
// create it if it doesn't exist, using `create_if_missing(true)`, right? Not if we called
133134
// connect and then our very first query was a read-only query, like `PRAGMA user_version;`,
134-
// for example. That would fail because the read pool cannot create the file
135+
// for example. That would fail because the read pool connections are read-only and cannot
136+
// create the file
135137
if !db_exists && !is_memory_database(&path) {
136138
let create_options = SqliteConnectOptions::new()
137139
.filename(&path)
@@ -174,7 +176,7 @@ impl SqliteDatabase {
174176
.await
175177
}
176178

177-
/// Get a reference to the connection pool for executing SELECT queries
179+
/// Get a reference to the connection pool for executing read queries
178180
///
179181
/// Use this for concurrent read operations. Multiple readers can access
180182
/// the pool simultaneously.
@@ -240,6 +242,11 @@ impl SqliteDatabase {
240242
.execute(&mut *conn)
241243
.await?;
242244

245+
// https://www.sqlite.org/wal.html#performance_considerations
246+
sqlx::query("PRAGMA synchronous = NORMAL")
247+
.execute(&mut *conn)
248+
.await?;
249+
243250
self.wal_initialized.store(true, Ordering::SeqCst);
244251
}
245252

@@ -273,11 +280,7 @@ impl SqliteDatabase {
273280

274281
// Remove from registry
275282
if let Err(e) = uncache_database(&self.path).await {
276-
// TODO: Investigate use of "tracing" crate to log this error
277-
#[cfg(debug_assertions)]
278-
eprintln!("Failed to remove database from cache: {}", e);
279-
#[cfg(not(debug_assertions))]
280-
let _ = e; // Suppress unused variable warning
283+
error!("Failed to remove database from cache: {}", e);
281284
}
282285

283286
// This will await all readers to be returned
@@ -370,6 +373,7 @@ mod tests {
370373
.fetch_one(db.read_pool().unwrap())
371374
.await
372375
.unwrap();
376+
373377
assert_eq!(count, 12);
374378
}));
375379
}
@@ -564,6 +568,17 @@ mod tests {
564568
"Journal mode should be WAL after first acquire_writer"
565569
);
566570

571+
// Check sync setting
572+
let (sync,): (i32,) = sqlx::query_as("PRAGMA synchronous")
573+
.fetch_one(&mut *writer)
574+
.await
575+
.unwrap();
576+
577+
assert_eq!(
578+
sync, 1,
579+
"Sync mode should be NORMAL after first acquire_writer"
580+
);
581+
567582
drop(writer);
568583

569584
db.remove().await.unwrap();
@@ -676,6 +691,7 @@ mod tests {
676691
.fetch_all(db_clone.read_pool().unwrap())
677692
.await
678693
.unwrap();
694+
679695
assert!(rows.len() > 0);
680696
}));
681697
}
@@ -703,6 +719,7 @@ mod tests {
703719
.fetch_one(db.read_pool().unwrap())
704720
.await
705721
.unwrap();
722+
706723
assert_eq!(count.0, 2);
707724

708725
db.remove().await.unwrap();

crates/sqlx-sqlite-conn-mgr/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
//! - Uses sqlx's `SqlitePoolOptions` for all pool configuration
5757
//! - Uses sqlx's `SqliteConnectOptions` for connection flags and configuration
5858
//! - Minimal custom logic - delegates to sqlx wherever possible
59-
//! - Global registry caches new database instances (with their pools) and returns existing ones
59+
//! - Global registry caches new database instances and returns existing ones
6060
//! - WAL mode is enabled lazily only when writes are needed
6161
//!
6262
mod config;

0 commit comments

Comments
 (0)