logger configuration throigh struct
cargo devel CI / cargo CI (push) Successful in 56s Details

This commit is contained in:
Christoph J. Scherr 2024-03-10 19:38:51 +01:00
parent ab9029fced
commit c918fbf196
Signed by: PlexSheep
GPG Key ID: 7CDD0B14851A08EF
5 changed files with 356 additions and 10 deletions

View File

@ -10,7 +10,7 @@ default-members = [".", "members/libpt-core"]
[workspace.package]
publish = true
version = "0.4.2"
version = "0.4.3"
edition = "2021"
authors = ["Christoph J. Scherr <software@cscherr.de>"]
license = "MIT"
@ -30,7 +30,7 @@ anyhow = "1.0.79"
thiserror = "1.0.56"
libpt-core = { version = "0.4.0", path = "members/libpt-core" }
libpt-bintols = { version = "0.4.0", path = "members/libpt-bintols" }
libpt-log = { version = "0.4.1", path = "members/libpt-log" }
libpt-log = { version = "0.4.2", path = "members/libpt-log" }
[package]
name = "libpt"

View File

@ -1,7 +1,7 @@
[package]
name = "libpt-log"
publish.workspace = true
version = "0.4.1"
version = "0.4.2"
edition.workspace = true
authors.workspace = true
license.workspace = true

View File

