Logging #26
@ -1,3 +1,5 @@
|
||||
workspace = { members = ["config", "logging"] }
|
||||
|
||||
[package]
|
||||
name = "wanessa"
|
||||
version = "0.1.0"
|
||||
@ -9,12 +11,13 @@ edition = "2021"
|
||||
|
||||
[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 = "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_assert_message = "deny" # 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_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
|
||||
|
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