Logging #26
@ -1,3 +1,5 @@
|
|||||||
|
workspace = { members = ["config", "logging"] }
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "wanessa"
|
name = "wanessa"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -9,12 +11,13 @@ edition = "2021"
|
|||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unsafe_code = "forbid"
|
unsafe_code = "forbid"
|
||||||
|
missing_docs = "warn"
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
enum_glob_use = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/enum_glob_use
|
enum_glob_use = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/enum_glob_use
|
||||||
pedantic = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=pedantic
|
pedantic = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=pedantic
|
||||||
nursery = "deny" # wip lints: https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=nursery
|
nursery = "deny" # wip lints: https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=nursery
|
||||||
unwrap_used = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/unwrap_used
|
unwrap_used = "forbid" # https://rust-lang.github.io/rust-clippy/master/index.html#/unwrap_used
|
||||||
missing_const_for_fn = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_const_for_fn
|
missing_const_for_fn = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_const_for_fn
|
||||||
missing_assert_message = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
missing_assert_message = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
||||||
missing_errors_doc = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
missing_errors_doc = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
||||||
|
23
config/Cargo.toml
Normal file
23
config/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "config"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
getset = { version = "0.1.2", default-features = false }
|
||||||
|
once_cell = { version = "1.19.0", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unsafe_code = "forbid"
|
||||||
|
missing_docs = "warn"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
enum_glob_use = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/enum_glob_use
|
||||||
|
pedantic = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=pedantic
|
||||||
|
nursery = "deny" # wip lints: https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=nursery
|
||||||
|
unwrap_used = "forbid" # https://rust-lang.github.io/rust-clippy/master/index.html#/unwrap_used
|
||||||
|
missing_const_for_fn = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_const_for_fn
|
||||||
|
missing_assert_message = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
||||||
|
missing_errors_doc = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
57
config/src/db.rs
Normal file
57
config/src/db.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*!
|
||||||
|
* This module contains everything related to [`DbConfig`].
|
||||||
|
*/
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
|
||||||
|
use getset::Getters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A immutable record of all the information needed to connect to the SQL database.
|
||||||
|
*/
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Getters, Debug)]
|
||||||
|
#[getset(get = "pub")]
|
||||||
|
pub struct DbConfig {
|
||||||
|
/// Database connection address.
|
||||||
|
/// Is an option to allow constructing a default config during compile time.<br>
|
||||||
|
addr: String,
|
||||||
|
/// Database connection port.<br>
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DbConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
addr: String::from("localhost"),
|
||||||
|
port: 6969,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for [`DbConfig`].
|
||||||
|
*/
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct DbConfigBuilder(DbConfig);
|
||||||
|
|
||||||
|
impl DbConfigBuilder {
|
||||||
|
/// Get a new [`DbConfigBuilder`]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the address to the location of the database.
|
||||||
|
#[must_use]
|
||||||
|
pub fn set_addr(mut self, addr: impl Into<String>) -> Self {
|
||||||
|
self.0.addr = addr.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the port to the port the database uses.
|
||||||
|
#[must_use]
|
||||||
|
pub fn set_port(mut self, port: impl Into<u16>) -> Self {
|
||||||
|
self.0.port = port.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
31
config/src/lib.rs
Normal file
31
config/src/lib.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*!
|
||||||
|
* Containing all singleton and thread-safe structs related to configuring `WANessa`.
|
||||||
|
*
|
||||||
|
* This crate differentiates between `Configs` and `Settings`:
|
||||||
|
* Configs:
|
||||||
|
* - Configs are immutable after they have been initialized. Example [`DbConfig`].
|
||||||
|
* - Changing the config requires a restart of `WANessa`.
|
||||||
|
*
|
||||||
|
* Settings:
|
||||||
|
* - Settings are mutable after they have been initialized. Example [`LogSettings`].
|
||||||
|
* - `WANessa` always uses the current setting and does not need a restart to apply new settings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod db;
|
||||||
|
pub mod log;
|
||||||
|
|
||||||
|
use crate::db::DbConfig;
|
||||||
|
use crate::log::LogSettings;
|
||||||
|
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
// TODO: replace default with parsed settings from files+flags
|
||||||
|
|
||||||
|
/// Singelton [`DbConfig`].
|
||||||
|
pub static DB_CONFIG: Lazy<Arc<DbConfig>> = Lazy::new(|| Arc::new(DbConfig::default()));
|
||||||
|
|
||||||
|
/// Singelton [`LogSettings`].
|
||||||
|
pub static LOG_SETTINGS: Lazy<RwLock<LogSettings>> =
|
||||||
|
Lazy::new(|| RwLock::new(LogSettings::default()));
|
98
config/src/log.rs
Normal file
98
config/src/log.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*!
|
||||||
|
* This module contains everything related to [`LogSettings`].
|
||||||
|
*/
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::log::LogVerbosity::Warning;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use getset::Getters;
|
||||||
|
use getset::Setters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All settings relating to how the project logs information.
|
||||||
|
*/
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Getters, Setters, Debug)]
|
||||||
|
#[getset(get = "pub", set = "pub")]
|
||||||
|
pub struct LogSettings {
|
||||||
|
/// See [`LogVerbosity`].<br>
|
||||||
|
verbosity: LogVerbosity,
|
||||||
|
/// Logs UTC time and date of message, if true.<br>
|
||||||
|
time: bool,
|
||||||
|
/// Time and date format.<br>
|
||||||
|
/// See [chrono](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).<br>
|
||||||
|
time_format: String,
|
||||||
|
/// Logs location in code, where the message was logged, if true.<br>
|
||||||
|
location: bool,
|
||||||
|
/// If `Some(path)` tries to also write the log to `path` in addition to stderr/stderr.<br>
|
||||||
|
#[getset(skip)]
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
/// Logs to standard out, if true.<br>
|
||||||
|
stdout: bool,
|
||||||
|
/// Logs to standard err, if true.<br>
|
||||||
|
stderr: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogSettings {
|
||||||
|
/// Setter for log path, including syntactic sugar for the [Option] enum.
|
||||||
|
pub fn set_path(&mut self, path: impl Into<Option<PathBuf>>) {
|
||||||
|
self.path = path.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getter for the log path.
|
||||||
|
#[must_use]
|
||||||
|
pub fn path(&self) -> Option<&Path> {
|
||||||
|
self.path.as_deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LogSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
verbosity: Warning,
|
||||||
|
time: false,
|
||||||
|
time_format: String::from("%F-%T:%f"),
|
||||||
|
location: false,
|
||||||
|
stdout: true,
|
||||||
|
stderr: true,
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each level includes the previous ones.
|
||||||
|
*/
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum LogVerbosity {
|
||||||
|
/// Critical Errors, may lead to crashes or deactivating certain features.
|
||||||
|
Error = 10,
|
||||||
|
/// Very minor and recovered errors, such as invalid configs.
|
||||||
|
Warning = 20,
|
||||||
|
/// Very verbose and detailed. Basically gives a step-by-step instruction on what is currently done.
|
||||||
|
Information = 30,
|
||||||
|
/// Very technical and even more verbose.
|
||||||
|
/// May contain secrets and private information.
|
||||||
|
/// **Do not use in production environments!**
|
||||||
|
Debugging = 40,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for LogVerbosity {
|
||||||
|
/// Some operator overloading of comparison symbols (==, <,>=, etc.) as syntactic sugar.
|
||||||
|
/// See [`PartialOrd`].
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for LogVerbosity {
|
||||||
|
/// Some operator overloading of comparison symbols (==, <,>=, etc.) as syntactic sugar.
|
||||||
|
/// See [`Ord`].
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
(*self as usize).cmp(&(*other as usize))
|
||||||
|
}
|
||||||
|
}
|
28
logging/Cargo.toml
Normal file
28
logging/Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "logging"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { version = "0.4.34", default-features = false, features = ["now"] }
|
||||||
|
config = { path = "../config"}
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
uuid = { version = "1.7.0", default-features = false, features = ["v4", "fast-rng"] }
|
||||||
|
once_cell = { version = "1.19.0", default-features = false, features = ["std"] }
|
||||||
|
futures = { version = "0.3.30", default-features = false, features = ["alloc", "executor"] }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unsafe_code = "forbid"
|
||||||
|
missing_docs = "warn"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
enum_glob_use = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/enum_glob_use
|
||||||
|
pedantic = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=pedantic
|
||||||
|
nursery = "deny" # wip lints: https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=nursery
|
||||||
|
unwrap_used = "forbid" # https://rust-lang.github.io/rust-clippy/master/index.html#/unwrap_used
|
||||||
|
missing_const_for_fn = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_const_for_fn
|
||||||
|
missing_assert_message = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
||||||
|
missing_errors_doc = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
|
195
logging/src/lib.rs
Normal file
195
logging/src/lib.rs
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
/*!
|
||||||
|
* This module handles logging messages asynchronously and is thread-safe.
|
||||||
|
* It should mostly be called statically using the [`log!`] macro.
|
||||||
|
*/
|
||||||
|
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use config::log::LogSettings;
|
||||||
|
use config::log::LogVerbosity;
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
use LogVerbosity::Information;
|
||||||
|
use LogVerbosity::Warning;
|
||||||
|
|
||||||
|
use LogMessageType::GenericDebug;
|
||||||
|
use LogMessageType::GenericErr;
|
||||||
|
use LogMessageType::GenericInfo;
|
||||||
|
use LogMessageType::GenericWarn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the given message.
|
||||||
|
*/
|
||||||
|
pub fn log_message(
|
||||||
|
msg: &LogMessage,
|
||||||
|
conf: &LogSettings,
|
||||||
|
file: &str,
|
||||||
|
line: u32,
|
||||||
|
column: u32,
|
||||||
|
) -> Result<(), io::Error> {
|
||||||
|
// Check if message may be logged according to config.
|
||||||
|
let Some(log_line) = log_to_str(msg, conf, file, line, column) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Log to file
|
||||||
|
match conf.path().as_ref() {
|
||||||
|
None => { /* Do not log to file */ }
|
||||||
|
Some(p) => {
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(p);
|
||||||
|
let mut file = match file {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
match writeln!(file, "{log_line}") {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if msg.1 <= Warning && *conf.stderr() {
|
||||||
|
let mut stdout = io::stdout().lock();
|
||||||
|
return writeln!(stdout, "{log_line}");
|
||||||
|
} else if msg.1 >= Information && *conf.stdout() {
|
||||||
|
let mut stderr = io::stderr().lock();
|
||||||
|
return writeln!(stderr, "{log_line}");
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return log line, if message may be logged according to [`config::log::LogSettings`].
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn log_to_str(
|
||||||
|
msg: &LogMessage,
|
||||||
|
conf: &LogSettings,
|
||||||
|
file: &str,
|
||||||
|
line: u32,
|
||||||
|
column: u32,
|
||||||
|
) -> Option<String> {
|
||||||
|
if conf.verbosity() < &msg.1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut log_line = String::new();
|
||||||
|
|
||||||
|
// add time substring
|
||||||
|
if *conf.time() {
|
||||||
|
log_line += &format!("{} ", Utc::now().format(conf.time_format()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add code location substring
|
||||||
|
if *conf.location() {
|
||||||
|
log_line += &format!("{file}:{line},{column} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(log_line + &msg.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand version for [`log_message`], which does not require information about where in the code
|
||||||
|
* the command was called from.
|
||||||
|
* Tries to aqcuire a read lock, if a reference to an instance of [`config::log::LogSettings`] is
|
||||||
|
* not given.
|
||||||
|
*/
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log {
|
||||||
|
($msg:expr) => {
|
||||||
|
let conf = config::LOG_SETTINGS
|
||||||
|
.read()
|
||||||
|
.unwrap_or_else(|_| panic!("Failed aqcuire read lock on config!"));
|
||||||
|
let res = log_message($msg, &*conf, file!(), line!(), column!());
|
||||||
|
drop(conf);
|
||||||
|
res
|
||||||
|
};
|
||||||
|
($msg:expr, $config:expr) => {
|
||||||
|
log_message($msg, $config, file!(), line!(), column!())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A named typle assigning a log [`LogVerbosity`] to a [`LogMessageType`].
|
||||||
|
*/
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct LogMessage(LogMessageType, LogVerbosity);
|
||||||
|
|
||||||
|
impl Display for LogMessage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
self.0.fmt(f) // just display LogMessageType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Evaluate replacing the following with a trait, to decouple this component from the rest.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every possible Message, which may be logged. Grouped the following:
|
||||||
|
*
|
||||||
|
* These groups correspond to the four levels of logging verbosity. See [`LogVerbosity`].
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum LogMessageType {
|
||||||
|
/// Errors
|
||||||
|
/// An error of any type; please avoid using this.
|
||||||
|
GenericErr(String),
|
||||||
|
/// Warnings
|
||||||
|
/// A warning of any type; please avoid using this.
|
||||||
|
GenericWarn(String),
|
||||||
|
/// Info
|
||||||
|
/// Some information of any type; please avoid using this.
|
||||||
|
GenericInfo(String),
|
||||||
|
/// Debug
|
||||||
|
/// a debug message of any type; please avoid using this.
|
||||||
|
GenericDebug(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Please don't put anything other except new match arms below this comment, since it is expected that
|
||||||
|
* the following code will have a lot of lines, due to exhausting all possible enum values
|
||||||
|
* respectively and (hopefully) detailed messages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
impl LogMessageType {
|
||||||
|
/// Returns a new [`LogMessage`] based on the default verbosity of the given [`LogMessageType`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn log_message(&self) -> LogMessage {
|
||||||
|
let verbosity = match self {
|
||||||
|
GenericErr(_) => LogVerbosity::Error,
|
||||||
|
GenericWarn(_) => LogVerbosity::Warning,
|
||||||
|
GenericInfo(_) => LogVerbosity::Information,
|
||||||
|
GenericDebug(_) => LogVerbosity::Debugging,
|
||||||
|
};
|
||||||
|
|
||||||
|
LogMessage(self.clone(), verbosity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LogMessageType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
match self {
|
||||||
|
GenericErr(err) => {
|
||||||
|
write!(f, "Generic Error: {err}")
|
||||||
|
}
|
||||||
|
GenericWarn(warn) => {
|
||||||
|
write!(f, "Generic Warning: {warn}")
|
||||||
|
}
|
||||||
|
GenericInfo(info) => {
|
||||||
|
write!(f, "Generic Information: {info}")
|
||||||
|
}
|
||||||
|
GenericDebug(debug) => {
|
||||||
|
write!(f, "Generic Debug Message: {debug}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
303
logging/src/test.rs
Normal file
303
logging/src/test.rs
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
/*!
|
||||||
|
* This test suite uses uuid to easily avoid race conditions when writing to the same log file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
env,
|
||||||
|
fs::{create_dir_all, read_to_string},
|
||||||
|
mem::take,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::{executor::block_on, future::join_all};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const CONCURRENT_MESSAGE_COUNT: usize = 99999;
|
||||||
|
|
||||||
|
static LOG_DIR: Lazy<String> = Lazy::new(|| {
|
||||||
|
if cfg!(unix) {
|
||||||
|
String::from("/tmp/WANessa/unit-tests/logging")
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
let tmp_path = env::var("TMP").unwrap_or_else(|_| {
|
||||||
|
env::var("TEMP").expect(
|
||||||
|
"Windows should have both TMP and TEMP, but you have neither, what did you do?",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
format!("{tmp_path}/WANessa/unit-tests/logging")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Tests if the macro logs properly with a given config.
|
||||||
|
#[test]
|
||||||
|
pub fn log_macro_given_config() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let message = LogMessageType::GenericWarn(String::from("Test Log")).log_message();
|
||||||
|
let mut config = config::log::LogSettings::default();
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
|
||||||
|
create_dir_all(PathBuf::from(LOG_DIR.as_str()))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not create directory: {}", *LOG_DIR));
|
||||||
|
|
||||||
|
log!(&message, &config);
|
||||||
|
|
||||||
|
let log_file = read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"));
|
||||||
|
|
||||||
|
assert_eq!(message.to_string() + "\n", log_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests if [`log_message`] to file correctly.
|
||||||
|
#[test]
|
||||||
|
pub fn log_msg_file() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let message = LogMessageType::GenericWarn(String::from("Test Log")).log_message();
|
||||||
|
let mut config = config::log::LogSettings::default();
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
|
||||||
|
create_dir_all(PathBuf::from(LOG_DIR.as_str()))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not create directory: {}", *LOG_DIR));
|
||||||
|
|
||||||
|
log!(&message, &config);
|
||||||
|
|
||||||
|
let log_file = read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"));
|
||||||
|
|
||||||
|
assert_eq!(message.to_string() + "\n", log_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests if [`log_message`] does not modify output from [`log_to_str`], when logging to file.
|
||||||
|
#[test]
|
||||||
|
pub fn log_str() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let message = LogMessageType::GenericWarn(String::from("Test Log")).log_message();
|
||||||
|
let mut config = config::log::LogSettings::default();
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
|
||||||
|
create_dir_all(PathBuf::from(LOG_DIR.as_str()))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not create directory: {}", *LOG_DIR));
|
||||||
|
|
||||||
|
let log_line = log_to_str(&message, &config, file!(), line!(), column!())
|
||||||
|
.expect("There should be a log line.")
|
||||||
|
+ "\n";
|
||||||
|
|
||||||
|
log!(&message, &config);
|
||||||
|
|
||||||
|
let log_file = read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"));
|
||||||
|
|
||||||
|
assert_eq!(log_line, log_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests if no messages are unintentionally filtered due to their verboisity.
|
||||||
|
#[test]
|
||||||
|
pub fn verbosity_no_filter() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let messages = vec![
|
||||||
|
LogMessageType::GenericErr(String::from("Test Err")).log_message(),
|
||||||
|
LogMessageType::GenericWarn(String::from("Test Warn")).log_message(),
|
||||||
|
LogMessageType::GenericInfo(String::from("Test Info")).log_message(),
|
||||||
|
LogMessageType::GenericDebug(String::from("Test Debug")).log_message(),
|
||||||
|
];
|
||||||
|
let mut config = config::log::LogSettings::default();
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
config.set_verbosity(LogVerbosity::Error);
|
||||||
|
|
||||||
|
create_dir_all(PathBuf::from(LOG_DIR.as_str()))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not create directory: {}", *LOG_DIR));
|
||||||
|
|
||||||
|
let log_line = log_to_str(&messages[0], &config, file!(), line!(), column!())
|
||||||
|
.expect("There should be a log line.")
|
||||||
|
+ "\n";
|
||||||
|
|
||||||
|
for msg in messages {
|
||||||
|
log!(&msg, &config);
|
||||||
|
}
|
||||||
|
|
||||||
|
let log_file = read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"));
|
||||||
|
|
||||||
|
assert_eq!(log_file, log_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests if messages are properly filtered according to their verbosity.
|
||||||
|
#[test]
|
||||||
|
pub fn verbosity_filter() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let messages = vec![
|
||||||
|
LogMessageType::GenericErr(String::from("Test Err")).log_message(),
|
||||||
|
LogMessageType::GenericWarn(String::from("Test Warn")).log_message(),
|
||||||
|
LogMessageType::GenericInfo(String::from("Test Info")).log_message(),
|
||||||
|
LogMessageType::GenericDebug(String::from("Test Debug")).log_message(),
|
||||||
|
];
|
||||||
|
let mut config = config::log::LogSettings::default();
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
|
||||||
|
create_dir_all(PathBuf::from(LOG_DIR.as_str()))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not create directory: {}", *LOG_DIR));
|
||||||
|
|
||||||
|
let mut log_line = log_to_str(&messages[0], &config, file!(), line!(), column!())
|
||||||
|
.expect("There should be a log line.")
|
||||||
|
+ "\n";
|
||||||
|
log_line += &(log_to_str(&messages[1], &config, file!(), line!(), column!())
|
||||||
|
.expect("There should be a log line.")
|
||||||
|
+ "\n");
|
||||||
|
|
||||||
|
for msg in messages {
|
||||||
|
log!(&msg, &config);
|
||||||
|
}
|
||||||
|
|
||||||
|
let log_file = read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"));
|
||||||
|
|
||||||
|
assert_eq!(log_file, log_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All testing concurrency in a controlled manner.
|
||||||
|
*
|
||||||
|
* When a test modifies the config according to it's requirements, another test might panic, because
|
||||||
|
* it might read the result from the changed config.
|
||||||
|
*/
|
||||||
|
#[test]
|
||||||
|
pub fn concurrency_tests() {
|
||||||
|
log_macro_shared_config();
|
||||||
|
log_concurrently_any_order();
|
||||||
|
log_concurrently_correct_order();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if log macro logs to file correctly.
|
||||||
|
* [`config::CONFIG`]
|
||||||
|
*/
|
||||||
|
fn log_macro_shared_config() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let message = LogMessageType::GenericWarn(String::from("Test Log")).log_message();
|
||||||
|
let mut config = config::LOG_SETTINGS
|
||||||
|
.write()
|
||||||
|
.expect("Could not acquire write lock on config!");
|
||||||
|
take(&mut *config);
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
config.set_stdout(false);
|
||||||
|
config.set_stderr(false);
|
||||||
|
drop(config);
|
||||||
|
|
||||||
|
create_dir_all(PathBuf::from(LOG_DIR.as_str()))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not create directory: {}", *LOG_DIR));
|
||||||
|
|
||||||
|
log!(&message);
|
||||||
|
|
||||||
|
let log_file = read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"));
|
||||||
|
|
||||||
|
assert_eq!(message.to_string() + "\n", log_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests concurrent logging. Log lines may be in any order.
|
||||||
|
*/
|
||||||
|
fn log_concurrently_any_order() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let mut config = config::LOG_SETTINGS
|
||||||
|
.write()
|
||||||
|
.expect("Could not acquire write lock on config!");
|
||||||
|
take(&mut *config);
|
||||||
|
let mut messages = Vec::with_capacity(CONCURRENT_MESSAGE_COUNT);
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
config.set_stdout(false);
|
||||||
|
config.set_stderr(false);
|
||||||
|
drop(config);
|
||||||
|
|
||||||
|
for i in 0..CONCURRENT_MESSAGE_COUNT {
|
||||||
|
let msg = i.to_string();
|
||||||
|
messages.push(async {
|
||||||
|
log!(&LogMessageType::GenericWarn(msg).log_message());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
block_on(join_all(messages));
|
||||||
|
|
||||||
|
let mut num_set = HashSet::with_capacity(CONCURRENT_MESSAGE_COUNT);
|
||||||
|
for i in 0..CONCURRENT_MESSAGE_COUNT {
|
||||||
|
num_set.insert(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"))
|
||||||
|
.lines()
|
||||||
|
{
|
||||||
|
let num_str = line
|
||||||
|
.split_whitespace()
|
||||||
|
.last()
|
||||||
|
.unwrap_or_else(|| panic!("Could not get message number from line: {line}"));
|
||||||
|
let num = num_str
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| panic!("Could not parse number: {num_str}"));
|
||||||
|
assert!(num_set.remove(&num));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(num_set.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests concurrent logging. Log lines must be in order.
|
||||||
|
*/
|
||||||
|
fn log_concurrently_correct_order() {
|
||||||
|
let log_path = &format!("{}/{}", *LOG_DIR, Uuid::new_v4());
|
||||||
|
println!("Log Path: {log_path}");
|
||||||
|
let mut config = config::LOG_SETTINGS
|
||||||
|
.write()
|
||||||
|
.expect("Could not acquire write lock on config!");
|
||||||
|
take(&mut *config);
|
||||||
|
let mut messages = Vec::with_capacity(CONCURRENT_MESSAGE_COUNT);
|
||||||
|
config.set_path(PathBuf::from(log_path));
|
||||||
|
config.set_stdout(false);
|
||||||
|
config.set_stderr(false);
|
||||||
|
drop(config);
|
||||||
|
|
||||||
|
for i in 0..CONCURRENT_MESSAGE_COUNT {
|
||||||
|
let msg = i.to_string();
|
||||||
|
messages.push(async {
|
||||||
|
log!(&LogMessageType::GenericWarn(msg).log_message());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
block_on(join_all(messages));
|
||||||
|
|
||||||
|
let mut num_set = HashSet::with_capacity(CONCURRENT_MESSAGE_COUNT);
|
||||||
|
for i in 0..CONCURRENT_MESSAGE_COUNT {
|
||||||
|
num_set.insert(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, line) in read_to_string(PathBuf::from(log_path))
|
||||||
|
.unwrap_or_else(|_| panic!("Could not read file: {log_path}"))
|
||||||
|
.lines()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let num_str = line
|
||||||
|
.split_whitespace()
|
||||||
|
.last()
|
||||||
|
.unwrap_or_else(|| panic!("Could not get message number from line: {line}"));
|
||||||
|
let num = num_str
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| panic!("Could not parse number: {num_str}"));
|
||||||
|
assert!(num_set.remove(&num));
|
||||||
|
assert_eq!(i, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(num_set.len(), 0);
|
||||||
|
}
|
Reference in New Issue
Block a user