use crate::misc;
#[cfg(feature = "slog-kvfilter")]
use crate::types::KVFilterParameters;
use crate::types::{Format, OverflowStrategy, Severity, SourceLocation, TimeZone};
use crate::{Build, Config, Result};
use slog::{self, Drain, FnValue, Logger};
use slog_async::Async;
#[cfg(feature = "slog-kvfilter")]
use slog_kvfilter::KVFilter;
use slog_term::{self, CompactFormat, FullFormat, PlainDecorator, TermDecorator};
use std::fmt::Debug;
use std::io;
use std::panic::{RefUnwindSafe, UnwindSafe};
#[derive(Debug)]
pub struct TerminalLoggerBuilder {
format: Format,
source_location: SourceLocation,
timezone: TimeZone,
destination: Destination,
overflow_strategy: OverflowStrategy,
level: Severity,
channel_size: usize,
#[cfg(feature = "slog-kvfilter")]
kvfilterparameters: Option<KVFilterParameters>,
}
impl TerminalLoggerBuilder {
pub fn new() -> Self {
TerminalLoggerBuilder {
format: Format::default(),
source_location: SourceLocation::default(),
overflow_strategy: OverflowStrategy::default(),
timezone: TimeZone::default(),
destination: Destination::default(),
level: Severity::default(),
channel_size: 1024,
#[cfg(feature = "slog-kvfilter")]
kvfilterparameters: None,
}
}
pub fn format(&mut self, format: Format) -> &mut Self {
self.format = format;
self
}
pub fn source_location(&mut self, source_location: SourceLocation) -> &mut Self {
self.source_location = source_location;
self
}
pub fn overflow_strategy(&mut self, overflow_strategy: OverflowStrategy) -> &mut Self {
self.overflow_strategy = overflow_strategy;
self
}
pub fn timezone(&mut self, timezone: TimeZone) -> &mut Self {
self.timezone = timezone;
self
}
pub fn destination(&mut self, destination: Destination) -> &mut Self {
self.destination = destination;
self
}
pub fn level(&mut self, severity: Severity) -> &mut Self {
self.level = severity;
self
}
pub fn channel_size(&mut self, channel_size: usize) -> &mut Self {
self.channel_size = channel_size;
self
}
#[cfg(feature = "slog-kvfilter")]
pub fn kvfilter(&mut self, parameters: KVFilterParameters) -> &mut Self {
self.kvfilterparameters = Some(parameters);
self
}
#[cfg(feature = "slog-kvfilter")]
fn build_with_drain<D>(&self, drain: D) -> Logger
where
D: Drain + Send + 'static,
D::Err: Debug,
{
let drain = Async::new(drain.fuse())
.chan_size(self.channel_size)
.overflow_strategy(self.overflow_strategy.to_async_type())
.build()
.fuse();
if let Some(ref p) = self.kvfilterparameters {
let kvdrain = KVFilter::new(drain, p.severity.as_level())
.always_suppress_any(p.always_suppress_any.clone())
.only_pass_any_on_all_keys(p.only_pass_any_on_all_keys.clone())
.always_suppress_on_regex(p.always_suppress_on_regex.clone())
.only_pass_on_regex(p.only_pass_on_regex.clone());
self.build_logger(kvdrain)
} else {
self.build_logger(drain)
}
}
#[cfg(not(feature = "slog-kvfilter"))]
fn build_with_drain<D>(&self, drain: D) -> Logger
where
D: Drain + Send + 'static,
D::Err: Debug,
{
let drain = Async::new(drain.fuse())
.chan_size(self.channel_size)
.overflow_strategy(self.overflow_strategy.to_async_type())
.build()
.fuse();
self.build_logger(drain)
}
fn build_logger<D>(&self, drain: D) -> Logger
where
D: Drain + Send + Sync + UnwindSafe + RefUnwindSafe + 'static,
D::Err: Debug,
{
let drain = self.level.set_level_filter(drain.fuse());
match self.source_location {
SourceLocation::None => Logger::root(drain.fuse(), o!()),
SourceLocation::ModuleAndLine => {
Logger::root(drain.fuse(), o!("module" => FnValue(misc::module_and_line)))
}
SourceLocation::FileAndLine => {
Logger::root(drain.fuse(), o!("module" => FnValue(misc::file_and_line)))
}
SourceLocation::LocalFileAndLine => Logger::root(
drain.fuse(),
o!("module" => FnValue(misc::local_file_and_line)),
),
}
}
}
impl Default for TerminalLoggerBuilder {
fn default() -> Self {
Self::new()
}
}
impl Build for TerminalLoggerBuilder {
fn build(&self) -> Result<Logger> {
let decorator = self.destination.to_decorator();
let timestamp = misc::timezone_to_timestamp_fn(self.timezone);
let logger = match self.format {
Format::Full => {
let format = FullFormat::new(decorator).use_custom_timestamp(timestamp);
self.build_with_drain(format.build())
}
Format::Compact => {
let format = CompactFormat::new(decorator).use_custom_timestamp(timestamp);
self.build_with_drain(format.build())
}
};
Ok(logger)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Destination {
Stdout,
Stderr,
}
impl Default for Destination {
fn default() -> Self {
Destination::Stdout
}
}
impl Destination {
fn to_decorator(self) -> Decorator {
let maybe_term_decorator = match self {
Destination::Stdout => TermDecorator::new().stdout().try_build(),
Destination::Stderr => TermDecorator::new().stderr().try_build(),
};
maybe_term_decorator
.map(Decorator::Term)
.unwrap_or_else(|| match self {
Destination::Stdout => Decorator::PlainStdout(PlainDecorator::new(io::stdout())),
Destination::Stderr => Decorator::PlainStderr(PlainDecorator::new(io::stderr())),
})
}
}
enum Decorator {
Term(TermDecorator),
PlainStdout(PlainDecorator<io::Stdout>),
PlainStderr(PlainDecorator<io::Stderr>),
}
impl slog_term::Decorator for Decorator {
fn with_record<F>(
&self,
record: &slog::Record,
logger_values: &slog::OwnedKVList,
f: F,
) -> io::Result<()>
where
F: FnOnce(&mut dyn slog_term::RecordDecorator) -> io::Result<()>,
{
match *self {
Decorator::Term(ref d) => d.with_record(record, logger_values, f),
Decorator::PlainStdout(ref d) => d.with_record(record, logger_values, f),
Decorator::PlainStderr(ref d) => d.with_record(record, logger_values, f),
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TerminalLoggerConfig {
#[serde(default)]
pub level: Severity,
#[serde(default)]
pub format: Format,
#[serde(default)]
pub source_location: SourceLocation,
#[serde(default)]
pub timezone: TimeZone,
#[serde(default)]
pub destination: Destination,
#[serde(default = "default_channel_size")]
pub channel_size: usize,
#[serde(default)]
pub overflow_strategy: OverflowStrategy,
}
impl Config for TerminalLoggerConfig {
type Builder = TerminalLoggerBuilder;
fn try_to_builder(&self) -> Result<Self::Builder> {
let mut builder = TerminalLoggerBuilder::new();
builder.level(self.level);
builder.format(self.format);
builder.source_location(self.source_location);
builder.timezone(self.timezone);
builder.destination(self.destination);
builder.channel_size(self.channel_size);
builder.overflow_strategy(self.overflow_strategy);
Ok(builder)
}
}
fn default_channel_size() -> usize {
1024
}