| Crates.io | wattle-appender |
| lib.rs | wattle-appender |
| version | 0.1.0 |
| created_at | 2026-01-12 07:09:16.366078+00 |
| updated_at | 2026-01-12 07:09:16.366078+00 |
| description | A feature-rich file appender for the tracing, providing flexible log rotation, compression, and both blocking and non-blocking write modes. |
| homepage | https://github.com/noobHuKai/wattle-appender |
| repository | https://github.com/noobHuKai/wattle-appender |
| max_upload_size | |
| id | 2037153 |
| size | 91,448 |
A feature-rich file appender for the tracing framework, providing flexible log rotation, compression, and both blocking and non-blocking write modes.
Add this to your Cargo.toml:
[dependencies]
wattle-appender = "0.1"
flate2)zstd)xz2)zip)non-blocking and compression-zstdTo use all features:
[dependencies]
wattle-appender = { version = "0.1", features = ["full"] }
use wattle_appender::FileAppender;
use std::io::Write;
fn main() -> std::io::Result<()> {
let mut appender = FileAppender::new()
.file_name("logs/app.log")
.build()?;
writeln!(appender, "Hello, world!")?;
Ok(())
}
use wattle_appender::FileAppender;
let appender = FileAppender::new()
.file_name("logs/app.log")
.daily_rotation()
.max_backup(7) // Keep last 7 days
.build()
.unwrap();
use wattle_appender::FileAppender;
let appender = FileAppender::new()
.file_name("logs/app.log")
.size_limit(10 * 1024 * 1024) // 10 MB
.max_backup(5) // Keep 5 backup files
.build()
.unwrap();
use wattle_appender::FileAppender;
let appender = FileAppender::new()
.file_name("logs/app.log")
.daily_rotation()
.zstd_compression() // Requires "compression-zstd" feature
.max_backup(30)
.build()
.unwrap();
use wattle_appender::FileAppender;
// Requires "non-blocking" feature
let appender = FileAppender::new()
.file_name("logs/app.log")
.blocking(false)
.daily_rotation()
.build()
.unwrap();
tracinguse tracing_subscriber::fmt;
use wattle_appender::FileAppender;
fn main() {
let appender = FileAppender::new()
.file_name("logs/app.log")
.daily_rotation()
.max_backup(7)
.build()
.unwrap();
tracing_subscriber::fmt()
.with_writer(appender)
.init();
tracing::info!("Application started");
}
The appender uses intelligent file naming based on your rotation configuration:
When rotation is not configured, logs are written directly to the specified file:
FileAppender::new()
.file_name("logs/app.log")
.build()
Result: logs/app.log
When only time-based rotation is enabled, files are named with timestamps:
FileAppender::new()
.file_name("logs/app.log")
.daily_rotation()
.build()
Files created:
logs/app.log → Symlink to the current log filelogs/app.log.2024-01-15_10-30-00.log → Actual log file with timestampPattern: {file_name}.{YYYY-MM-DD_HH-MM-SS}.log
When only size-based rotation is enabled, files are numbered sequentially:
FileAppender::new()
.file_name("logs/app.log")
.size_limit(10 * 1024 * 1024)
.build()
Files created:
logs/app.log → Symlink to the current log filelogs/app.log.1.log → First log filelogs/app.log.2.log → Second log file (after size limit reached)logs/app.log.3.log → Third log filePattern: {file_name}.{N}.log
When both time and size rotation are enabled, files include both timestamp and sequence number:
FileAppender::new()
.file_name("logs/app.log")
.daily_rotation()
.size_limit(10 * 1024 * 1024)
.build()
Files created:
logs/app.log → Symlink to the current log filelogs/app.log.2024-01-15_10-30-00.1.log → First file of the daylogs/app.log.2024-01-15_10-30-00.2.log → Second file (after size limit)logs/app.log.2024-01-16_08-15-30.1.log → Next day's first filePattern: {file_name}.{YYYY-MM-DD_HH-MM-SS}.{N}.log
Note: Sequence numbers reset to 1 when a new time period begins.
When compression is enabled, rotated files are automatically compressed:
FileAppender::new()
.file_name("logs/app.log")
.daily_rotation()
.zstd_compression()
.build()
Files created:
logs/app.log → Symlink to current log filelogs/app.log.2024-01-15_10-30-00.log.zst → Compressed rotated fileCompression extensions:
.gz.zst.xz.zipFileAppender::new()Creates a new builder for configuring the appender.
file_name(path: impl AsRef<Path>) - Set the base file path (required)
blocking(bool) - Set blocking/non-blocking mode (default: true)
non-blocking feature for false valueminutely_rotation() - Rotate every minutehourly_rotation() - Rotate every hourdaily_rotation() - Rotate daily at midnightweekly_rotation() - Rotate weeklymonthly_rotation() - Rotate monthlyyearly_rotation() - Rotate yearlynever_rotation() - Disable time-based rotation (default)size_limit(bytes: u64) - Set maximum file size in bytes before rotationmax_backup(count: usize) - Maximum number of backup files to keepmax_age_days(days: usize) - Maximum age of backup files in dayscompress(bool) - Enable/disable compression (default: false)gzip_compression() - Use Gzip compression (requires compression-gzip feature)zstd_compression() - Use Zstd compression (requires compression-zstd feature)xz_compression() - Use XZ compression (requires compression-xz feature)zip_compression() - Use Zip compression (requires compression-zip feature)Note: Calling any *_compression() method automatically enables compression.
symlink_latest(bool) - Enable/disable symlink to latest log (default: true)build() - Construct the FileAppender (returns io::Result<FileAppender>)use wattle_appender::FileAppender;
let appender = FileAppender::new()
.file_name("logs/myapp.log")
.blocking(false) // Non-blocking mode
.daily_rotation() // Rotate daily
.size_limit(50 * 1024 * 1024) // Also rotate at 50 MB
.max_backup(30) // Keep 30 backup files
.max_age_days(90) // Delete files older than 90 days
.zstd_compression() // Compress rotated files
.symlink_latest(true) // Create symlink to current file
.build()
.expect("Failed to create appender");
use std::sync::Arc;
use std::thread;
use wattle_appender::FileAppender;
use std::io::Write;
let appender = Arc::new(
FileAppender::new()
.file_name("logs/app.log")
.daily_rotation()
.build()
.unwrap()
);
let mut handles = vec![];
for i in 0..10 {
let appender_clone = Arc::clone(&appender);
let handle = thread::spawn(move || {
for j in 0..100 {
let mut app = appender_clone.clone();
writeln!(app, "Thread {} - Message {}", i, j).unwrap();
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
use wattle_appender::FileAppender;
let appender = FileAppender::new()
.file_name("logs/hourly.log")
.hourly_rotation()
.gzip_compression() // Requires "compression-gzip" feature
.max_backup(24) // Keep 24 hours
.build()
.unwrap();
use wattle_appender::FileAppender;
// Rotate only when file reaches 5 MB
let appender = FileAppender::new()
.file_name("logs/size-based.log")
.size_limit(5 * 1024 * 1024)
.max_backup(10)
.build()
.unwrap();
The appender checks for rotation on every write operation:
When rotation is triggered:
max_backup and max_age_daysThe cleanup process:
max_age_days (if set)max_backup files (if set)FileAppender instanceBlocking Mode:
Non-Blocking Mode:
Compression reduces disk space but adds CPU overhead:
| Algorithm | Speed | Compression Ratio | Best For |
|---|---|---|---|
| Gzip | Fast | Good | General use |
| Zstd | Very Fast | Excellent | Best balance |
| XZ | Slow | Best | Maximum compression |
| Zip | Fast | Good | Windows compatibility |
Recommendation: Use Zstd for the best balance of speed and compression.
tracing-subscriber's filtering insteadCheck the examples/ directory for more complete examples:
basic_usage.rs - Simple file appender setuprotation_demo.rs - Demonstrates different rotation strategiesadvanced_usage.rs - Complete configuration with all featurestracing_integration.rs - Integration with tracing frameworkRun an example:
cargo run --example basic_usage
cargo run --example rotation_demo --features full
Run tests with all features:
cargo test --all-features
Run tests for specific features:
cargo test --features non-blocking
cargo test --features compression-zstd
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.