Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn main() {
})
.collect::<Vec<_>>();

if failed.len() > 0 {
if !failed.is_empty() {
for failed in failed {
eprintln!("FAIL: {:?}", failed);
}
Expand Down
2 changes: 1 addition & 1 deletion ci/src/permute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ where
{
let mut permutations = BTreeSet::new();

if input.len() == 0 {
if input.is_empty() {
return permutations;
}

Expand Down
4 changes: 2 additions & 2 deletions ci/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ impl Default for TestArgs {

impl TestArgs {
fn features_string(&self) -> Option<String> {
if self.features.len() == 0 {
if self.features.is_empty() {
return None;
}

let s = self.features.iter().fold(String::new(), |mut s, f| {
if s.len() > 0 {
if !s.is_empty() {
s.push_str(" ");
}
s.push_str(f);
Expand Down
81 changes: 81 additions & 0 deletions examples/custom_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*!
Using `env_logger`.

Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`:

```no_run,shell
$ export MY_LOG_LEVEL='info'
```

Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors
or `auto` to enable them:

```no_run,shell
$ export MY_LOG_STYLE=never
```
*/

#[macro_use]
extern crate log;

use env_logger::{Builder, Env, Target};
use std::{
io,
sync::mpsc::{channel, Sender},
};

// This struct is used as an adaptor, it implements io::Write and forwards the buffer to a mpsc::Sender
struct WriteAdapter {
sender: Sender<u8>,
}

impl io::Write for WriteAdapter {
// On write we forward each u8 of the buffer to the sender and return the length of the buffer
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for chr in buf {
self.sender.send(*chr).unwrap();
}
Ok(buf.len())
}

fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

fn main() {
// The `Env` lets us tweak what the environment
// variables to read are and what the default
// value is if they're missing
let env = Env::default()
.filter_or("MY_LOG_LEVEL", "trace")
// Normally using a pipe as a target would mean a value of false, but this forces it to be true.
.write_style_or("MY_LOG_STYLE", "always");

// Create the channel for the log messages
let (rx, tx) = channel();

Builder::from_env(env)
// The Sender of the channel is given to the logger
// A wrapper is needed, because the `Sender` itself doesn't implement `std::io::Write`.
.target(Target::Pipe(Box::new(WriteAdapter { sender: rx })))
.init();

trace!("some trace log");
debug!("some debug log");
info!("some information log");
warn!("some warning log");
error!("some error log");

// Collect all messages send to the channel and parse the result as a string
String::from_utf8(tx.try_iter().collect::<Vec<u8>>())
.unwrap()
// Split the result into lines so a prefix can be added to each line
.split('\n')
.for_each(|msg| {
// Print the message with a prefix if it has any content
if !msg.is_empty() {
println!("from pipe: {}", msg)
}
});
}
99 changes: 75 additions & 24 deletions src/fmt/writer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ mod termcolor;

use self::atty::{is_stderr, is_stdout};
use self::termcolor::BufferWriter;
use std::{fmt, io};
use std::{fmt, io, mem, sync::Mutex};

pub(in crate::fmt) mod glob {
pub(super) mod glob {
pub use super::termcolor::glob::*;
pub use super::*;
}

pub(in crate::fmt) use self::termcolor::Buffer;
pub(super) use self::termcolor::Buffer;

/// Log target, either `stdout` or `stderr`.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
/// Log target, either `stdout`, `stderr` or a custom pipe.
#[non_exhaustive]
pub enum Target {
/// Logs will be sent to standard output.
Stdout,
/// Logs will be sent to standard error.
Stderr,
/// Logs will be sent to a custom pipe.
Pipe(Box<dyn io::Write + Send + 'static>),
}

impl Default for Target {
Expand All @@ -27,6 +29,61 @@ impl Default for Target {
}
}

impl fmt::Debug for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Stdout => "stdout",
Self::Stderr => "stderr",
Self::Pipe(_) => "pipe",
}
)
}
}

/// Log target, either `stdout`, `stderr` or a custom pipe.
///
/// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability.
pub(super) enum WritableTarget {
/// Logs will be sent to standard output.
Stdout,
/// Logs will be sent to standard error.
Stderr,
/// Logs will be sent to a custom pipe.
Pipe(Box<Mutex<dyn io::Write + Send + 'static>>),
}

impl From<Target> for WritableTarget {
fn from(target: Target) -> Self {
match target {
Target::Stdout => Self::Stdout,
Target::Stderr => Self::Stderr,
Target::Pipe(pipe) => Self::Pipe(Box::new(Mutex::new(pipe))),
}
}
}

impl Default for WritableTarget {
fn default() -> Self {
Self::from(Target::default())
}
}

impl fmt::Debug for WritableTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Stdout => "stdout",
Self::Stderr => "stderr",
Self::Pipe(_) => "pipe",
}
)
}
}
/// Whether or not to print styles to the target.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WriteStyle {
Expand Down Expand Up @@ -55,20 +112,21 @@ impl Writer {
self.write_style
}

pub(in crate::fmt) fn buffer(&self) -> Buffer {
pub(super) fn buffer(&self) -> Buffer {
self.inner.buffer()
}

pub(in crate::fmt) fn print(&self, buf: &Buffer) -> io::Result<()> {
pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> {
self.inner.print(buf)
}
}

/// A builder for a terminal writer.
///
/// The target and style choice can be configured before building.
#[derive(Debug)]
pub(crate) struct Builder {
target: Target,
target: WritableTarget,
write_style: WriteStyle,
is_test: bool,
built: bool,
Expand All @@ -87,7 +145,7 @@ impl Builder {

/// Set the target to write to.
pub(crate) fn target(&mut self, target: Target) -> &mut Self {
self.target = target;
self.target = target.into();
self
}

Expand Down Expand Up @@ -119,9 +177,10 @@ impl Builder {

let color_choice = match self.write_style {
WriteStyle::Auto => {
if match self.target {
Target::Stderr => is_stderr(),
Target::Stdout => is_stdout(),
if match &self.target {
WritableTarget::Stderr => is_stderr(),
WritableTarget::Stdout => is_stdout(),
WritableTarget::Pipe(_) => false,
} {
WriteStyle::Auto
} else {
Expand All @@ -131,9 +190,10 @@ impl Builder {
color_choice => color_choice,
};

let writer = match self.target {
Target::Stderr => BufferWriter::stderr(self.is_test, color_choice),
Target::Stdout => BufferWriter::stdout(self.is_test, color_choice),
let writer = match mem::take(&mut self.target) {
WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice),
WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice),
WritableTarget::Pipe(pipe) => BufferWriter::pipe(self.is_test, color_choice, pipe),
};

Writer {
Expand All @@ -149,15 +209,6 @@ impl Default for Builder {
}
}

impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Logger")
.field("target", &self.target)
.field("write_style", &self.write_style)
.finish()
}
}

impl fmt::Debug for Writer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Writer").finish()
Expand Down
48 changes: 37 additions & 11 deletions src/fmt/writer/termcolor/extern_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use std::cell::RefCell;
use std::fmt;
use std::io::{self, Write};
use std::rc::Rc;
use std::sync::Mutex;

use log::Level;
use termcolor::{self, ColorChoice, ColorSpec, WriteColor};

use crate::fmt::{Formatter, Target, WriteStyle};
use crate::fmt::{Formatter, WritableTarget, WriteStyle};

pub(in crate::fmt::writer) mod glob {
pub use super::*;
Expand Down Expand Up @@ -70,46 +71,71 @@ impl Formatter {

pub(in crate::fmt::writer) struct BufferWriter {
inner: termcolor::BufferWriter,
test_target: Option<Target>,
test_target: Option<WritableTarget>,
}

pub(in crate::fmt) struct Buffer {
inner: termcolor::Buffer,
test_target: Option<Target>,
has_test_target: bool,
}

impl BufferWriter {
pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self {
BufferWriter {
inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()),
test_target: if is_test { Some(Target::Stderr) } else { None },
test_target: if is_test {
Some(WritableTarget::Stderr)
} else {
None
},
}
}

pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self {
BufferWriter {
inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()),
test_target: if is_test { Some(Target::Stdout) } else { None },
test_target: if is_test {
Some(WritableTarget::Stdout)
} else {
None
},
}
}

pub(in crate::fmt::writer) fn pipe(
is_test: bool,
write_style: WriteStyle,
pipe: Box<Mutex<dyn io::Write + Send + 'static>>,
) -> Self {
BufferWriter {
// The inner Buffer is never printed from, but it is still needed to handle coloring and other formating
inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()),
test_target: if is_test {
Some(WritableTarget::Pipe(pipe))
} else {
None
},
}
}

pub(in crate::fmt::writer) fn buffer(&self) -> Buffer {
Buffer {
inner: self.inner.buffer(),
test_target: self.test_target,
has_test_target: self.test_target.is_some(),
}
}

pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> {
if let Some(target) = self.test_target {
if let Some(target) = &self.test_target {
// This impl uses the `eprint` and `print` macros
// instead of `termcolor`'s buffer.
// This is so their output can be captured by `cargo test`
let log = String::from_utf8_lossy(buf.bytes());

match target {
Target::Stderr => eprint!("{}", log),
Target::Stdout => print!("{}", log),
WritableTarget::Stderr => eprint!("{}", log),
WritableTarget::Stdout => print!("{}", log),
WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), "{}", log)?,
}

Ok(())
Expand Down Expand Up @@ -138,7 +164,7 @@ impl Buffer {

fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
// Ignore styles for test captured logs because they can't be printed
if self.test_target.is_none() {
if !self.has_test_target {
self.inner.set_color(spec)
} else {
Ok(())
Expand All @@ -147,7 +173,7 @@ impl Buffer {

fn reset(&mut self) -> io::Result<()> {
// Ignore styles for test captured logs because they can't be printed
if self.test_target.is_none() {
if !self.has_test_target {
self.inner.reset()
} else {
Ok(())
Expand Down
Loading