#![doc(test(attr(deny(warnings))))]
#![doc(test(attr(allow(dead_code))))]
#![doc(test(attr(allow(unused_variables))))]
#![warn(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::invalid_codeblock_attributes)]
#[cfg(feature = "native-test-stubs")]
mod native_test_stubs;
#[cfg(feature = "native-test-stubs")]
pub use native_test_stubs::*;
#[cfg(not(feature = "native-test-stubs"))]
use fastly::{
error::{anyhow, Error},
log::Endpoint,
};
use log::{Level, LevelFilter, Log, Metadata, Record};
use regex::{RegexSet, RegexSetBuilder};
use std::collections::HashMap;
use std::io::Write;
#[derive(Debug)]
pub struct Logger {
endpoints: HashMap<String, (Endpoint, Option<LevelFilter>)>,
default_endpoints: HashMap<Level, Endpoint>,
max_level: LevelFilter,
module_filters: Option<HashMap<Level, RegexSet>>,
echo_stdout: bool,
echo_stderr: bool,
}
impl Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
self.lookup_endpoint(metadata, None).is_some()
}
fn log(&self, record: &Record) {
if let Some(endpoint) = self.lookup_endpoint(record.metadata(), record.module_path()) {
let msg = format!("{}", record.args());
let _ = write!(endpoint.clone(), "{}", msg);
if self.echo_stdout {
println!("{}", msg);
}
if self.echo_stderr {
eprintln!("{}", msg);
}
}
}
fn flush(&self) {}
}
impl Logger {
pub fn builder() -> Builder {
Builder::new()
}
fn max_level(&self) -> LevelFilter {
self.max_level
}
fn lookup_endpoint(&self, metadata: &Metadata, module_path: Option<&str>) -> Option<&Endpoint> {
let level = metadata.level();
if level > self.max_level {
return None;
}
if let Some(module_path) = module_path {
if let Some(filter) = self.module_filters.as_ref().and_then(|fs| fs.get(&level)) {
if !filter.is_match(module_path) {
return None;
}
}
}
if let Some((endpoint, filter)) = self.endpoints.get(metadata.target()) {
if let Some(filter) = filter {
if level <= *filter {
Some(endpoint)
} else {
None
}
} else {
Some(endpoint)
}
} else {
self.default_endpoints.get(&level)
}
}
}
#[derive(Debug)]
pub struct Builder {
inner: Result<Inner, Error>,
}
#[derive(Debug)]
struct Inner {
endpoints: HashMap<String, (Endpoint, Option<LevelFilter>)>,
default_endpoints: HashMap<Level, Endpoint>,
max_level: LevelFilter,
module_filters: HashMap<LevelFilter, Vec<String>>,
echo_stdout: bool,
echo_stderr: bool,
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl Builder {
pub fn new() -> Self {
let inner = Inner {
endpoints: HashMap::new(),
default_endpoints: HashMap::new(),
max_level: LevelFilter::Off,
module_filters: HashMap::new(),
echo_stdout: false,
echo_stderr: false,
};
Self { inner: Ok(inner) }
}
fn with_inner<F: FnOnce(&mut Inner) -> R, R>(&mut self, f: F) {
let _ = self.inner.as_mut().map(f);
}
fn with_inner_and_then<A, F, R, E>(&mut self, arg: Result<A, E>, f: F)
where
F: FnOnce(&mut Inner, A) -> R,
E: Into<Error>,
{
match arg {
Ok(x) => self.with_inner(|i| f(i, x)),
Err(e) => self.inner = Err(e.into()),
}
}
pub fn endpoint<E>(&mut self, endpoint: E) -> &mut Self
where
E: TryInto<Endpoint>,
<E as TryInto<Endpoint>>::Error: Into<Error>,
{
self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
i.endpoints
.insert(endpoint.name().to_owned(), (endpoint, None))
});
self
}
pub fn endpoint_level<E>(&mut self, endpoint: E, level: LevelFilter) -> &mut Self
where
E: TryInto<Endpoint>,
<E as TryInto<Endpoint>>::Error: Into<Error>,
{
self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
i.endpoints
.insert(endpoint.name().to_owned(), (endpoint, Some(level)))
});
self
}
pub fn default_endpoint<E>(&mut self, endpoint: E) -> &mut Self
where
E: TryInto<Endpoint>,
<E as TryInto<Endpoint>>::Error: Into<Error>,
{
self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
for level in &[
Level::Error,
Level::Warn,
Level::Info,
Level::Debug,
Level::Trace,
] {
i.default_endpoints.insert(*level, endpoint.clone());
}
});
self
}
pub fn default_level_endpoint<E>(&mut self, endpoint: E, level: Level) -> &mut Self
where
E: TryInto<Endpoint>,
<E as TryInto<Endpoint>>::Error: Into<Error>,
{
self.with_inner_and_then(endpoint.try_into(), |i, endpoint| {
i.default_endpoints.insert(level, endpoint.clone());
i.endpoints
.insert(endpoint.name().to_owned(), (endpoint, None))
});
self
}
pub fn max_level(&mut self, level: LevelFilter) -> &mut Self {
self.with_inner(|i| i.max_level = level);
self
}
pub fn filter_module(&mut self, regex: &str, level: LevelFilter) -> &mut Self {
self.with_inner(|i| {
i.module_filters
.entry(level)
.or_insert_with(|| vec![])
.push(regex.to_owned())
});
self
}
pub fn echo_stdout(&mut self, enabled: bool) -> &mut Self {
self.with_inner(|i| i.echo_stdout = enabled);
self
}
pub fn echo_stderr(&mut self, enabled: bool) -> &mut Self {
self.with_inner(|i| i.echo_stderr = enabled);
self
}
pub fn init(&mut self) {
self.try_init().expect("log_fastly::Builder::init() failed")
}
pub fn try_init(&mut self) -> Result<(), Error> {
let logger = self.build()?;
let max_level = logger.max_level();
let res = log::set_boxed_logger(Box::new(logger));
if res.is_ok() {
log::set_max_level(max_level);
}
res.map_err(Into::into)
}
pub fn build(&mut self) -> Result<Logger, Error> {
let inner = std::mem::replace(
&mut self.inner,
Err(anyhow!("Builder can only be built once")),
)?;
let endpoint_max = inner
.endpoints
.values()
.map(|e| e.1.unwrap_or_else(LevelFilter::max))
.max()
.unwrap_or(LevelFilter::Off);
let default_max = inner
.default_endpoints
.keys()
.max()
.map(Level::to_level_filter)
.unwrap_or(LevelFilter::Off);
let max_level = std::cmp::min(inner.max_level, std::cmp::max(endpoint_max, default_max));
let module_filters = generate_module_filters(inner.module_filters)?;
Ok(Logger {
endpoints: inner.endpoints,
default_endpoints: inner.default_endpoints,
max_level,
module_filters,
echo_stdout: inner.echo_stdout,
echo_stderr: inner.echo_stderr,
})
}
}
fn generate_module_filters(
mut regex_map: HashMap<LevelFilter, Vec<String>>,
) -> Result<Option<HashMap<Level, RegexSet>>, Error> {
if regex_map.is_empty() {
Ok(None)
} else {
let levels = [
LevelFilter::Trace,
LevelFilter::Debug,
LevelFilter::Info,
LevelFilter::Warn,
LevelFilter::Error,
];
let mut module_filters = HashMap::with_capacity(levels.len());
let mut running_regexes = vec![];
for level in levels.iter() {
if let Some(regexes) = regex_map.remove(&level) {
running_regexes.extend(regexes);
}
let matcher = RegexSetBuilder::new(running_regexes.iter()).build()?;
let level = level
.to_level()
.expect("only iterating on LevelFilters that match a Level");
module_filters.insert(level, matcher);
}
Ok(Some(module_filters))
}
}
pub fn init_simple<E>(endpoint: E, level: LevelFilter)
where
E: TryInto<Endpoint>,
<E as TryInto<Endpoint>>::Error: Into<Error>,
{
Logger::builder()
.default_endpoint(endpoint)
.max_level(level)
.try_init()
.expect("log_fastly::init_simple() failed");
}