<p align="center">
<img height="300" src="https://codeberg.org/mo8it/logs-wheel/raw/branch/main/wheel.svg" />
</p>
<p align="center">Rolling log files with compression</p>
This library offers the struct [`LogFileInitializer`](https://docs.rs/logs-wheel/latest/logs_wheel/struct.LogFileInitializer.html) to get a log file inside a logs directory.
It uses a rolling strategy that works like the wheel image above 🎡
The initializer has the fields `directory`, `filename`, `max_n_old_files` and `preferred_max_file_size_mib`.
Rolling will be applied to the file `directory/file` if all the following conditions are true:
- The file already exists.
- The file has a size >= `preferred_max_file_size_mib` (in MiB).
- No rolling was already done today.
In the case of rolling, the file will be compressed with GZIP to `directory/filename-YYYYMMDD.gz` with today's date (in UTC).
If rolling was applied and the number of old files exceeds `max_n_old_files`, the oldest file will be deleted.
It is important to know that **rolling happens only on initialization**.
The [`init` method](https://docs.rs/logs-wheel/latest/logs_wheel/struct.LogFileInitializer.html#method.init) returns a normal `File`.
It will not apply rolling if your program runs for multiple days without being restarted.
This has the following advantages:
- No overhead on writing to check when to roll.
- No latency spikes during the actual rolling.
- The whole logs of one run (from start to termination) are inside one file.
Your program should restart anyway every couple of days when restart the host after a system update.
But if you really want rolling every fixed amount of days while your program is running,
you can use your own type that calls the [`init` method](https://docs.rs/logs-wheel/latest/logs_wheel/struct.LogFileInitializer.html#method.init) under the hood when rolling should happen and then swap the file.
For more details, read the documentation of the [`LogFileInitializer` struct](https://docs.rs/logs-wheel/latest/logs_wheel/struct.LogFileInitializer.html)
and its [`init` method](https://docs.rs/logs-wheel/latest/logs_wheel/struct.LogFileInitializer.html#method.init).
## Example
We will see what happens when calling the [`init` method](https://docs.rs/logs-wheel/latest/logs_wheel/struct.LogFileInitializer.html#method.init)
with the following field values for the initializer:
```rust
use logs_wheel::LogFileInitializer;
let log_file = LogFileInitializer {
directory: "logs",
filename: "test",
max_n_old_files: 2,
preferred_max_file_size_mib: 1,
}.init()?;
Ok::<(), std::io::Error>(())
```
This method call will always return the file `logs/test` at the end.
But we will discuss its side effects.
### First call
The first call will create the directory `logs/` in the current directory (because we specified a relative path) with the file `test` inside it.
Content of `logs/`:
- `test`
### Later call
If we call the same function again on the date 2023-11-12 (in UTC) and the size of the file `logs/test` is bigger than 1 MiB,
the file will be compressed to `logs/test-20231112.gz`.
The file `logs/test` will be returned after truncation (empty file).
Content of `logs/`:
- `test`
- `test-20231112.gz`
### Call on the same day
If we call the same function again on the same day, nothing will change,
even when the size of `logs/test` is bigger than 1 MiB.
The file `logs/test` will be open in append mode.
Content of `logs/`:
- `test`
- `test-20231112.gz`
### Call on a later day
If we call the same function again on the next day and the size of the file `logs/test` is bigger than 1 MiB,
the file will be compressed to `logs/test-20231113.gz`.
The file `logs/test` will be returned after truncation.
Content of `logs/`:
- `test`
- `test-20231113.gz`
- `test-20231112.gz`
### Call on a later day with an exceeded number of old files
Now, we already have 2 old files which means that we reached the limit `max_n_old_files`.
If we call the same function again on the next day and the size of the file `logs/test` is bigger than 1 MiB,
the file will be compressed to `logs/test-20231114.gz`.
The oldest file `test-20231112.gz` will be deleted.
The file `logs/test` will be returned after truncation.
Content of `logs/`:
- `test`
- `test-20231114.gz`
- `test-20231113.gz`
## Tracing Subscriber
You can use this library with the [tracing](https://docs.rs/tracing/latest/tracing/) ecosystem!
Here is an example of how to use the returned file as a [tracing subscriber](https://docs.rs/tracing-core/0.1.32/tracing_core/subscriber/trait.Subscriber.html):
```rust
use logs_wheel::LogFileInitializer;
use std::sync::Mutex;
let log_file = LogFileInitializer {
directory: "logs",
filename: "test",
max_n_old_files: 2,
preferred_max_file_size_mib: 1,
}.init()?;
let writer = Mutex::new(log_file);
let subscriber = tracing_subscriber::fmt()
.with_writer(writer)
// … (other `SubscriberBuilder` methods)
.finish();
# Ok::<(), std::io::Error>(())
```
## Similar crates
- [tracing-appender](https://docs.rs/tracing-appender/latest/tracing_appender/index.html): Offers [RollingFileAppender](https://docs.rs/tracing-appender/0.2.2/tracing_appender/rolling/struct.RollingFileAppender.html) which rolls every fixed amount of time while the program is running. But it doesn't compress old log files and doesn't delete any. `logs-wheel` can be used as an alternative. It can also be used in combination with [NonBlocking](https://docs.rs/tracing-appender/0.2.2/tracing_appender/non_blocking/struct.NonBlocking.html) for non blocking writes.
- [rolling-file](https://docs.rs/rolling-file/latest/rolling_file/index.html): Provides rolling every fixed amount of time while the program is running. No compression. Has to rename every file during the rolling because they are numbered.