logs-wheel 0.3.1

Rolling log files with compression
Documentation
<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.