Skip to content

Commit

Permalink
Introduce AdaptiveFormat to improve color handling.
Browse files Browse the repository at this point in the history
  - Support using atty without provided coloring

  - Extend example colors to provide insight in how AdaptiveFormat works

  - Remove deprecated method `Logger::do_not-log()`
    (use `log_target()` with `LogTarget::DevNull` instead).

  - Remove deprecated method `Logger::o_log_to_file()`
    (use  `log_target()` instead).
	The clearer convenience method `Logger::log_to_file()` is still available.
  • Loading branch information
emabee committed Sep 19, 2020
1 parent 2dd2fee commit ed23959
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 108 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "flexi_logger"
version = "0.15.13"
version = "0.16.0-alpha"
authors = ["emabee <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,22 @@ Make use of any of these features by specifying them in your `Cargo.toml`

### **`colors`**

Getting colored output was also possible without this feature, by adding
colors to the logged message,
and/or implementing and using your own coloring format function.
Getting colored output is also possible without this feature,
by implementing and using your own coloring format function.

The new default feature `colors` simplifies this by doing three things:
The default feature `colors` simplifies this by doing three things:

* it activates the optional dependency to `yansi` and
* provides additional colored pendants to the existing uncolored format functions
* it uses `colored_default_format()` for the output to stderr,
and the non-colored `default_format()` for the output to files
* it activates the optional dependency to `atty` to being able to switch off
coloring if the output is not sent to a terminal but e.g. piped to another program.

**<span style="color:red">C</span><span style="color:blue">o</span><span style="color:green">l</span><span style="color:orange">o</span><span style="color:magenta">r</span><span style="color:darkturquoise">s</span>**,
or styles in general, are a matter of taste, and no choice will fit every need. So you can override the default formatting for stderr, using `Logger::format_for_stderr()`, and for the files using `Logger::format_for_files()`, or for both in one shot using `Logger::format()`.
or styles in general, are a matter of taste, and no choice will fit every need. So you can override the default formatting and coloring in various ways.

With `--no-default-features --features="atty"` you can remove the yansi-based coloring but keep the capability to switch off your own coloring.

### **`specfile`**

Expand Down
73 changes: 71 additions & 2 deletions examples/colors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,75 @@
fn main() {
#[cfg(not(feature = "colors"))]
println!("Feature color is switched off");

#[cfg(feature = "colors")]
for i in 0..=255 {
println!("{}: {}", i, yansi::Paint::fixed(i, i));
{
use atty::Stream::{Stderr, Stdout};
use yansi::{Color, Paint, Style};

for i in 0..=255 {
println!("{}: {}", i, Paint::fixed(i, i));
}

println!("");

if atty::is(Stdout) {
println!(
"Stdout is considered a tty - \
flexi_logger::AdaptiveFormat will use colors",
);
} else {
println!(
"Stdout is not considered a tty - \
flexi_logger::AdaptiveFormat will NOT use colors"
);
}

if atty::is(Stderr) {
println!(
"Stderr is considered a tty - \
flexi_logger::AdaptiveFormat will use colors",
);
} else {
println!(
"Stderr is not considered a tty - \
flexi_logger::AdaptiveFormat will NOT use colors!"
);
}

// Enable ASCII escape sequence support on Windows consoles,
// but disable coloring on unsupported Windows consoles
if cfg!(windows) {
if !Paint::enable_windows_ascii() {
println!("unsupported windows console detected => coloring disabled");
Paint::disable();
return;
}
}

println!(
"\n{}",
Style::new(Color::Fixed(196))
.bold()
.paint("This is red output like by default with err!")
);
println!(
"{}",
Style::new(Color::Fixed(208))
.bold()
.paint("This is yellow output like by default with warn!")
);
println!(
"{}",
Style::new(Color::Unset).paint("This is normal output like by default with info!")
);
println!(
"{}",
Style::new(Color::Fixed(7)).paint("This is output like by default with debug!")
);
println!(
"{}",
Style::new(Color::Fixed(8)).paint("This is grey output like by default with trace!")
);
}
}
1 change: 1 addition & 0 deletions scripts/qualify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fn main() {
// Build in important variants
run_command!("cargo", "build");
run_command!("cargo", "build", "--no-default-features");
run_command!("cargo", "build", "--no-default-features", "--features=atty");
run_command!("cargo", "build", "--all-features");
run_command!("cargo", "+1.37.0", "build", "--no-default-features");
run_command!("cargo", "+1.37.0", "build", "--all-features");
Expand Down
4 changes: 4 additions & 0 deletions src/flexi_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,8 @@ pub enum FlexiLoggerError {
/// Some synchronization object is poisoned.
#[error("Some synchronization object is poisoned")]
Poison,

/// Palette parsing failed
#[error("Palette parsing failed")]
Palette(#[from] std::num::ParseIntError),
}
174 changes: 148 additions & 26 deletions src/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@ use std::thread;
#[cfg(feature = "colors")]
use yansi::{Color, Paint, Style};

/// Function type for Format functions.
///
/// If you want to write the log lines in your own format,
/// implement a function with this signature and provide it to one of the methods
/// [`Logger::format()`](struct.Logger.html#method.format),
/// [`Logger::format_for_files()`](struct.Logger.html#method.format_for_files),
/// or [`Logger::format_for_stderr()`](struct.Logger.html#method.format_for_stderr).
///
/// Checkout the code of the provided [format functions](index.html#functions)
/// if you want to start with a template.
///
/// ## Parameters
///
/// - `write`: the output stream
///
/// - `now`: the timestamp that you should use if you want a timestamp to appear in the log line
///
/// - `record`: the log line's content and metadata, as provided by the log crate's macros.
///
pub type FormatFunction = fn(
write: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &Record,
) -> Result<(), std::io::Error>;

/// A logline-formatter that produces log lines like <br>
/// ```INFO [my_prog::some_submodule] Task successfully read from conf.json```
///
Expand Down Expand Up @@ -212,43 +237,48 @@ pub fn colored_with_thread(
)
}

/// Helper function that is used in the provided colored format functions.
///
/// The palette that is used by `style` can be overridden by setting the environment variable
/// `FLEXI_LOGGER_PALETTE` to a semicolon-separated list of numbers (0..=255) and/or dashes (´-´).
/// The first five values denote the fixed color that is used for coloring error, warning, info,
/// debug, and trace messages.
///
/// `FLEXI_LOGGER_PALETTE = "196;208;-;7;8"`
/// reflects the default palette; color 196 is used for error messages, and so on.
/// Helper function that is used in the provided coloring format functions to apply
/// colors based on the log level and the effective color palette.
///
/// The '-' means that no coloring is done, i.e., with "-;-;-;-;-" all coloring is switched off.
///
/// For your convenience, if you want to specify your own palette,
/// you can produce a colored list of all 255 colors with `cargo run --example colors`
/// to see the available colors.
/// See [`Logger::set_palette`](struct.Logger.html#method.set_palette) if you want to
/// modify the color palette.
///
/// Only available with feature `colors`.
#[cfg(feature = "colors")]
pub fn style<T>(level: log::Level, item: T) -> Paint<T> {
let palette = &*(PALETTE.read().unwrap());
match level {
log::Level::Error => Paint::new(item).with_style(PALETTE.error),
log::Level::Warn => Paint::new(item).with_style(PALETTE.warn),
log::Level::Info => Paint::new(item).with_style(PALETTE.info),
log::Level::Debug => Paint::new(item).with_style(PALETTE.debug),
log::Level::Trace => Paint::new(item).with_style(PALETTE.trace),
log::Level::Error => palette.error,
log::Level::Warn => palette.warn,
log::Level::Info => palette.info,
log::Level::Debug => palette.debug,
log::Level::Trace => palette.trace,
}
.paint(item)
}

#[cfg(feature = "colors")]
lazy_static::lazy_static! {
static ref PALETTE: Palette = {
match std::env::var("FLEXI_LOGGER_PALETTE") {
Ok(palette) => Palette::parse(&palette).unwrap_or_else(|_| Palette::default()),
Err(..) => Palette::default(),
}
static ref PALETTE: std::sync::RwLock<Palette> = std::sync::RwLock::new(Palette::default());
}

};
// Overwrites the default PALETTE value either from the environment, if set,
// or from the parameter, if filled.
// Returns an error if parsing failed.
#[cfg(feature = "colors")]
pub(crate) fn set_palette(input: &Option<String>) -> Result<(), std::num::ParseIntError> {
match std::env::var_os("FLEXI_LOGGER_PALETTE") {
Some(ref env_osstring) => {
*(PALETTE.write().unwrap()) = Palette::from(env_osstring.to_string_lossy().as_ref())?;
}
None => match input {
Some(ref input_string) => {
*(PALETTE.write().unwrap()) = Palette::from(input_string)?;
}
None => {}
},
}
Ok(())
}

#[cfg(feature = "colors")]
Expand All @@ -271,7 +301,7 @@ impl Palette {
}
}

fn parse(palette: &str) -> Result<Palette, std::num::ParseIntError> {
fn from(palette: &str) -> Result<Palette, std::num::ParseIntError> {
let mut items = palette.split(';');
Ok(Palette {
error: parse_style(items.next().unwrap_or("196").trim())?,
Expand All @@ -291,3 +321,95 @@ fn parse_style(input: &str) -> Result<Style, std::num::ParseIntError> {
Style::new(Color::Fixed(input.parse()?))
})
}

/// Specifies the `FormatFunction` and decides if coloring should be used.
///
/// Is used in
/// [`Logger::adaptive_format_for_stderr`](struct.Logger.html#method.adaptive_format_for_stderr) and
/// [`Logger::adaptive_format_for_stdout`](struct.Logger.html#method.adaptive_format_for_stdout).
/// The coloring format functions are used if the output channel is a tty.
///
/// Only available with feature `atty`.
#[cfg(feature = "atty")]
#[derive(Clone, Copy)]
pub enum AdaptiveFormat {
/// Chooses between [`default_format`](fn.default_format.html)
/// and [`colored_default_format`](fn.colored_default_format.html).
///
/// Only available with feature `colors`.
#[cfg(feature = "colors")]
Default,
/// Chooses between [`detailed_format`](fn.detailed_format.html)
/// and [`colored_detailed_format`](fn.colored_detailed_format.html).
///
/// Only available with feature `colors`.
#[cfg(feature = "colors")]
Detailed,
/// Chooses between [`opt_format`](fn.opt_format.html)
/// and [`colored_opt_format`](fn.colored_opt_format.html).
///
/// Only available with feature `colors`.
#[cfg(feature = "colors")]
Opt,
/// Chooses between [`with_thread`](fn.with_thread.html)
/// and [`colored_with_thread`](fn.colored_with_thread.html).
///
/// Only available with feature `colors`.
#[cfg(feature = "colors")]
WithThread,
/// Chooses between the first format function (which is supposed to be uncolored)
/// and the second (which is supposed to be colored).
///
/// Allows providing own format functions, with freely choosable coloring technique,
/// _and_ making use of the tty detection.
Custom(FormatFunction, FormatFunction),
}

#[cfg(feature = "atty")]
impl AdaptiveFormat {
#[must_use]
pub(crate) fn format_function(self, stream: Stream) -> FormatFunction {
if stream.is_tty() {
match self {
#[cfg(feature = "colors")]
Self::Default => colored_default_format,
#[cfg(feature = "colors")]
Self::Detailed => colored_detailed_format,
#[cfg(feature = "colors")]
Self::Opt => colored_opt_format,
#[cfg(feature = "colors")]
Self::WithThread => colored_with_thread,
Self::Custom(_, colored) => colored,
}
} else {
match self {
#[cfg(feature = "colors")]
Self::Default => default_format,
#[cfg(feature = "colors")]
Self::Detailed => detailed_format,
#[cfg(feature = "colors")]
Self::Opt => opt_format,
#[cfg(feature = "colors")]
Self::WithThread => with_thread,
Self::Custom(uncolored, _) => uncolored,
}
}
}
}

#[cfg(feature = "atty")]
#[derive(Clone, Copy)]
pub(crate) enum Stream {
StdOut,
StdErr,
}
#[cfg(feature = "atty")]
impl Stream {
#[must_use]
pub fn is_tty(self) -> bool {
match self {
Self::StdOut => atty::is(atty::Stream::Stdout),
Self::StdErr => atty::is(atty::Stream::Stderr),
}
}
}
25 changes: 0 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,3 @@ pub use crate::log_specification::{LogSpecBuilder, LogSpecification, ModuleFilte
pub use crate::logger::{Duplicate, LogTarget, Logger};
pub use crate::parameters::{Age, Cleanup, Criterion, Naming};
pub use crate::reconfiguration_handle::ReconfigurationHandle;

/// Function type for Format functions.
///
/// If you want to write the log lines in your own format,
/// implement a function with this signature and provide it to one of the methods
/// [`Logger::format()`](struct.Logger.html#method.format),
/// [`Logger::format_for_files()`](struct.Logger.html#method.format_for_files),
/// or [`Logger::format_for_stderr()`](struct.Logger.html#method.format_for_stderr).
///
/// Checkout the code of the provided [format functions](index.html#functions)
/// if you want to start with a template.
///
/// ## Parameters
///
/// - `write`: the output stream
///
/// - `now`: the timestamp that you should use if you want a timestamp to appear in the log line
///
/// - `record`: the log line's content and metadata, as provided by the log crate's macros.
///
pub type FormatFunction = fn(
write: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &Record,
) -> Result<(), std::io::Error>;
Loading

0 comments on commit ed23959

Please sign in to comment.