Logging to stdout and 2 separate files: human-readable logs and stats for processing

This commit is contained in:
Данила Горнушко 2024-06-03 13:43:09 +03:00
parent a8eaf73f57
commit 5e8476be07
3 changed files with 112 additions and 47 deletions

28
Cargo.lock generated
View file

@ -235,26 +235,12 @@ dependencies = [
]
[[package]]
name = "env_filter"
version = "0.1.0"
name = "fern"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
@ -272,7 +258,7 @@ dependencies = [
"colored",
"crc",
"ctrlc",
"env_logger",
"fern",
"hound",
"log",
"serialport",
@ -291,12 +277,6 @@ version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.60"

View file

@ -6,9 +6,9 @@ edition = "2021"
[dependencies]
serialport = {version="4.3", default-features=false}
log = "0.4"
env_logger = "0.11"
chrono = "0.4"
fern = "0.6"
colored = "2.1"
chrono = "0.4"
hound = "3.5"
clap = { version = "4.5", features = ["derive"] }
ctrlc = "3.4"

View file

@ -1,7 +1,7 @@
use chrono::{Local, Timelike};
use clap::{arg, Parser};
use crc::{Crc, CRC_16_IBM_3740};
use env_logger::Builder;
use fern::Dispatch;
use hound::{SampleFormat, WavSpec, WavWriter};
use log::{debug, error, info, trace, warn, Level, LevelFilter};
use serialport::SerialPort;
@ -53,11 +53,18 @@ impl fmt::Display for StatsOnDetect {
impl fmt::Display for Stats10Min {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"In last 10 min: Hail detected: {}. Noise detected: {}.",
self.hail, self.noise,
)
write!(f, "{} {}.", self.hail, self.noise,)
}
}
impl Stats10Min {
fn nice_log_format(input: &str) -> Option<String> {
let parts: Vec<&str> = input.split_whitespace().collect();
let hail = parts.first()?;
let noise = parts.get(1)?;
Some(format!(
"In last 10 min: Hail detected: {hail}. Noise detected: {noise}."
))
}
}
@ -111,6 +118,12 @@ struct Args {
#[arg(long, default_value = "wave")]
name: String,
#[arg(long)]
app_logs_path: Option<String>,
#[arg(long)]
stat_logs_path: Option<String>,
#[arg(long, default_value = "./out/")]
path: String,
@ -118,18 +131,61 @@ struct Args {
verbose: u8,
}
fn main() {
let args = Args::parse();
let log_level = match args.verbose {
fn init_logger(
verbosity: u8,
app_log_path: Option<&Path>,
stat_log_path: Option<&Path>,
) -> Result<(), fern::InitError> {
let log_level = match verbosity {
0 => LevelFilter::Info,
1 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
let mut builder = Builder::new();
builder
.format(|buf, record| {
let app_dispatch = app_log_path.map(|path| {
Dispatch::new()
.format(|out, message, record| {
let now = Local::now();
let message = message.to_string();
out.finish(format_args!(
"{} [{}] - {}",
now.format("%Y-%m-%d %H:%M:%S%.3f"),
record.level(),
if record.target() == "stats10min" {
Stats10Min::nice_log_format(&message)
.expect("Couldn't get nice string for 10-min stats!")
} else {
message
}
))
})
.level(log_level)
.chain(fern::log_file(path).unwrap())
});
let stat_dispatch = stat_log_path.map(|path| {
Dispatch::new()
.level(LevelFilter::Info)
.format(|out, message, _| {
let now = Local::now();
out.finish(format_args!(
"{} {}",
now.format("%Y-%m-%d %H:%M:%S%.3f"),
message
))
})
.filter(|metadata| {
metadata.level() == Level::Info && metadata.target().contains("stats10min")
})
.chain(fern::log_file(path).unwrap())
});
let stdout_dispatch = Dispatch::new()
.format(|out, message, record| {
use colored::*;
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S,%3f");
let now = Local::now();
let message = message.to_string();
let level = match record.level() {
Level::Error => "ERROR".red(),
Level::Warn => "WARN".yellow(),
@ -137,11 +193,40 @@ fn main() {
Level::Debug => "DEBUG".blue(),
Level::Trace => "TRACE".purple(),
};
writeln!(buf, "{} [{}] - {}", timestamp, level, record.args())
out.finish(format_args!(
"{} [{}] - {}",
now.format("%Y-%m-%d %H:%M:%S%.3f"),
level,
if record.target() == "stats10min" {
Stats10Min::nice_log_format(&message)
.expect("Couldn't get nice string for 10-min stats!")
} else {
message
}
))
})
.filter(None, log_level)
.init();
.level(log_level)
.chain(std::io::stdout());
let root_dispatch = Dispatch::new().chain(stdout_dispatch);
let root_dispatch = match stat_dispatch {
Some(dispatch) => root_dispatch.chain(dispatch),
None => root_dispatch,
};
let root_dispatch = match app_dispatch {
Some(dispatch) => root_dispatch.chain(dispatch),
None => root_dispatch,
};
root_dispatch.apply()?;
Ok(())
}
fn main() {
let args = Args::parse();
let app_logs_path = args.app_logs_path.as_deref().map(Path::new);
let stat_logs_path = args.stat_logs_path.as_deref().map(Path::new);
init_logger(args.verbose, app_logs_path, stat_logs_path)
.expect("Failed to initialize the logger");
assert!(
args.request_rate >= 1 && args.request_rate <= 100,
@ -239,7 +324,7 @@ fn main() {
handle_error_with_timeout(&mut err_counter);
continue;
}
StatsOptions::Stats(val) => info!("{val}"),
StatsOptions::Stats(val) => info!(target: "stats10min", "{val}"),
StatsOptions::DetectedNew(cur_stats) => {
err_counter = err_counter.saturating_sub(1);
if prev_stats.map_or(true, |v| v != cur_stats) {
@ -249,11 +334,11 @@ fn main() {
cur_stats.hail_since_startup > prev_stats.hail_since_startup,
cur_stats.noise_since_startup > prev_stats.noise_since_startup,
) {
(true, true) => info!("Hail and noise detected, {cur_stats}"),
(true, false) => info!("Hail detected, {cur_stats}"),
(true, true) => info!(target: "detected", "Hail and noise detected, {cur_stats}"),
(true, false) => info!(target: "detected", "Hail detected, {cur_stats}"),
(false, true) => {
if args.noise_logging {
info!("Noise detected, {cur_stats}")
info!(target: "detected", "Noise detected, {cur_stats}")
}
}
(false, false) => (),