@ -37,20 +37,346 @@ pub const DEFAULT_LOG_DIR: &str = "/dev/null";
static INITIALIZED: AtomicBool = AtomicBool::new(false);
/// ## Logger for [`pt`](../libpt/index.html)
/// Builder for a well configured [Logger]
///
/// This struct exists mainly for the python module, so that we can use the same logger with both
/// python and rust.
/// This struct helps configure a global logger that can be used with either macros or methods, see
/// [Logger].
///
/// ## Examples
///
/// ```
/// # use libpt_log::{Logger, info};
/// # fn main() {
/// Logger::builder()
/// .uptime(true)
/// .build();
/// info!("hello world");
/// # }
///
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct LoggerBuilder {
/// create and log to logfiles
log_to_file: bool,
/// logfiles would be created here
log_dir: PathBuf,
/// use ANSI control sequences
ansi: bool,
/// show which source file produces a log
display_filename: bool,
/// show the log level of the message
display_level: bool,
/// show target context
display_target: bool,
/// sets the maximum verbosity level.
///
/// For example, if set to [Error](Level::ERROR), logs at [Info](Level::INFO) will not be
/// printed. If set to [Debug](Level::DEBUG), logs at [Info](Level::INFO) will be printed.
max_level: Level,
/// show the id of the thread that created this message
display_thread_ids: bool,
/// show the name of the thread that created this message
display_thread_names: bool,
/// show which line in the source file produces a log
display_line_number: bool,
/// splits a log over multiple lines, looks like a python traceback
pretty: bool,
/// show when the log was created
show_time: bool,
/// show timestamps as uptime (duration since the logger was initialized)
uptime: bool,
}
impl LoggerBuilder {
/// use the configured settings to build and initialize a new global [Logger]
///
/// This will build a functional [Logger]. You don't need to use the [Logger] struct, it's
/// better to use the macros:
///
/// * `error!`
/// * `warn!`
/// * `info!`
/// * `debug!`
/// * `trace!`
///
/// instead of the methods of the [Logger] struct. You can however use the [Logger] struct in
/// cases where usage of a macro is bad or you are somehow working with multiple loggers.
///
/// ## Examples
///
/// ```
/// # use libpt_log::{Logger, info};
/// # fn main() {
/// Logger::builder()
/// .uptime(true)
/// .build();
/// info!("hello world");
/// # }
///
/// ```
/// # Errors
///
/// This function will return an error if a global Logger was aready initialized. This module
/// uses the [tracing] crate for logging, so if a [tracing] logger is initialized elsewhere,
/// this method will error.
pub fn build(self) -> Result<Logger> {
// only init if no init has been performed yet
if INITIALIZED.load(Ordering::Relaxed) {
warn!("trying to reinitialize the logger, ignoring");
bail!(Error::Usage("logging is already initialized".to_string()));
}
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.with_level(self.display_level)
.with_max_level(self.max_level)
.with_ansi(self.ansi)
.with_target(self.display_target)
.with_file(self.display_filename)
.with_thread_ids(self.display_thread_ids)
.with_line_number(self.display_line_number)
.with_thread_names(self.display_thread_names)
.with_span_events(FmtSpan::FULL);
// I know this is hacky, but I couldn't get it any other way. I couldn't even find a
// project that could do it any other way. You can't apply one after another, because the
// type is changed every time. When using `Box<dyn Whatever>`, some methods complain about
// not being in trait bounds.
// TODO: somehow find a better solution for this
match (self.log_to_file, self.show_time, self.pretty, self.uptime) {
(true, true, true, true) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.with_timer(time::uptime())
.pretty()
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, true, true, false) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.pretty()
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, false, true, _) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.without_time()
.pretty()
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, true, false, true) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.with_timer(time::uptime())
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, true, false, false) => {
let subscriber = subscriber
.with_writer(new_file_appender(self.log_dir))
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(true, false, false, _) => {
let file_appender = tracing_appender::rolling::daily(self.log_dir.clone(), "log");
let (file_writer, _guard) = tracing_appender::non_blocking(file_appender);
let subscriber = subscriber.with_writer(file_writer).without_time().finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, true, true) => {
let subscriber = subscriber.pretty().with_timer(time::uptime()).finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, true, false) => {
let subscriber = subscriber.pretty().with_timer(time::uptime()).finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, false, true, _) => {
let subscriber = subscriber.without_time().pretty().finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, false, true) => {
let subscriber = subscriber.with_timer(time::uptime()).finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, true, false, false) => {
let subscriber = subscriber.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
(false, false, false, _) => {
let subscriber = subscriber.without_time().finish();
tracing::subscriber::set_global_default(subscriber)?;
}
}
INITIALIZED.store(true, Ordering::Relaxed);
Ok(Logger {})
}
/// enable or disable logging to and creating of logfiles
pub fn log_to_file(mut self, log_to_file: bool) -> Self {
self.log_to_file = log_to_file;
self
}
/// set a directory where logfiles would be created in
///
/// Enable or disable creation and logging to logfiles with [log_to_file](Self::log_to_file).
///
/// The default logdir is [DEFAULT_LOG_DIR].
pub fn log_dir(mut self, log_dir: PathBuf) -> Self {
self.log_dir = log_dir;
self
}
/// enable or disable ANSI control sequences
///
/// Disabling ANSI control sequences might improve compatibility and readability when the logs
/// are displayed by a program that does not interpret them.
///
/// Keeping ANSI control sequences enabled has the disadvantage of added colors for the logs.
pub fn ansi(mut self, ansi: bool) -> Self {
self.ansi = ansi;
self
}
/// when making a log, display the source file in which a log was crated in
pub fn display_filename(mut self, display_filename: bool) -> Self {
self.display_filename = display_filename;
self
}
/// when making a log, display the log level of the message
pub fn display_level(mut self, display_level: bool) -> Self {
self.display_level = display_level;
self
}
/// show target context
pub fn display_target(mut self, display_target: bool) -> Self {
self.display_target = display_target;
self
}
/// set the maximum verbosity level.
pub fn max_level(mut self, max_level: Level) -> Self {
self.max_level = max_level;
self
}
/// show the id of the thread that created this message
pub fn display_thread_ids(mut self, display_thread_ids: bool) -> Self {
self.display_thread_ids = display_thread_ids;
self
}
/// show the name of the thread that created this message
pub fn display_thread_names(mut self, display_thread_names: bool) -> Self {
self.display_thread_names = display_thread_names;
self
}
/// show which line in the source file produces a log
pub fn display_line_number(mut self, display_line_number: bool) -> Self {
self.display_line_number = display_line_number;
self
}
/// splits a log over multiple lines, looks like a python traceback
pub fn pretty(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
}
/// show a timestamp describing when the log was created
pub fn show_time(mut self, show_time: bool) -> Self {
self.show_time = show_time;
self
}
/// show timestamps as uptime (duration since the logger was initialized)
pub fn uptime(mut self, uptime: bool) -> Self {
self.uptime = uptime;
self
}
}
impl Default for LoggerBuilder {
fn default() -> Self {
Self {
log_to_file: false,
log_dir: PathBuf::from(DEFAULT_LOG_DIR),
ansi: true,
display_filename: false,
display_level: true,
display_target: false,
max_level: DEFAULT_LOG_LEVEL,
display_thread_ids: false,
display_thread_names: false,
display_line_number: false,
pretty: false,
show_time: true,
uptime: false,
}
}
}
/// ## Logger for [`pt`](libpt)
///
/// A logger is generally a functionality that let's you write information from your library or
/// application in a more structured manner than if you just wrote all information to `stdout` or
/// `stderr` with the likes of `println!` or `eprintln!`.
///
/// It offers writing to multiple targets, such as both the terminal and a log file, and allows
/// users to choose the verbosity of the information that gets printed by selecting a
/// [Loglevel](Level).
///
/// ## Levels
///
/// TODO: add levels desc and ascii art
///
/// ## Usage
///
/// You don't need to use the [Logger] struct, it's better to use the macros instead:
///
/// * `error!`
/// * `warn!`
/// * `info!`
/// * `debug!`
/// * `trace!`
///
/// You can however use the [Logger] struct in cases where usage of a macro is bad or
/// you are somehow working with multiple loggers. The macros offer additional functionalities,
/// suck as full `format!` support and context, see [`tracing`], which we use as backend.
///
/// ## Examples
///
/// ```
/// # use libpt_log::{Logger, info};
/// # fn main() {
/// Logger::builder()
/// .uptime(true)
/// .build();
/// info!("hello world");
/// # }
///
/// ```
pub struct Logger;
/// ## Main implementation
impl Logger {
/// Get a new [LoggerBuilder]
pub fn builder() -> LoggerBuilder {
LoggerBuilder::default()
}
/// ## initializes the logger
///
/// Will enable the logger to be used.
///
/// Assumes some defaults, use [`init_customized`](Self::init_customized) for more control
#[deprecated(since = "0.4.1", note = "use Logger::builder() instead")]
pub fn build(log_dir: Option<PathBuf>, max_level: Option<Level>, uptime: bool) -> Result<Self> {
#[allow(deprecated)]
Self::build_customized(
log_dir.is_some(),
log_dir.unwrap_or(PathBuf::from(DEFAULT_LOG_DIR)),
@ -74,7 +400,9 @@ impl Logger {
/// useful in cases with only one sender to the logging framework.
///
/// Assumes some defaults, use [`init_customized`](Self::init_customized) for more control
#[deprecated(since = "0.4.1", note = "use Logger::builder() instead")]
pub fn build_mini(max_level: Option<Level>) -> Result<Self> {
#[allow(deprecated)]
Self::build_customized(
false,
PathBuf::from(DEFAULT_LOG_DIR),
@ -92,11 +420,11 @@ impl Logger {
)
}
// TODO: make the args a struct for easy access
//
/// ## initializes the logger
///
/// Will enable the logger to be used.
#[deprecated(since = "0.4.1", note = "use Logger::builder() instead")]
#[allow(clippy::too_many_arguments)]
pub fn build_customized(
log_to_file: bool,
log_dir: PathBuf,
@ -250,6 +578,14 @@ impl fmt::Debug for Logger {
}
}
impl Default for Logger {
fn default() -> Self {
LoggerBuilder::default()
.build()
.expect("building a Logger failed")
}
}
fn new_file_appender(log_dir: PathBuf) -> NonBlocking {
let file_appender = tracing_appender::rolling::daily(log_dir.clone(), "log");
tracing_appender::non_blocking(file_appender).0

View File

@ -19,7 +19,7 @@ name = "libpt"
crate-type = ["cdylib", "rlib"]
[dependencies]
libpt = { version = "0.4.2", path = "../.." }
libpt = { version = "0.4.3", path = "../.." }
pyo3 = { version = "0.19.0", features = ["full"] }
anyhow.workspace = true

View File

@ -47,7 +47,17 @@ impl Logger {
) -> anyhow::Result<Self> {
// concert our wrapper type
let max_level = max_level.map(origin::Level::from);
Ok(origin::Logger::build(log_dir, max_level, uptime.unwrap_or(false))?.into())
let mut builder = origin::Logger::builder();
if log_dir.is_some() {
builder = builder.log_dir(log_dir.unwrap());
}
if max_level.is_some() {
builder = builder.max_level(max_level.unwrap());
}
if uptime.is_some() {
builder = builder.uptime(uptime.unwrap());
}
Ok(builder.build()?.into())
}
/// ## logging at [`Level::ERROR`]