Logging is an essential part of nearly every application. It helps you debug and track errors effectively. In Rust, you’ll likely use the log
crate, which provides a logging facade that your application and its dependencies can hook into. However, the log
crate doesn’t provide actual logging implementations, meaning it won’t write logs to the console or a file by itself.
That’s where Ftail comes in — a crate I created to provide simple, configurable logging to both the console and files. Setting it up is straightforward, and you can have both console and file logging working with just a few lines of code.
Here’s an example of setting up logging to both the console (with a minimum log level of Debug
) and to a daily file (with a minimum log level of Error
):
use ftail::Ftail;
use log::LevelFilter;
Ftail::new()
.console(LevelFilter::Debug)
.daily_file("logs", LevelFilter::Error)
.init()?;
This initializes an instance of Ftail
, adds a driver for logging to the console, and another driver for daily log files. The init()
method ties it all together, ensuring that logs are captured and handled according to the filters you’ve defined.
Default Implementations
The ftail
crate offers several default logging drivers, so you can quickly tailor logging to your needs:
- Console
- Formatted Console
- Single File
- Daily File
Console Logging
The basic console logger prints log messages using a standard format. It’s simple, effective, and great for development or debugging in the terminal.
Formatted Console Logging
The formatted_console logger adds some polish to your logs, including color formatting and clearer timestamps. This makes logs easier to read, especially during development. Some terminals even support clickable links to the exact source file and line number where the log message was generated.
Example output:
2024-09-13 17:35:37 · TRACE
This is a trace message
examples/formatted_console/src/main.rs:9
Single File Logging
The single_file implementation logs messages to a single .log
file. You can specify the file’s name and location when setting up this driver.
Daily File Logging
The daily_file implementation creates a new log file every day. The logs are stored in a directory you specify, with each file named using the format YYYY-MM-DD.log
. This is perfect for long-running applications, as it keeps log files organized and easy to manage.
Log Messages in Rust
In Rust, you log messages using the log
crate’s logging macros. There are five log levels: Error
, Warn
, Info
, Debug
, and Trace
.
Here’s a quick example:
log::trace!("This is a trace message");
log::debug!("This is a debug message");
log::info!(target: "foo", "bar");
log::warn!("This is a warning message");
log::error!("This is an error message");
These macros are simple to use and, depending on the driver configuration, they will write output to both the console and any files you’ve set up.
The resulting logs might look like this:
13-09-2024 17:35:18 TRACE console This is a trace message
13-09-2024 17:35:18 DEBUG console This is a debug message
13-09-2024 17:35:18 INFO foo bar
13-09-2024 17:35:18 WARN console This is a warning message
13-09-2024 17:35:18 ERROR console This is an error message
Notice the target: "foo"
in the info!
macro. This allows you to filter specific targets, which is helpful if you want to separate logs from different parts of your application or from external crates.
Customizing Ftail
Ftail offers various options to customize your logging setup beyond the defaults. Here’s an example of how to customize your timezone, date-time format, and log filters:
Ftail::new()
.timezone(ftail::Tz::Europe__Amsterdam)
.datetime_format("%d-%m-%Y %H:%M:%S%.3f")
.console(LevelFilter::Off)
.filter_levels(vec![
Level::Trace,
Level::Debug,
Level::Info,
Level::Error,
])
.filter_targets(vec!["foo"])
.init()?;
Date-Time Format
By default, Ftail uses the format "%Y-%m-%d %H:%M:%S"
for timestamps in log messages. However, you can customize this format to suit your needs. In the example above, we changed the format to "%d-%m-%Y %H:%M:%S%.3f"
, which adds milliseconds to the timestamps (%.3f
represents three decimal places).
Timezone
Ftail allows you to set a specific timezone for your logs, which is helpful if your app runs in multiple regions or if you need consistent timestamps across different servers. In the example above, we set the timezone to Europe/Amsterdam
. The default is the system’s local timezone.
To use timezones, ensure you enable the timezone
feature in your Cargo.toml
:
[dependencies]
ftail = { version = "0.1", features = ["timezone"] }
Maximum File Size
Log files can grow quickly, especially in production environments. To prevent a single log file from becoming too large, Ftail lets you set a maximum file size (in MB). Once the limit is reached, it rolls the log into a new file, appending .old1
to the previous log file’s name, and so on.
.max_file_size(100)
Filtering Log Levels
You can filter which log levels are written by specifying them explicitly. In the example above, we filter to only include Debug
, Info
, and Error
messages, skipping Trace
and Warn
.
.filter_levels(vec![Level::Debug, Level::Info, Level::Error])
Filtering Targets
Sometimes, you only want to log specific parts of your application. By filtering targets, you can control which parts of the codebase or which dependencies get logged. Here, we filter to log only messages from the foo
target.
.filter_targets(vec!["foo", "bar"])
Adding Custom Drivers
If the default implementations don’t meet your needs, Ftail allows you to define your own custom logging drivers. Here’s an example where we create a custom logger, implement the Log
trait for it, and hook it into Ftail:
Ftail::new()
.custom(
|config: Config| Box::new(CustomLogger { config }) as Box<dyn Log + Send + Sync>,
LevelFilter::Debug,
)
.datetime_format("%H:%M:%S%.3f")
.init()?;
And here’s the CustomLogger
struct and its implementation:
struct CustomLogger {
config: Config,
}
impl log::Log for CustomLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.config.level_filter
}
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
let time = chrono::Local::now()
.format(&self.config.datetime_format)
.to_string();
println!("{} [{}] {}", time.black(), record.level().bold(), record.args());
}
fn flush(&self) {}
}
This gives you full flexibility to build a custom logging solution with Ftail, including advanced formatting, filtering, and output options.
Explore More
For more examples and detailed usage, check out the GitHub repository. You’ll find working code samples in the /examples
directory to help you get started.