diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d728097..6988418 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,7 @@ jobs: - name: Run examples run: | cargo run --example simple_stdio + cargo run --example fn_layout_filter cargo run --features="no-color" --example no_color_stdio cargo run --features="json" --example json_stdio cargo run --features="json,file" --example rolling_file diff --git a/Cargo.toml b/Cargo.toml index e678675..f81c48f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,10 @@ time = { version = "0.3", features = [ "parsing", ], optional = true } +[[example]] +name = "fn_layout_filter" +path = "examples/fn_layout_filter.rs" + [[example]] name = "simple_stdio" path = "examples/simple_stdio.rs" diff --git a/README.md b/README.md index c4ac9f6..ae9bf81 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# LogForth Project +# Logforth Project A versatile and extensible logging implementation. diff --git a/examples/fn_layout_filter.rs b/examples/fn_layout_filter.rs new file mode 100644 index 0000000..0d9282d --- /dev/null +++ b/examples/fn_layout_filter.rs @@ -0,0 +1,48 @@ +// Copyright 2024 tison +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::LevelFilter; +use logforth::BoxDynFilter; +use logforth::BoxDynLayout; +use logforth::DispatchAppend; +use logforth::FilterResult; +use logforth::Logger; +use logforth::StdoutAppend; + +fn main() { + let layout = BoxDynLayout::new(|record: &log::Record| { + let message = format!("[box dyn] {}", record.args()); + Ok(message.into_bytes()) + // ...or + // anyhow::bail!("boom: {}", message) + }); + + let filter = BoxDynFilter::new(|metadata: &log::Metadata| { + if metadata.level() <= LevelFilter::Info { + FilterResult::Accept + } else { + FilterResult::Reject + } + }); + + let append = StdoutAppend::default().with_layout(layout); + let append = DispatchAppend::new(append).filter(filter); + Logger::new().add_append(append).apply().unwrap(); + + log::error!("Hello error!"); + log::warn!("Hello warn!"); + log::info!("Hello info!"); + log::debug!("Hello debug!"); + log::trace!("Hello trace!"); +} diff --git a/src/append/boxdyn.rs b/src/append/boxdyn.rs new file mode 100644 index 0000000..430c501 --- /dev/null +++ b/src/append/boxdyn.rs @@ -0,0 +1,55 @@ +// Copyright 2024 tison +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; + +use log::Metadata; +use log::Record; + +use crate::Append; +use crate::AppendImpl; + +pub struct BoxDynAppend(Box); + +impl BoxDynAppend { + pub fn new(append: impl Append + Send + Sync + 'static) -> Self { + Self(Box::new(append)) + } +} + +impl Debug for BoxDynAppend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BoxDynAppend {{ ... }}") + } +} + +impl Append for BoxDynAppend { + fn enabled(&self, metadata: &Metadata) -> bool { + (*self.0).enabled(metadata) + } + + fn try_append(&self, record: &Record) -> anyhow::Result<()> { + (*self.0).try_append(record) + } + + fn flush(&self) { + (*self.0).flush() + } +} + +impl From for AppendImpl { + fn from(append: BoxDynAppend) -> Self { + AppendImpl::BoxDyn(append) + } +} diff --git a/src/append/boxlog.rs b/src/append/boxlog.rs new file mode 100644 index 0000000..50aadc1 --- /dev/null +++ b/src/append/boxlog.rs @@ -0,0 +1,57 @@ +// Copyright 2024 tison +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; + +use log::Log; +use log::Metadata; +use log::Record; + +use crate::Append; +use crate::AppendImpl; + +pub struct BoxLogAppend(Box); + +impl Debug for BoxLogAppend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BoxLogAppend {{ ... }}") + } +} + +impl BoxLogAppend { + pub fn new(log: impl Log + 'static) -> Self { + Self(Box::new(log)) + } +} + +impl Append for BoxLogAppend { + fn enabled(&self, metadata: &Metadata) -> bool { + (*self.0).enabled(metadata) + } + + fn try_append(&self, record: &Record) -> anyhow::Result<()> { + (*self.0).log(record); + Ok(()) + } + + fn flush(&self) { + (*self.0).flush() + } +} + +impl From for AppendImpl { + fn from(append: BoxLogAppend) -> Self { + AppendImpl::BoxLog(append) + } +} diff --git a/src/append/mod.rs b/src/append/mod.rs index 428d180..6211fad 100644 --- a/src/append/mod.rs +++ b/src/append/mod.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub use boxdyn::*; +pub use boxlog::*; pub use dispatch::*; #[cfg(feature = "fastrace")] pub use fastrace::*; @@ -19,6 +21,8 @@ pub use fastrace::*; pub use file::*; pub use stdio::*; +mod boxdyn; +mod boxlog; mod dispatch; #[cfg(feature = "fastrace")] mod fastrace; @@ -41,6 +45,8 @@ pub trait Append { #[derive(Debug)] pub enum AppendImpl { + BoxDyn(BoxDynAppend), + BoxLog(BoxLogAppend), Dispatch(DispatchAppend), #[cfg(feature = "fastrace")] Fastrace(FastraceAppend), @@ -53,6 +59,8 @@ pub enum AppendImpl { impl Append for AppendImpl { fn enabled(&self, metadata: &log::Metadata) -> bool { match self { + AppendImpl::BoxDyn(append) => append.enabled(metadata), + AppendImpl::BoxLog(append) => append.enabled(metadata), AppendImpl::Dispatch(append) => append.enabled(metadata), #[cfg(feature = "fastrace")] AppendImpl::Fastrace(append) => append.enabled(metadata), @@ -65,6 +73,8 @@ impl Append for AppendImpl { fn try_append(&self, record: &log::Record) -> anyhow::Result<()> { match self { + AppendImpl::BoxDyn(append) => append.try_append(record), + AppendImpl::BoxLog(append) => append.try_append(record), AppendImpl::Dispatch(append) => append.try_append(record), #[cfg(feature = "fastrace")] AppendImpl::Fastrace(append) => append.try_append(record), @@ -77,6 +87,8 @@ impl Append for AppendImpl { fn flush(&self) { match self { + AppendImpl::BoxDyn(append) => append.flush(), + AppendImpl::BoxLog(append) => append.flush(), AppendImpl::Dispatch(append) => append.flush(), #[cfg(feature = "fastrace")] AppendImpl::Fastrace(append) => append.flush(), diff --git a/src/filter/boxdyn.rs b/src/filter/boxdyn.rs new file mode 100644 index 0000000..9130182 --- /dev/null +++ b/src/filter/boxdyn.rs @@ -0,0 +1,58 @@ +// Copyright 2024 tison +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; + +use log::Metadata; +use log::Record; + +use crate::Filter; +use crate::FilterImpl; +use crate::FilterResult; + +pub struct BoxDynFilter(Box); + +impl Debug for BoxDynFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BoxDynFilter {{ ... }}") + } +} + +impl BoxDynFilter { + pub fn new(filter: impl Filter + Send + Sync + 'static) -> Self { + Self(Box::new(filter)) + } +} + +impl Filter for BoxDynFilter { + fn filter(&self, record: &Record) -> FilterResult { + (*self.0).filter(record) + } + + fn filter_metadata(&self, metadata: &Metadata) -> FilterResult { + (*self.0).filter_metadata(metadata) + } +} + +impl From for FilterImpl { + fn from(filter: BoxDynFilter) -> Self { + FilterImpl::BoxDyn(filter) + } +} + +impl FilterResult> Filter for T { + fn filter_metadata(&self, metadata: &Metadata) -> FilterResult { + self(metadata) + } +} diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 826b351..09a081d 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use log::Metadata; -use log::Record; +pub use boxdyn::BoxDynFilter; pub use log_level::LogLevelFilter; +mod boxdyn; mod log_level; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -29,27 +29,30 @@ pub enum FilterResult { } pub trait Filter { - fn filter(&self, _record: &Record) -> FilterResult { + fn filter(&self, _record: &log::Record) -> FilterResult { FilterResult::Neutral } - fn filter_metadata(&self, metadata: &Metadata) -> FilterResult; + fn filter_metadata(&self, metadata: &log::Metadata) -> FilterResult; } #[derive(Debug)] pub enum FilterImpl { + BoxDyn(BoxDynFilter), LogLevel(LogLevelFilter), } impl Filter for FilterImpl { - fn filter(&self, record: &Record) -> FilterResult { + fn filter(&self, record: &log::Record) -> FilterResult { match self { + FilterImpl::BoxDyn(filter) => filter.filter(record), FilterImpl::LogLevel(filter) => filter.filter(record), } } - fn filter_metadata(&self, metadata: &Metadata) -> FilterResult { + fn filter_metadata(&self, metadata: &log::Metadata) -> FilterResult { match self { + FilterImpl::BoxDyn(filter) => filter.filter_metadata(metadata), FilterImpl::LogLevel(filter) => filter.filter_metadata(metadata), } } diff --git a/src/layout/boxdyn.rs b/src/layout/boxdyn.rs new file mode 100644 index 0000000..fc46e73 --- /dev/null +++ b/src/layout/boxdyn.rs @@ -0,0 +1,52 @@ +// Copyright 2024 tison +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; + +use log::Record; + +use crate::Layout; +use crate::LayoutImpl; + +pub struct BoxDynLayout(Box); + +impl Debug for BoxDynLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BoxDynLayout {{ ... }}") + } +} + +impl BoxDynLayout { + pub fn new(layout: impl Layout + Send + Sync + 'static) -> Self { + Self(Box::new(layout)) + } +} + +impl Layout for BoxDynLayout { + fn format_bytes(&self, record: &Record) -> anyhow::Result> { + (*self.0).format_bytes(record) + } +} + +impl From for LayoutImpl { + fn from(layout: BoxDynLayout) -> Self { + LayoutImpl::BoxDyn(layout) + } +} + +impl anyhow::Result>> Layout for T { + fn format_bytes(&self, record: &Record) -> anyhow::Result> { + self(record) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8c3eea9..03b9e8b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -12,23 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub use boxdyn::BoxDynLayout; pub use kv_display::KvDisplay; -use log::Record; #[cfg(feature = "json")] pub use simple_json::SimpleJsonLayout; pub use simple_text::SimpleTextLayout; +mod boxdyn; mod kv_display; #[cfg(feature = "json")] mod simple_json; mod simple_text; pub trait Layout { - fn format_bytes(&self, record: &Record) -> anyhow::Result>; + fn format_bytes(&self, record: &log::Record) -> anyhow::Result>; } #[derive(Debug)] pub enum LayoutImpl { + BoxDyn(BoxDynLayout), SimpleText(SimpleTextLayout), #[cfg(feature = "json")] SimpleJson(SimpleJsonLayout), @@ -41,8 +43,9 @@ impl Default for LayoutImpl { } impl Layout for LayoutImpl { - fn format_bytes(&self, record: &Record) -> anyhow::Result> { + fn format_bytes(&self, record: &log::Record) -> anyhow::Result> { match self { + LayoutImpl::BoxDyn(layout) => layout.format_bytes(record), LayoutImpl::SimpleText(layout) => layout.format_bytes(record), #[cfg(feature = "json")] LayoutImpl::SimpleJson(layout) => layout.format_bytes(record), diff --git a/src/logger.rs b/src/logger.rs index f194761..dee0b38 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -72,12 +72,12 @@ impl log::Log for Logger { fn handle_error(record: &Record, error: anyhow::Error) { let Err(fallback_error) = write!( std::io::stderr(), - r#" - Error perform logging. - Attempted to log: {args} - Record: {record:?} - Error: {error} - "#, + r###" +Error perform logging. + Attempted to log: {args} + Record: {record:?} + Error: {error} +"###, args = record.args(), record = record, error = error, @@ -86,13 +86,13 @@ fn handle_error(record: &Record, error: anyhow::Error) { }; panic!( - r#" - Error performing stderr logging after error occurred during regular logging. - Attempted to log: {args} - Record: {record:?} - Error: {error} - Fallback error: {fallback_error} - "#, + r###" +Error performing stderr logging after error occurred during regular logging. + Attempted to log: {args} + Record: {record:?} + Error: {error} + Fallback error: {fallback_error} +"###, args = record.args(), record = record, error = error,