diff --git a/src/bin/main/mod.rs b/src/bin/main/mod.rs index f9e53ad..8aea29b 100644 --- a/src/bin/main/mod.rs +++ b/src/bin/main/mod.rs @@ -13,14 +13,14 @@ // enable clippy's extra lints, the pedantic version #![warn(clippy::pedantic)] +use std::path::PathBuf; + //// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// -use pt::networking::monitoring::uptime; +use pt::{logger, networking::monitoring::uptime}; // we want the log macros in any case #[allow(unused_imports)] -use log::{debug, error, info, trace, warn}; - -use env_logger; +use tracing::{debug, error, info, trace, warn}; use clap::Parser; @@ -28,6 +28,8 @@ pub mod args; use args::*; //// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// +const EXIT_SUCCESS: i32 = 0; +const EXIT_FAILURE_USAGE: i32 = 1; //// STATICS /////////////////////////////////////////////////////////////////////////////////////// @@ -43,20 +45,46 @@ use args::*; /// ## Main function of the [`pt`](crate) binary pub fn main() { let cli = Cli::parse(); + let ll: tracing::Level = match cli.verbose.log_level().unwrap().as_str() { + "TRACE" => tracing::Level::TRACE, + "DEBUG" => tracing::Level::DEBUG, + "INFO" => tracing::Level::INFO, + "WARN" => tracing::Level::WARN, + "ERROR" => tracing::Level::ERROR, + _ => { + eprintln!("'{}' is not a valid loglevel", cli.verbose.to_string()); + std::process::exit(EXIT_FAILURE_USAGE); + } + }; if cli.log_meta { - // set up our logger to use the given verbosity - env_logger::Builder::new() - .filter_module("pt", cli.verbose.log_level_filter()) - .init(); - } - else { - // set up our logger to use the given verbosity - env_logger::Builder::new() - .filter_module("pt", cli.verbose.log_level_filter()) - .format_level(false) - .format_target(false) - .format_timestamp(None) - .init(); + logger::Logger::init_customized( + false, + PathBuf::from("/dev/null"), + true, + false, + true, + true, + ll, + false, + false, + false, + ) + .expect("could not initialize Logger"); + } else { + // less verbose version + logger::Logger::init_customized( + false, + PathBuf::from("/dev/null"), + true, + false, + true, + false, + ll, + false, + false, + false, + ) + .expect("could not initialize Logger"); } trace!("started the main function"); @@ -76,13 +104,12 @@ pub fn net(cli: &Cli, command: NetCommands) { success_ratio, extra_urls, no_default, - timeout + timeout, } => { let urls: Vec; if no_default { urls = extra_urls; - } - else { + } else { let mut combined: Vec = Vec::new(); for i in uptime::DEFAULT_CHECK_URLS { combined.push(i.to_string()); @@ -94,11 +121,13 @@ pub fn net(cli: &Cli, command: NetCommands) { if repeat > 0 { uptime::continuous_uptime_monitor(success_ratio, urls, repeat * 1000, timeout); } else { - let status = uptime::UptimeStatus::new(success_ratio, urls, timeout); - println!("{}", status); + let status = uptime::UptimeStatus::new(success_ratio, urls, timeout); + info!("status:\n{}", status); } } - NetCommands::Discover {} => {todo!()} + NetCommands::Discover {} => { + todo!() + } } } diff --git a/src/logger/error.rs b/src/logger/error.rs new file mode 100644 index 0000000..ac86f3c --- /dev/null +++ b/src/logger/error.rs @@ -0,0 +1,98 @@ +//! # very short description +//! +//! Short description +//! +//! Details +//! +//! ## Section 1 +//! +//! ## Section 2 + +//// ATTRIBUTES //////////////////////////////////////////////////////////////////////////////////// +// we want docs +#![warn(missing_docs)] +#![warn(rustdoc::missing_crate_level_docs)] +// we want Debug everywhere. +#![warn(missing_debug_implementations)] +// enable clippy's extra lints, the pedantic version +#![warn(clippy::pedantic)] + +//// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// +use pyo3::{exceptions::PyException, PyErr}; +use tracing::subscriber::SetGlobalDefaultError; + +//// TYPES ///////////////////////////////////////////////////////////////////////////////////////// +/// a quick alias for a result with a [`LoggerError`] +pub type Result = std::result::Result; + +//// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// + +//// STATICS /////////////////////////////////////////////////////////////////////////////////////// + +//// MACROS //////////////////////////////////////////////////////////////////////////////////////// + +//// ENUMS ///////////////////////////////////////////////////////////////////////////////////////// +/// ## Errors for the [logger](crate::logger) +pub enum Error { + /// Bad IO operation + IO(std::io::Error), + /// Various errors raised when the messenger is used in a wrong way + Usage(String), + SetGlobalDefaultFail(SetGlobalDefaultError), +} + +//// STRUCTS /////////////////////////////////////////////////////////////////////////////////////// + +//// IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////// +impl From for Error { + fn from(value: std::io::Error) -> Self { + Error::IO(value) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +impl From for Error { + fn from(value: SetGlobalDefaultError) -> Self { + Error::SetGlobalDefaultFail(value) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +impl Into for Error { + fn into(self) -> PyErr { + match self { + Error::IO(err) => PyException::new_err(format!("LoggerError: IO {err:?}")), + Error::Usage(err) => PyException::new_err(format!("LoggerError: Usage {err}")), + Error::SetGlobalDefaultFail(err) => { + PyException::new_err(format!("LoggerError: SetGlobalDefaultFail {err}")) + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::IO(e) => write!(f, ""), + Error::Usage(e) => write!(f, ""), + Error::SetGlobalDefaultFail(e) => write!(f, ""), + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::IO(e) => write!(f, "IO Error {e}"), + Error::Usage(e) => write!(f, "Usage Error {e}"), + Error::SetGlobalDefaultFail(e) => write!(f, "SetGlobalDefaultFail {e}"), + } + } +} + +//// PUBLIC FUNCTIONS ////////////////////////////////////////////////////////////////////////////// + +//// PRIVATE FUNCTIONS ///////////////////////////////////////////////////////////////////////////// + diff --git a/src/logger/mod.rs b/src/logger/mod.rs index e3878bc..1e51da2 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -15,18 +15,21 @@ //// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// use std::{ fmt, + path::PathBuf, sync::atomic::{AtomicBool, Ordering}, }; -use env_logger::{Env, Target, WriteStyle}; -use log::{debug, error, info, trace, warn, Level}; +pub mod error; +use error::*; + +pub use tracing::{debug, error, info, trace, warn, Level}; +use tracing_appender; +use tracing_subscriber::prelude::*; use pyo3::prelude::*; //// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// /// The log level used when none is specified -pub const DEFAULT_LOG_LEVEL: Level = Level::Info; -/// Register your level to this environment variable to override the used level -pub const LOGGER_ENV_KEY: &'static str = "LIBPT_LOGLEVEL"; +pub const DEFAULT_LOG_LEVEL: Level = Level::INFO; //// STATICS /////////////////////////////////////////////////////////////////////////////////////// static INITIALIZED: AtomicBool = AtomicBool::new(false); @@ -63,50 +66,68 @@ impl Logger { /// ## initializes the logger /// /// Will enable the logger to be used. - pub fn init() { + /// + /// Assumes some defaults, use [`init_customized`](init_customized) for more control + pub fn init() -> Result<()> { + Self::init_customized( + false, + PathBuf::from("/dev/null"), + true, + false, + true, + false, + Level::INFO, + false, + false, + false, + ) + } + + /// ## initializes the logger + /// + /// Will enable the logger to be used. + pub fn init_customized( + log_to_file: bool, + log_dir: PathBuf, + ansi: bool, + display_filename: bool, + display_level: bool, + display_target: bool, + max_level: Level, + display_thread_ids: bool, + display_thread_names: bool, + display_line_number: bool, + ) -> Result<()> { // only init if no init has been performed yet if INITIALIZED.load(Ordering::Relaxed) { warn!("trying to reinitialize the logger, ignoring"); - return; + return Err(Error::Usage(format!("logging is already initialized"))); } else { - let env = Env::default().filter_or(LOGGER_ENV_KEY, DEFAULT_LOG_LEVEL.to_string()); - let res = env_logger::Builder::from_env(env) - .try_init(); - if res.is_err() { - eprintln!("could not init logger: {}", res.unwrap_err()); - } - INITIALIZED.store(true, Ordering::Relaxed); - } - } + let basic_subscriber = tracing_subscriber::fmt::Subscriber::builder() + // subscriber configuration + .with_ansi(ansi) + .with_file(display_filename) + .with_level(display_level) + .with_target(display_target) + .with_max_level(max_level) + .with_thread_ids(display_thread_ids) + .with_line_number(display_line_number) + .with_thread_names(display_thread_names) + //.pretty // too verbose and over multiple lines, a bit like python tracebacks + .finish(); - /// ## initializes the logger to log to a target - /// - /// Will enable the logger to be used. - pub fn init_specialized(show_module: bool, test: bool, color: bool, target: Option) { - let target = match target { - Some(t) => t, - None => Target::Stdout, - }; - // only init if no init has been performed yet - if INITIALIZED.load(Ordering::Relaxed) { - eprintln!("trying to reinitialize the logger, ignoring"); - return; - } else { - let env = Env::default().filter_or(LOGGER_ENV_KEY, DEFAULT_LOG_LEVEL.to_string()); - let res = env_logger::Builder::from_env(env) - .is_test(test) - .target(target) - .write_style(if color { - WriteStyle::Auto - } else { - WriteStyle::Never - }) - .format_target(show_module) - .try_init(); - if res.is_err() { - eprintln!("could not init logger: {}", res.unwrap_err()); + if log_to_file { + let file_appender = tracing_appender::rolling::daily(log_dir, "log"); + let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); + let layered_subscriber = basic_subscriber + .with(tracing_subscriber::fmt::Layer::default().with_writer(file_writer)); + tracing::subscriber::set_global_default(layered_subscriber)?; + } else { + tracing::subscriber::set_global_default(basic_subscriber)?; } + INITIALIZED.store(true, Ordering::Relaxed); + Ok(()) } } @@ -159,14 +180,8 @@ impl Logger { /// ## Python version of [`init()`](Logger::init) #[pyo3(name = "init")] #[staticmethod] - pub fn py_init() { - Self::init_specialized(false, false, true, None) - } - /// ## Python version of [`init_specialized()`](Logger::init_specialized) - #[pyo3(name = "init_specialized")] - #[staticmethod] - pub fn py_init_specialized(color: bool) { - Self::init_specialized(false, false, color, None) + pub fn py_init() -> Result<()> { + Self::init() } /// ## Python version of [`error()`](Logger::error) #[pyo3(name = "error")] diff --git a/src/networking/monitoring/uptime.rs b/src/networking/monitoring/uptime.rs index 72d7b77..f2097b0 100644 --- a/src/networking/monitoring/uptime.rs +++ b/src/networking/monitoring/uptime.rs @@ -21,7 +21,7 @@ use std::{fmt, time::Duration}; //// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// // we want the log macros in any case #[allow(unused_imports)] -use log::{debug, error, info, trace, warn}; +use tracing; use reqwest; @@ -142,10 +142,10 @@ impl UptimeStatus { return; } let ratio: f32 = (self.reachable as f32) / (self.urls.len() as f32) * 100f32; - trace!("calculated success_ratio: {}", ratio); + tracing::trace!("calculated success_ratio: {}", ratio); self.success_ratio = ratio.floor() as u8; self.success = self.success_ratio >= self.success_ratio_target; - trace!("calculated success as: {}", self.success) + tracing::trace!("calculated success as: {}", self.success) } } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -218,7 +218,7 @@ pub fn continuous_uptime_monitor( timeout: u64, ) { if urls.len() == 0 { - error!("No URLs provided. There is nothing to monitor."); + tracing::error!("No URLs provided. There is nothing to monitor."); return; } @@ -293,19 +293,19 @@ fn display_uptime_status( ) { // I know it's weird that this has two spaces too much, but somehow just the tabs is missing // two spaces. - info!("uptime check: {}", msg); - info!("last uptime: {}", match_format_time(last_uptime)); - info!("last downtime: {}", match_format_time(last_downtime)); - info!( + tracing::info!("uptime check: {}", msg); + tracing::info!("last uptime: {}", match_format_time(last_uptime)); + tracing::info!("last downtime: {}", match_format_time(last_downtime)); + tracing::info!( "since downtime: {}", match_format_duration_since(last_downtime) ); - info!( + tracing::info!( "since uptime: {}", match_format_duration_since(last_uptime) ); - debug!("\n{}", status); - info!("{}", divider!()); + tracing::debug!("\n{}", status); + tracing::info!("{}", divider!()); } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tests/test_logger_struct.rs b/tests/test_logger_struct.rs index a635c40..6b5f3a0 100644 --- a/tests/test_logger_struct.rs +++ b/tests/test_logger_struct.rs @@ -1,21 +1,37 @@ //! # Tests for pt::logger::Logger //! -//! Note: the module uses a global variable to store if the thread has +//! Note: the module uses a global variable to store if the thread has //// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// +use pt::common::macros::get_stdout_for; /// ## Tests for basic logging functionality use pt::logger::*; -use pt::common::macros::get_stdout_for; use regex::Regex; +use std::sync::Once; + //// HELPERS /////////////////////////////////////////////////////////////////////////////////////// +static SETUP: Once = Once::new(); // only initialize once /// ## setup that's needed before testing the logger struct fn setup() { - // we don't want to log messages during our tests! - std::env::set_var(LOGGER_ENV_KEY, "Trace"); - Logger::init_specialized(true, false, false, None); - println!() + SETUP.call_once(|| { + // we don't want to log messages during our tests! + Logger::init_customized( + false, + std::path::PathBuf::from("/dev/null"), + false, + false, + true, + false, + tracing::Level::TRACE, + false, + false, + false, + ) + .expect("could not initialize Logger"); + println!() + }); } //// IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////// @@ -51,8 +67,7 @@ fn test_log_basic() { // this matches the format of the env_logger perfectly, but make sure that color is off, // else the ANSI escape sequences break this test let regex = Regex::new(concat!( - r"(?m)\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z ", - r"(TRACE|DEBUG|INFO|WARN|ERROR) +pt::logger\] MSG" + r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z\s+(TRACE|DEBUG|INFO|WARN|ERROR)\sMSG" )) .unwrap(); @@ -65,9 +80,9 @@ fn test_multi_initialize() { setup(); let l = Logger::new(); // these should be ignored due to the global flag - Logger::init(); - Logger::init(); - Logger::init(); - Logger::init(); + Logger::init().unwrap_err(); + Logger::init().unwrap_err(); + Logger::init().unwrap_err(); + Logger::init().unwrap_err(); l.info("Successfully ignored extra init"); }