diff --git a/Cargo.toml b/Cargo.toml index dac060b..e7e5cd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ clap-num = "1.0.2" clap-verbosity-flag = "2.0.1" env_logger = "0.10.0" gag = "1.0.0" +humantime = "2.1.0" log = { version = "0.4.19", features = ["max_level_trace", "release_max_level_trace"] } num = "0.4.0" pyo3 = "0.18.1" diff --git a/src/bin/main/args.rs b/src/bin/main/args.rs index 8f0313f..c109a60 100644 --- a/src/bin/main/args.rs +++ b/src/bin/main/args.rs @@ -19,7 +19,7 @@ use clap::{Parser, Subcommand}; use clap_num::number_range; -use clap_verbosity_flag::Verbosity; +use clap_verbosity_flag::{Verbosity, InfoLevel}; //// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// /// short about section displayed in help @@ -51,7 +51,11 @@ r#"libpt: {version}{about-section}Author: pub struct Cli { /// set a verbosity, multiple allowed (f.e. -vvv) #[command(flatten)] - pub verbose: Verbosity, + pub verbose: Verbosity, + + /// show logger meta + #[arg(short, long)] + pub log_meta: bool, /// choose a subcommand #[command(subcommand)] diff --git a/src/bin/main/mod.rs b/src/bin/main/mod.rs index 278a96d..dfbed19 100644 --- a/src/bin/main/mod.rs +++ b/src/bin/main/mod.rs @@ -47,10 +47,21 @@ fn main() { std::env::set_var(logger::LOGGER_ENV_KEY, "trace"); let cli = Cli::parse(); - // set up our logger to use the given verbosity - env_logger::Builder::new() - .filter_level(cli.verbose.log_level_filter()) - .init(); + if cli.log_meta { + // set up our logger to use the given verbosity + env_logger::Builder::new() + .filter_module("libpt", cli.verbose.log_level_filter()) + .init(); + } + else { + // set up our logger to use the given verbosity + env_logger::Builder::new() + .filter_module("libpt", cli.verbose.log_level_filter()) + .format_level(false) + .format_target(false) + .format_timestamp(None) + .init(); + } trace!("started the main function"); trace!("{:?}", &cli); @@ -84,12 +95,7 @@ fn net(cli: &Cli, command: NetCommands) { } let _verbose = cli.verbose.log_level().is_some(); if repeat > 0 { - loop { - let status = uptime::UptimeStatus::new(success_ratio, &urls); - println!("{}", status); - std::thread::sleep(std::time::Duration::from_secs(repeat)); - - } + uptime::continuous_uptime_monitor(success_ratio, urls, repeat * 1000); } else { let status = uptime::UptimeStatus::new(success_ratio, &urls); println!("{}", status); diff --git a/src/common/mod.rs b/src/common/mod.rs index c2af571..2c1382e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -17,7 +17,9 @@ #![warn(clippy::pedantic)] //// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// +/// macros to make things faster in your code pub mod macros; +/// some general use printing to stdout tools pub mod printing; //// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// diff --git a/src/common/printing.rs b/src/common/printing.rs index e69de29..56d8163 100644 --- a/src/common/printing.rs +++ b/src/common/printing.rs @@ -0,0 +1,48 @@ +//! # tools that make printing stuff better + +//// 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 /////////////////////////////////////////////////////////////////////////////////////// +// reimport our macros to this module, so the user does not get confused when importing the macros +pub use crate::get_stdout_for; + +//// TYPES ///////////////////////////////////////////////////////////////////////////////////////// + +//// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// + +//// STATICS /////////////////////////////////////////////////////////////////////////////////////// + +//// MACROS //////////////////////////////////////////////////////////////////////////////////////// +/// Quickly get a one line visual divider +#[macro_export] +macro_rules! divider { + () => {{ + format!("{:=^80}", "=") + }}; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// Quickly print a one line visual divider +#[macro_export] +macro_rules! println_divider { + () => {{ + println!("{}", divider!()) + }}; +} + +//// ENUMS ///////////////////////////////////////////////////////////////////////////////////////// + +//// STRUCTS /////////////////////////////////////////////////////////////////////////////////////// + +//// IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////// + +//// PUBLIC FUNCTIONS ////////////////////////////////////////////////////////////////////////////// + +//// PRIVATE FUNCTIONS ///////////////////////////////////////////////////////////////////////////// diff --git a/src/networking/monitoring/uptime.rs b/src/networking/monitoring/uptime.rs index 43c48f1..ca723f6 100644 --- a/src/networking/monitoring/uptime.rs +++ b/src/networking/monitoring/uptime.rs @@ -1,7 +1,7 @@ //! # monitor your network uptime //! //! This method offers a way to monitor your networks/hosts uptime. This is achieved by making -//! https requests to a given list of +//! HTTPS requests to a given list of //// ATTRIBUTES //////////////////////////////////////////////////////////////////////////////////// // we want docs @@ -14,7 +14,7 @@ // enable clippy's extra lints, the pedantic version #![warn(clippy::pedantic)] -use std::{fmt, str::FromStr}; +use std::{fmt, str::FromStr, time::Duration}; //// IMPORTS /////////////////////////////////////////////////////////////////////////////////////// // we want the log macros in any case @@ -23,6 +23,11 @@ use log::{debug, error, info, trace, warn}; use reqwest::{self, Url}; +use humantime::{format_duration, format_rfc3339}; +use std::time::SystemTime; + +use crate::divider; + //// TYPES ///////////////////////////////////////////////////////////////////////////////////////// //// CONSTANTS ///////////////////////////////////////////////////////////////////////////////////// @@ -73,6 +78,7 @@ impl UptimeStatus { warn!("Invalid URL: '{}", s); } } + status.urls.dedup(); status.check(); @@ -85,7 +91,11 @@ impl UptimeStatus { pub fn check(&mut self) { self.reachable = 0; self.urls.iter().for_each(|url| { - let response = reqwest::blocking::get(url.clone()); + let client = reqwest::blocking::Client::builder() + .timeout(Duration::from_millis(2000)) + .build() + .expect("could not build a client for https requests"); + let response = client.get(url.clone()).send(); if response.is_ok() { self.reachable += 1 } @@ -117,9 +127,10 @@ impl UptimeStatus { return; } let ratio: f32 = (self.reachable as f32) / (self.urls.len() as f32) * 100f32; - debug!("calculated success_ratio: {}", ratio); + 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) } } @@ -160,5 +171,103 @@ impl fmt::Display for UptimeStatus { } //// PUBLIC FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/// ## Uptime monitor +/// +/// This function continuously monitors the uptime of your host/network +pub fn continuous_uptime_monitor(success_ratio_target: u8, urls: Vec, interval: u64) { + if urls.len() == 0 { + error!("No URLs provided. There is nothing to monitor."); + return; + } + + let interval = std::time::Duration::from_millis(interval); + let mut last_downtime: Option = None; + let mut last_uptime: Option = None; + let mut status = UptimeStatus::new(success_ratio_target, &urls); + let mut last_was_up: bool = false; + let mut last_ratio: u8 = status.success_ratio; + loop { + if !status.success { + if last_was_up { + display_uptime_status("fail", last_uptime, last_downtime, &status) + } + last_downtime = Some(SystemTime::now()); + last_was_up = false; + } else if status.success_ratio < 100 { + if status.success_ratio != last_ratio { + let msg = format!( + "uptime check: not all urls are reachable ({}%)", + status.success_ratio + ); + display_uptime_status(&msg, last_uptime, last_downtime, &status) + } + last_uptime = Some(SystemTime::now()); + last_was_up = true; + } else { + if !last_was_up { + display_uptime_status("success", last_uptime, last_downtime, &status) + } + last_uptime = Some(SystemTime::now()); + last_was_up = true; + } + + last_ratio = status.success_ratio; + std::thread::sleep(interval); + status.check(); + } +} //// PRIVATE FUNCTIONS ///////////////////////////////////////////////////////////////////////////// +fn display_uptime_status( + msg: &str, + last_uptime: Option, + last_downtime: Option, + status: &UptimeStatus, +) { + // 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!( + "since downtime: {}", + match_format_duration_since(last_downtime) + ); + info!( + "since uptime: {}", + match_format_duration_since(last_uptime) + ); + debug!("\n{}", status); + info!("{}", divider!()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// Returns "None" if the given [Option] is [None](Option::None). Otherwise, returns the time stamp +/// formatted according to rfc3999. +fn match_format_time(time: Option) -> String { + match time { + Some(time) => format_rfc3339(time).to_string(), + None => String::from("None"), + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// Returns "None" if the given [Option] is [None](Option::None). Otherwise, returns duration since +/// that time in a human readable format. +fn match_format_duration_since(time: Option) -> String { + match time { + Some(time) => format_duration( + SystemTime::now() + .duration_since(time) + .expect("could not calculate elapsed time"), + ) + .to_string(), + None => String::from("None"), + } +}