From f57e73e0c682247b3dd0536dde6aa612c056e0ee Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 20 Apr 2022 18:09:21 +0200 Subject: [PATCH 1/3] Replace custom logging with log crate and custom format Signed-off-by: trivernis --- Cargo.lock | 135 ++++++++++++++++++++++++++++++++ Cargo.toml | 3 + src/args.rs | 3 + src/functions/desktops.rs | 4 +- src/functions/partition.rs | 4 +- src/internal/config.rs | 58 +++++--------- src/internal/files.rs | 13 +-- src/internal/mod.rs | 45 +++-------- src/internal/returncode_eval.rs | 8 +- src/internal/strings.rs | 45 +---------- src/logging.rs | 69 ++++++++++++++++ src/main.rs | 2 + 12 files changed, 255 insertions(+), 134 deletions(-) create mode 100644 src/logging.rs diff --git a/Cargo.lock b/Cargo.lock index 8ff7633..faed101 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,24 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "atty" version = "0.2.14" @@ -25,6 +43,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "3.1.10" @@ -64,6 +88,29 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "flexi_logger" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969940c39bc718475391e53a3a59b0157e64929c80cf83ad5dde5f770ecdc423" +dependencies = [ + "ansi_term", + "atty", + "glob", + "lazy_static", + "log", + "regex", + "rustversion", + "thiserror", + "time", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "hashbrown" version = "0.11.2" @@ -106,6 +153,9 @@ name = "jade" version = "0.1.0" dependencies = [ "clap", + "flexi_logger", + "lazy_static", + "log", "serde", "serde_json", ] @@ -122,6 +172,30 @@ version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +[[package]] +name = "log" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "num_threads" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +dependencies = [ + "libc", +] + [[package]] name = "os_str_bytes" version = "6.0.0" @@ -170,6 +244,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "ryu" version = "1.0.9" @@ -239,6 +336,44 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "unicode-xid" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 367f85b..42ee9d7 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ edition = "2021" clap = {version = "3.1.10", features = ["derive"] } serde_json = "1.0.59" serde = { version = "1.0.117", features = [ "derive" ] } +log = "0.4.16" +flexi_logger = {version = "0.22.3", features = ["colors"] } +lazy_static = "1.4.0" \ No newline at end of file diff --git a/src/args.rs b/src/args.rs index 6fe3b50..ae8751c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -7,6 +7,9 @@ use std::path::PathBuf; pub struct Opt { #[clap(subcommand)] pub command: Command, + + #[clap(long, short, parse(from_occurrences))] + pub verbose: usize, } #[derive(Debug, Subcommand)] diff --git a/src/functions/desktops.rs b/src/functions/desktops.rs index 5773ad5..0bffcfc 100755 --- a/src/functions/desktops.rs +++ b/src/functions/desktops.rs @@ -3,7 +3,7 @@ use crate::internal::exec::*; use crate::internal::*; pub fn install_desktop_setup(desktop_setup: DesktopSetup) { - log(format!("Installing {:?}", desktop_setup)); + log::debug!("Installing {:?}", desktop_setup); match desktop_setup { DesktopSetup::Onyx => install_onyx(), DesktopSetup::Gnome => install_gnome(), @@ -166,7 +166,7 @@ fn install_onyx() { } fn enable_dm(dm: &str) { - log(format!("Enabling {}", dm)); + log::debug!("Enabling {}", dm); exec_eval( exec_chroot("systemctl", vec![String::from("enable"), String::from(dm)]), format!("Enable {}", dm).as_str(), diff --git a/src/functions/partition.rs b/src/functions/partition.rs index 478bf4d..7f24fad 100755 --- a/src/functions/partition.rs +++ b/src/functions/partition.rs @@ -9,7 +9,7 @@ pub fn partition(device: PathBuf, mode: PartitionMode, efi: bool) { } match mode { PartitionMode::Auto => { - log(format!("automatically partitioning {device:?}")); + log::debug!("automatically partitioning {device:?}"); if efi { partition_with_efi(&device); } else { @@ -17,7 +17,7 @@ pub fn partition(device: PathBuf, mode: PartitionMode, efi: bool) { } } PartitionMode::Manual => { - log("Manual partitioning".to_string()); + log::debug!("Manual partitioning"); } } if device.to_string_lossy().contains("nvme") { diff --git a/src/internal/config.rs b/src/internal/config.rs index 08f61a5..e9d813b 100755 --- a/src/internal/config.rs +++ b/src/internal/config.rs @@ -54,9 +54,7 @@ pub fn read_config(configpath: PathBuf) { let data = std::fs::read_to_string(&configpath); match &data { Ok(_) => { - log(format!( - "[ \x1b[2;1;32mOK\x1b[0m ] Read config file {configpath:?}" - )); + log::debug!("[ \x1b[2;1;32mOK\x1b[0m ] Read config file {configpath:?}"); } Err(e) => { crash( @@ -69,22 +67,16 @@ pub fn read_config(configpath: PathBuf) { serde_json::from_str(&data.unwrap()); match &config { Ok(_) => { - log(format!( - "[ \x1b[2;1;32mOK\x1b[0m ] Parse config file {configpath:?}", - )); + log::debug!("[ \x1b[2;1;32mOK\x1b[0m ] Parse config file {configpath:?}",); } Err(e) => { crash(format!("Parse config file {configpath:?} ERROR: {}", e), 1); } } let config: Config = config.unwrap(); - - info(format!( - "Block device to use : /dev/{}", - config.partition.device - )); - info(format!("Partitioning mode : {:?}", config.partition.mode)); - info(format!("Partitioning for EFI : {}", config.partition.efi)); + log::info!("Block device to use : /dev/{}", config.partition.device); + log::info!("Partitioning mode : {:?}", config.partition.mode); + log::info!("Partitioning for EFI : {}", config.partition.efi); partition::partition( PathBuf::from("/dev/").join(config.partition.device), config.partition.mode, @@ -93,29 +85,23 @@ pub fn read_config(configpath: PathBuf) { base::install_base_packages(); base::genfstab(); println!(); - info(format!( - "Installing bootloader : {}", - config.bootloader.r#type - )); - info(format!( - "Installing bootloader to : {}", - config.bootloader.location - )); + log::info!("Installing bootloader : {}", config.bootloader.r#type); + log::info!("Installing bootloader to : {}", config.bootloader.location); if config.bootloader.r#type == "grub-efi" { base::install_bootloader_efi(PathBuf::from(config.bootloader.location)); } else if config.bootloader.r#type == "grub-legacy" { base::install_bootloader_legacy(PathBuf::from(config.bootloader.location)); } println!(); - info(format!("Adding Locales : {:?}", config.locale.locale)); - info(format!("Using keymap : {}", config.locale.keymap)); - info(format!("Setting timezone : {}", config.locale.timezone)); + log::info!("Adding Locales : {:?}", config.locale.locale); + log::info!("Using keymap : {}", config.locale.keymap); + log::info!("Setting timezone : {}", config.locale.timezone); locale::set_locale(config.locale.locale.join(" ")); locale::set_keyboard(config.locale.keymap.as_str()); locale::set_timezone(config.locale.timezone.as_str()); println!(); - info(format!("Hostname : {}", config.networking.hostname)); - info(format!("Enabling ipv6 : {}", config.networking.ipv6)); + log::info!("Hostname : {}", config.networking.hostname); + log::info!("Enabling ipv6 : {}", config.networking.ipv6); network::set_hostname(config.networking.hostname.as_str()); network::create_hosts(); if config.networking.ipv6 { @@ -124,15 +110,9 @@ pub fn read_config(configpath: PathBuf) { println!(); println!("---------"); for i in 0..config.users.len() { - info(format!("Creating user : {}", config.users[i].name)); - info(format!( - "Setting use password : {}", - config.users[i].password - )); - info(format!( - "Enabling root for user : {}", - config.users[i].hasroot - )); + log::info!("Creating user : {}", config.users[i].name); + log::info!("Setting use password : {}", config.users[i].password); + log::info!("Enabling root for user : {}", config.users[i].hasroot); users::new_user( config.users[i].name.as_str(), config.users[i].hasroot, @@ -141,19 +121,19 @@ pub fn read_config(configpath: PathBuf) { println!("---------"); } println!(); - info(format!("Setting root password : {}", config.rootpass)); + log::info!("Setting root password : {}", config.rootpass); users::root_pass(config.rootpass.as_str()); println!(); - info(format!("Installing desktop : {:?}", config.desktop)); + log::info!("Installing desktop : {:?}", config.desktop); if let Some(desktop) = &config.desktop { desktops::install_desktop_setup(*desktop); } println!(); - info(format!("Enabling timeshift : {}", config.timeshift)); + log::info!("Enabling timeshift : {}", config.timeshift); if config.timeshift { base::setup_timeshift(); } - info(format!("Extra packages : {:?}", config.extra_packages)); + log::info!("Extra packages : {:?}", config.extra_packages); let mut extra_packages: Vec<&str> = Vec::new(); for i in 0..config.extra_packages.len() { extra_packages.push(config.extra_packages[i].as_str()); diff --git a/src/internal/files.rs b/src/internal/files.rs index 5eacf28..91bcad2 100755 --- a/src/internal/files.rs +++ b/src/internal/files.rs @@ -6,7 +6,7 @@ pub fn create_file(path: &str) { let returncode = File::create(path); match returncode { Ok(_) => { - log(format!("[ \x1b[2;1;32mOK\x1b[0m ] Create {}", path)); + log::info!("Create {}", path); } Err(e) => { crash(format!("Create {}: Failed with error {}", path, e), 1); @@ -18,10 +18,7 @@ pub fn copy_file(path: &str, destpath: &str) { let return_code = std::fs::copy(path, destpath); match return_code { Ok(_) => { - log(format!( - "[ \x1b[2;1;32mOK\x1b[0m ] Copy {} to {}", - path, destpath - )); + log::info!("Copy {} to {}", path, destpath); } Err(e) => { crash( @@ -33,11 +30,7 @@ pub fn copy_file(path: &str, destpath: &str) { } pub fn append_file(path: &str, content: &str) -> std::io::Result<()> { - log(format!( - "[ \x1b[2;1;32mOK\x1b[0m ] Append '{}' to file {}", - content.trim_end(), - path - )); + log::info!("Append '{}' to file {}", content.trim_end(), path); let mut file = OpenOptions::new().append(true).open(path)?; file.write_all(content.as_bytes())?; Ok(()) diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 0a157c2..1bc9017 100755 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -5,43 +5,20 @@ pub mod install; pub mod returncode_eval; pub mod strings; -pub fn install(pkgs: Vec<&str>) { - install::install(pkgs); -} - -pub fn crash>(a: S, b: i32) -> ! { - strings::crash(a.as_ref().to_string(), b); -} - -pub fn log(a: String) { - strings::log(a); -} - -pub fn info(a: String) { - strings::info(a); -} -pub fn files_eval(returncode: std::result::Result<(), std::io::Error>, logmsg: &str) { - returncode_eval::files_eval(returncode, logmsg); -} - -pub fn exec_eval( - returncode: std::result::Result, - logmsg: &str, -) { - returncode_eval::exec_eval(returncode, logmsg); -} +pub use install::install; +pub use returncode_eval::*; +pub use strings::crash; #[macro_export] macro_rules! uwu { ($x:expr) => {{ - let uwu: String = String::from_str($x).unwrap(); - let uwu = uwu.replace("l", "w"); - let uwu = uwu.replace("L", "W"); - let uwu = uwu.replace("r", "w"); - let uwu = uwu.replace("R", "W"); - let uwu = uwu.replace("na", "nya"); - let uwu = uwu.replace("Na", "Nya"); - let uwu = uwu.replace("NA", "NYA"); - uwu + let uwu: String = $x.to_string(); + uwu.replace("l", "w") + .replace("L", "W") + .replace("r", "w") + .replace("R", "W") + .replace("na", "nya") + .replace("Na", "Nya") + .replace("NA", "NYA") }}; } diff --git a/src/internal/returncode_eval.rs b/src/internal/returncode_eval.rs index dec9b76..f8ec5c7 100755 --- a/src/internal/returncode_eval.rs +++ b/src/internal/returncode_eval.rs @@ -6,11 +6,11 @@ pub fn exec_eval( ) { match &return_code { Ok(_) => { - log(format!("[ \x1b[2;1;32mOK\x1b[0m ] {}", logmsg)); + log::info!("{}", logmsg); } Err(e) => { crash( - format!("[ \x1b[2;1;32mFAILED\x1b[0m ] {} ERROR: {}", logmsg, e), + format!("{} ERROR: {}", logmsg, e), return_code.unwrap_err().raw_os_error().unwrap(), ); } @@ -20,11 +20,11 @@ pub fn exec_eval( pub fn files_eval(return_code: std::result::Result<(), std::io::Error>, logmsg: &str) { match &return_code { Ok(_) => { - log(format!("[ \x1b[2;1;32mOK\x1b[0m ] {}", logmsg)); + log::info!("{}", logmsg); } Err(e) => { crash( - format!("[ \x1b[2;1;32mFAILED\x1b[0m ] {} ERROR: {}", logmsg, e), + format!("{} ERROR: {}", logmsg, e), return_code.unwrap_err().raw_os_error().unwrap(), ); } diff --git a/src/internal/strings.rs b/src/internal/strings.rs index 49ae707..836c20a 100755 --- a/src/internal/strings.rs +++ b/src/internal/strings.rs @@ -1,47 +1,6 @@ -use crate::uwu; -use std::env; use std::process::exit; -use std::str::FromStr; -use std::time::UNIX_EPOCH; -pub fn crash(a: String, b: i32) -> ! { - let a = if env::var("JADE_UWU").unwrap_or_else(|_| "".to_string()) == "true" { - uwu!(&a) - } else { - a - }; - println!("{}", a); +pub fn crash>(a: S, b: i32) -> ! { + log::error!("{}", a.as_ref()); exit(b); } -pub fn log(a: String) { - let a = if env::var("JADE_UWU").unwrap_or_else(|_| "".to_string()) == "true" - && env::var("JADE_UWU_DEBUG").unwrap_or_else(|_| "".to_string()) == "true" - { - uwu!(&a) - } else { - a - }; - eprintln!( - "[ \x1b[2;1;33mLOG\x1b[0m ] {} {}", - std::time::SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - a - ); -} -pub fn info(a: String) { - let a = if env::var("JADE_UWU").unwrap_or_else(|_| "".to_string()) == "true" { - uwu!(&a) - } else { - a - }; - eprintln!( - "[ \x1b[2;1;39mINFO\x1b[0m ] {} {}", - std::time::SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - a - ); -} diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..7769802 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,69 @@ +use crate::uwu; +use flexi_logger::{style, DeferredNow, LogSpecification, Logger}; +use lazy_static::lazy_static; +use log::{Level, LevelFilter}; +use std::env; +use std::io::Write; + +lazy_static! { + static ref UWU: bool = env::var("JADE_UWU").map(|v| v == "true").unwrap_or(false); + static ref UWU_DEBUG: bool = env::var("JADE_UWU_DEBUG") + .map(|v| v == "true") + .unwrap_or(false); +} + +pub fn init(verbosity: usize) { + let log_specification = match verbosity { + 0 => LogSpecification::builder() + .default(LevelFilter::Info) + .build(), + 1 => LogSpecification::builder() + .default(LevelFilter::Debug) + .build(), + _ => LogSpecification::builder() + .default(LevelFilter::Trace) + .build(), + }; + Logger::with(log_specification) + .format(format_log_entry) + .start() + .unwrap(); +} + +/// Formats a log entry with color +fn format_log_entry( + w: &mut dyn Write, + now: &mut DeferredNow, + record: &log::Record, +) -> std::io::Result<()> { + let msg = record.args().to_string(); + let level = record.level(); + let msg = apply_uwu(level, msg); + write!( + w, + "[ {} ] {} {}", + style(level).paint(level.to_string()), + now.now(), + msg + ) +} + +/// Applies uwu if the required environment variables are set +fn apply_uwu(level: Level, msg: String) -> String { + match level { + Level::Error | Level::Warn | Level::Info => { + if *UWU { + uwu!(msg) + } else { + msg + } + } + Level::Debug | Level::Trace => { + if *UWU_DEBUG { + uwu!(msg) + } else { + msg + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 7eccf69..b4420d6 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod args; mod functions; mod internal; +mod logging; use crate::args::{BootloaderSubcommand, Command, Opt, UsersSubcommand}; use crate::functions::*; @@ -8,6 +9,7 @@ use clap::Parser; fn main() { let opt: Opt = Opt::parse(); + logging::init(opt.verbose); match opt.command { Command::Partition(args) => { partition::partition(args.device, args.mode, args.efi); From 0913e35f7f04401bfe7cd6dc0fea4b071e80360f Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 20 Apr 2022 18:16:00 +0200 Subject: [PATCH 2/3] Add info about debug logging to the README Signed-off-by: trivernis --- README.md | 16 ++++++++++++++-- src/args.rs | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7e4886f..b6aa861 100755 --- a/README.md +++ b/README.md @@ -62,10 +62,10 @@ jade networking getcryst.al --ipv6 ### configure users ```sh # make a new user called nonRootHaver, without sudo and easytohack as the password -jade users newUser nonRootaver false easytohack +jade users newUser nonRootaver easytohack # make a user called rootHaver, with sudo and omgsosuperhardtohack as the password -jade users newUser rootHaver true omgsosuperhardtohack +jade users newUser rootHaver omgsosuperhardtohack --sudoer ``` ### set root password @@ -88,6 +88,18 @@ jade desktops gnome jade setup-timeshift ``` +### debug logging + +debug messages: +```sh +jade -v +``` + +traces: +```sh +jade -vv +``` + ## How to build: Tested on latest Cargo (1.60.0-nightly) diff --git a/src/args.rs b/src/args.rs index ae8751c..17f8447 100644 --- a/src/args.rs +++ b/src/args.rs @@ -155,7 +155,7 @@ pub struct NewUserArgs { pub username: String, /// If the user should have root privileges - #[clap(long, aliases=&["has-root"])] + #[clap(long, aliases=&["has-root", "sudoer", "root"])] pub hasroot: bool, /// The password to set. NOTE: Takes hashed password, use `openssl passwd -1 ` to generate the hash. From 672b19b79f7fff259d186b423534a7195870a6de Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 20 Apr 2022 18:37:31 +0200 Subject: [PATCH 3/3] Fix timestamp format in log entries Signed-off-by: trivernis --- src/logging.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/logging.rs b/src/logging.rs index 7769802..4970c3d 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -39,11 +39,14 @@ fn format_log_entry( let msg = record.args().to_string(); let level = record.level(); let msg = apply_uwu(level, msg); + let (h, m, s) = now.now().time().as_hms(); write!( w, - "[ {} ] {} {}", + "[ {} ] {}:{}:{} {}", style(level).paint(level.to_string()), - now.now(), + h, + m, + s, msg ) }