use derivative::Derivative;
use log::Record;
use std::{
fmt,
io::{self, Write},
};
#[cfg(feature = "config_parsing")]
use crate::config::{Deserialize, Deserializers};
#[cfg(feature = "config_parsing")]
use crate::encode::EncoderConfig;
use crate::{
append::Append,
encode::{
self,
pattern::PatternEncoder,
writer::{
console::{ConsoleWriter, ConsoleWriterLock},
simple::SimpleWriter,
},
Encode, Style,
},
priv_io::{StdWriter, StdWriterLock},
};
#[cfg(feature = "config_parsing")]
#[derive(Debug, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConsoleAppenderConfig {
target: Option<ConfigTarget>,
encoder: Option<EncoderConfig>,
tty_only: Option<bool>,
}
#[cfg(feature = "config_parsing")]
#[derive(Debug, serde::Deserialize)]
enum ConfigTarget {
#[serde(rename = "stdout")]
Stdout,
#[serde(rename = "stderr")]
Stderr,
}
enum Writer {
Tty(ConsoleWriter),
Raw(StdWriter),
}
impl Writer {
fn lock(&self) -> WriterLock {
match *self {
Writer::Tty(ref w) => WriterLock::Tty(w.lock()),
Writer::Raw(ref w) => WriterLock::Raw(SimpleWriter(w.lock())),
}
}
fn is_tty(&self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match self {
Self::Tty(_) => true,
_ => false,
}
}
}
enum WriterLock<'a> {
Tty(ConsoleWriterLock<'a>),
Raw(SimpleWriter<StdWriterLock<'a>>),
}
impl<'a> io::Write for WriterLock<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
WriterLock::Tty(ref mut w) => w.write(buf),
WriterLock::Raw(ref mut w) => w.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match *self {
WriterLock::Tty(ref mut w) => w.flush(),
WriterLock::Raw(ref mut w) => w.flush(),
}
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
match *self {
WriterLock::Tty(ref mut w) => w.write_all(buf),
WriterLock::Raw(ref mut w) => w.write_all(buf),
}
}
fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
match *self {
WriterLock::Tty(ref mut w) => w.write_fmt(fmt),
WriterLock::Raw(ref mut w) => w.write_fmt(fmt),
}
}
}
impl<'a> encode::Write for WriterLock<'a> {
fn set_style(&mut self, style: &Style) -> io::Result<()> {
match *self {
WriterLock::Tty(ref mut w) => w.set_style(style),
WriterLock::Raw(ref mut w) => w.set_style(style),
}
}
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct ConsoleAppender {
#[derivative(Debug = "ignore")]
writer: Writer,
encoder: Box<dyn Encode>,
do_write: bool,
}
impl Append for ConsoleAppender {
fn append(&self, record: &Record) -> anyhow::Result<()> {
if self.do_write {
let mut writer = self.writer.lock();
self.encoder.encode(&mut writer, record)?;
writer.flush()?;
}
Ok(())
}
fn flush(&self) {}
}
impl ConsoleAppender {
pub fn builder() -> ConsoleAppenderBuilder {
ConsoleAppenderBuilder {
encoder: None,
target: Target::Stdout,
tty_only: false,
}
}
}
pub struct ConsoleAppenderBuilder {
encoder: Option<Box<dyn Encode>>,
target: Target,
tty_only: bool,
}
impl ConsoleAppenderBuilder {
pub fn encoder(mut self, encoder: Box<dyn Encode>) -> ConsoleAppenderBuilder {
self.encoder = Some(encoder);
self
}
pub fn target(mut self, target: Target) -> ConsoleAppenderBuilder {
self.target = target;
self
}
pub fn tty_only(mut self, tty_only: bool) -> ConsoleAppenderBuilder {
self.tty_only = tty_only;
self
}
pub fn build(self) -> ConsoleAppender {
let writer = match self.target {
Target::Stderr => match ConsoleWriter::stderr() {
Some(writer) => Writer::Tty(writer),
None => Writer::Raw(StdWriter::stderr()),
},
Target::Stdout => match ConsoleWriter::stdout() {
Some(writer) => Writer::Tty(writer),
None => Writer::Raw(StdWriter::stdout()),
},
};
let do_write = writer.is_tty() || !self.tty_only;
ConsoleAppender {
writer,
encoder: self
.encoder
.unwrap_or_else(|| Box::<PatternEncoder>::default()),
do_write,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Target {
Stdout,
Stderr,
}
#[cfg(feature = "config_parsing")]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct ConsoleAppenderDeserializer;
#[cfg(feature = "config_parsing")]
impl Deserialize for ConsoleAppenderDeserializer {
type Trait = dyn Append;
type Config = ConsoleAppenderConfig;
fn deserialize(
&self,
config: ConsoleAppenderConfig,
deserializers: &Deserializers,
) -> anyhow::Result<Box<dyn Append>> {
let mut appender = ConsoleAppender::builder();
if let Some(target) = config.target {
let target = match target {
ConfigTarget::Stdout => Target::Stdout,
ConfigTarget::Stderr => Target::Stderr,
};
appender = appender.target(target);
}
if let Some(tty_only) = config.tty_only {
appender = appender.tty_only(tty_only);
}
if let Some(encoder) = config.encoder {
appender = appender.encoder(deserializers.deserialize(&encoder.kind, encoder.config)?);
}
Ok(Box::new(appender.build()))
}
}