# swing
Log like it's 1978 with this logging implementation for the [log](https://crates.io/crates/log) crate. Color themes, pluggable formatting, we've got it all!
![multi-line-gradient](https://i.imgur.com/2cGHo5L.gif)
# Installation
Add the following to `Cargo.toml`:
```toml
[dependencies]
swing = "0.1"
log = "0.4"
```
# Quick start
Create and initialize a `Logger`, then use the [log](https://crates.io/crates/log) crate macros to log messages:
```rust
use swing::Logger;
fn main() {
// setup logger
Logger::new().init().unwrap();
// log away!
log::trace!("foo");
log::debug!("bar");
log::info!("baz");
log::warn!("spam");
log::error!("eggs");
}
```
Note that the default `Logger` created with `::new()` has a log level filter of `info`, so in this example, only `"baz"` `"spam"` and `"eggs"` will be printed to the console.
# Logger config options
For more control, `Logger`s can be created with a `Config` struct via `Logger::with_config`:
```rust
use swing::{Config, Logger};
use log::LevelFilter;
fn main() {
let config = Config {
level: LevelFilter::Trace,
..Default::default()
};
Logger::with_config(config).init().unwrap();
}
```
The default configuration uses the following settings:
```rust
use swing::{Config, ColorFormat, RecordFormat, theme};
use log::LevelFilter;
Config {
level: LevelFilter::Info,
record_format: RecordFormat::Simple,
color_format: Some(ColorFormat::Solid),
theme: Box::new(theme::Spectral {}),
use_stderr: true,
};
```
Each setting is explained in its own subsection:
- [level](#level)
- [record_format](#record_format)
- [color_format](#color_format)
- [theme](#theme)
- [use_stderr](#use_stderr)
## level
The `level` setting controls which log records are processed and which are ignored. The `LevelFilter` enum used in `Config` is taken directly
from [the log crate](https://docs.rs/log/latest/log/enum.LevelFilter.html). It defines the following variants:
- `Off`
- `Trace`
- `Debug`
- `Info`
- `Warn`
- `Error`
Only records logged at or above the chosen severity will be output to `stdout`/`stderr`. For example:
```rust
use swing::Config;
use log::LevelFilter;
// --snip--
let config = Config {
level: LevelFilter::Info,
..Default::default()
};
// --snip--
// trace and debug logs will be ignored, only info, warn, and error messages will print to the screen
log::trace!("foo");
log::debug!("bar");
log::info!("baz");
log::warn!("spam");
log::error!("eggs");
```
## record_format
The `record_format` setting controls how log records are (structurally) formatted when they are displayed. Each call to the [log](https://docs.rs/log/latest/log/)
crate macros (`trace!`, `info!`, etc...) generates a log [record](https://docs.rs/log/latest/log/struct.Record.html). These records are then formatted by this crate using one
of the variants in the `RecordFormat` enum:
- `Json`
- `Simple`
- `Custom`
Record formats are imported and used by:
```rust
use swing::{Config, Logger, RecordFormat};
use log::LevelFilter;
fn main() {
let config = Config {
level: LevelFilter::Trace,
record_format: RecordFormat::Simple,
..Default::default()
};
Logger::with_config(config).init().unwrap();
}
```
### Simple format
This is the default record format and will generate log lines that look like this:
```text
2022-07-31T20:25:31.108560826Z [main] TRACE - foo
2022-07-31T20:25:31.108623041Z [main] DEBUG - bar
2022-07-31T20:25:31.108645580Z [main] INFO - baz
2022-07-31T20:25:31.108667634Z [main] WARN - spam
2022-07-31T20:25:31.108736790Z [main] ERROR - eggs
```
Note that times are always in ISO 8601 format, UTC time.
### Json format
This record format will generate log lines as JSON:
```json
{"time":"2022-07-31T20:28:11.863634602Z","level":"TRACE","target":"main","message":"foo"}
{"time":"2022-07-31T20:28:11.864114090Z","level":"DEBUG","target":"main","message":"bar"}
{"time":"2022-07-31T20:28:11.864201937Z","level":"INFO","target":"main","message":"baz"}
{"time":"2022-07-31T20:28:11.864269093Z","level":"WARN","target":"main","message":"spam"}
{"time":"2022-07-31T20:28:11.864372619Z","level":"ERROR","target":"main","message":"eggs"}
```
Note that times are always in ISO 8601 format, UTC time.
### Custom format
If you don't like any of the above formats, you can inject your own custom record formatting by using the `Custom` format:
```rust
use swing::{Config, Logger, RecordFormat};
use log::{LevelFilter, Record};
fn main() {
let fmt_rec = Box::new(|r: &Record| -> String {
format!("{} - {}", r.level(), r.args())
});
let config = Config {
level: LevelFilter::Trace,
record_format: RecordFormat::Custom(fmt_rec),
..Default::default()
};
Logger::with_config(config).init().unwrap();
// log away!
log::trace!("foo");
log::debug!("bar");
log::info!("baz");
log::warn!("spam");
log::error!("eggs");
}
```
The above example format will generate log lines that look like this:
```text
TRACE - foo
DEBUG - bar
INFO - baz
WARN - spam
ERROR - eggs
```
See [the log crate "Record" struct](https://docs.rs/log/latest/log/struct.Record.html) for available record fields/methods to use within the custom format closure.
For reference, the `Simple` record format can be reproduced with the following `Custom` record format:
```rust
use time::format_description::well_known::Iso8601;
use time::OffsetDateTime;
use log::Record;
// --snip--
let fmt_rec = Box::new(|r: &Record| -> String {
let now = OffsetDateTime::now_utc()
.format(&Iso8601::DEFAULT)
.expect("Failed to format time as ISO 8601");
format!("{} [{}] {} - {}", now, r.target(), r.level(), r.args())
});
```
## color_format
The `color_format` setting controls how log records are colored (specifically how a theme is applied) when they are displayed. Log records are formatted by this crate using one of the variants in the `ColorFormat` enum, or `None`:
- `Solid`
- `InlineGradient()`
- `MultiLineGradient()`
Color formats are imported and used by:
```rust
use swing::{Config, Logger, ColorFormat};
use log::LevelFilter;
fn main() {
let config = Config {
level: LevelFilter::Trace,
color_format: Some(ColorFormat::Solid),
..Default::default()
};
Logger::with_config(config).init().unwrap();
}
```
### None format
If `None` is provided as the `color_format`, log records will not be colored (warnings and errors will still be made bold).
### Solid format
This will generate log lines that each have a solid color, determined by log level. The below screenshot uses the `Spectral` theme and the following color format:
```rust
use swing::ColorFormat;
// --snip--
let color_format = Some(ColorFormat::Solid);
```
![solid color format](https://i.imgur.com/AiSOt2W.png)
### Inline gradient format
This will generate log lines that are colored with a repeating linear gradient from left to right, determined by log level. The below screenshot uses the `Spectral` theme and the following color format:
```rust
use swing::ColorFormat;
// --snip--
let color_format = Some(ColorFormat::InlineGradient(60));
```
![inline gradient color format](https://i.imgur.com/RkGZWEh.png)
This color format takes a `usize` argument which represents the number of steps required to go from the start color to the end color for each level's color gradient. Gradients will be traversed in alternating ascending and descending order. In the above example, it will take `60` characters to go from the starting color for each line to the ending color, then `60` more characters to return to the starting color again.
Note that this color format will incur a nontrivial performance hit with heavy logging. If you have a lot of logs and are trying to break the next land speed record for fastest program, you probably shouldn't use this color format.
### Multi-line gradient format
This will generate log lines that each have a solid color, determined by level. Lines within each level will change color step by step, moving through a linear gradient of colors determined by the relevant log level. The below screenshot uses the `Spectral` theme and the following color format:
```rust
use swing::ColorFormat;
// --snip--
let color_format = Some(ColorFormat::MultiLineGradient(30));
```
![multi-line gradient](https://i.imgur.com/x4Z0tN3.png)
This color format takes a `usize` argument which represents the number of steps required to go from the start color to the end color for each level's color gradient. Gradients will be traversed in alternating ascending and descending order. In the above example, it will take `30` lines to go from the starting color for each level to the ending color, then `30` more lines to return to the starting color again.
## theme
The `theme` setting determines the color palette to use when applying color formats. It is set by providing an instance of something that implements the `Theme` trait:
```rust
use swing::theme::Spectral;
// --snip--
let theme = Box::new(Spectral {});
```
This crate provides a few premade themes, but you can also implement your own, or use themes made by others.
### Spectral
The `Spectral` theme provides a palette that moves through the color spectrum in 5 segments:
![spectral theme](https://i.imgur.com/DIdDbI7.png)
### Simple
The `Simple` theme provides a relatively flat palette that uses dark/light versions of 5 main colors:
![simple theme](https://i.imgur.com/Rrku1BP.png)
### Creating your own custom theme
Anything that implements the `Theme` trait can be used as a theme. To make your own theme, you just have to implement this trait for a struct, then set `Config`'s `theme` member to a boxed instance of that struct. See `examples/custom-theme.rs` for an example of a custom theme implementation.
## use_stderr
The `use_stderr` setting determines if log records are split between `stdout` and `stderr` or not. When this field is false, all log records will be written to `stdout`. When this field is true, records at levels `trace`, `debug`, and `info` are written to `stdout`, while those at `warn` and `error` levels are written to `stderr`.
# Examples
See the `examples` directory for a variety of usage examples. You can run any of these examples with:
```shell
$ cargo run --example
```
# Stream redirection tips in Linux
When logging with `use_stderr` set to true, you must redirect both streams to capture all output.
To redirect all log records to file:
```shell
$ ./example &> foo.log
```
To redirect all logs to file, while watching output:
```shell
# write all log data to foo.log and stdout simultaneously
$ ./example 2>&1 | tee foo.log
```
You can add `jq` for pretty printing when using `RecordFormat::Json`:
```shell
$ ./example 2>&1 | tee foo.log | jq
```
# Contributing
Contributions are welcome and greatly appreciated. See [CONTRIBUTING](CONTRIBUTING.md) for some general guidelines.
# License
Licensed under either of [Apache License, Version 2.0](https://github.com/diffuse/swing/blob/main/LICENSE-APACHE) or [MIT license](https://github.com/diffuse/swing/blob/main/LICENSE-MIT) at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in swing by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.