From 79166669c48e8abe85d980b925ddf4db4a732d0f Mon Sep 17 00:00:00 2001
From: theMackabu <theMackabu@gmail.com>
Date: Wed, 20 Nov 2024 14:43:25 -0800
Subject: [PATCH] full migration to tracing

---
 Cargo.toml                                    |  6 +-
 maid/client/cli/{butler.rs => dispatch.rs}    | 39 ++++++-----
 maid/client/cli/mod.rs                        | 43 ++++++-------
 maid/client/cli/run.rs                        | 59 ++++++-----------
 maid/client/cli/tasks.rs                      | 28 ++++----
 maid/client/helpers/logger.rs                 | 24 +++----
 maid/client/helpers/maidfile.rs               | 13 +---
 maid/client/helpers/status.rs                 | 16 ++---
 maid/client/helpers/string.rs                 | 11 +---
 maid/client/main.rs                           | 64 +++++++++----------
 maid/client/parse/file.rs                     | 40 ++++--------
 maid/client/server/cli.rs                     | 25 +++-----
 maid/client/server/file.rs                    |  9 +--
 maid/client/table.rs                          | 28 ++++----
 maid/server/docker/run.rs                     | 52 +++++++--------
 maid/server/helpers/file.rs                   |  9 +--
 maid/server/main.rs                           |  5 +-
 maid/server/table.rs                          | 34 ++++------
 maid/shared/colors.rs                         | 36 +++++++++++
 maid/shared/lib.rs                            |  2 +
 maid/{client/cli/verbose.rs => shared/log.rs} | 50 ++++++++++++---
 21 files changed, 297 insertions(+), 296 deletions(-)
 rename maid/client/cli/{butler.rs => dispatch.rs} (75%)
 create mode 100644 maid/shared/colors.rs
 create mode 100644 maid/shared/lib.rs
 rename maid/{client/cli/verbose.rs => shared/log.rs} (80%)

diff --git a/Cargo.toml b/Cargo.toml
index 03696a3..5251675 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,10 @@ build = "build/attribute.rs"
 repository = "https://github.com/exact-rs/maid"
 description = "๐Ÿ”จ A comprehensive build tool for all your needs."
 
+[lib]
+name = "maid"
+path = "maid/shared/lib.rs"
+
 [[bin]]
 name = "maid"
 path = "maid/client/main.rs"
@@ -71,6 +75,7 @@ home = "0.5.9"
 colored = "2.1.0"
 flate2 = "1.0.35"
 anyhow = "1.0.93"
+tracing = "0.1.40"
 termcolor = "1.4.1"
 macros-rs = "0.5.2"
 indicatif = "0.17.9"
@@ -78,7 +83,6 @@ serde_json = "1.0.133"
 text_placeholder = "0.5.1"
 global_placeholders = "0.1.0"
 
-log = { version = "0.1.40", package = "tracing" }
 clap = { version = "4.5.21", features = ["derive"] }
 serde = { version = "1.0.215", features = ["derive"] }
 uuid = { version = "1.11.0", features = ["v4", "fast-rng"] }
diff --git a/maid/client/cli/butler.rs b/maid/client/cli/dispatch.rs
similarity index 75%
rename from maid/client/cli/butler.rs
rename to maid/client/cli/dispatch.rs
index c92f238..3e2710e 100644
--- a/maid/client/cli/butler.rs
+++ b/maid/client/cli/dispatch.rs
@@ -1,19 +1,24 @@
 use crate::helpers;
+use maid::log::prelude::*;
 
-use colored::Colorize;
 use inquire::Text;
 use macros_rs::string;
 use notify::RecursiveMode;
 use notify_debouncer_mini::new_debouncer;
 use std::{fs::File, io::Write, path::Path, time::Duration};
 
-fn create_error(name: &str, path: &str) {
-    println!("An error happened when asking for {name}, try again later.");
-    std::fs::remove_file(path).unwrap();
-    std::process::exit(1);
+pub(crate) fn clean() {
+    if let Ok(_) = std::fs::remove_dir_all(".maid/temp") {
+        info!("{}", "Purged temp archives".green())
+    }
+
+    match std::fs::remove_dir_all(".maid/cache") {
+        Ok(_) => info!("{}", "Emptied build cache".green()),
+        Err(_) => warn!("{}", "Build cache does not exist, cannot remove".yellow()),
+    };
 }
 
-pub fn watch(path: &Path) {
+pub(crate) fn watch(path: &Path) {
     let (tx, rx) = std::sync::mpsc::channel();
     let mut debouncer = new_debouncer(Duration::from_secs(1), tx).unwrap();
 
@@ -25,11 +30,15 @@ pub fn watch(path: &Path) {
     }
 }
 
-pub fn update() { println!("check and retrive updates") }
+pub(crate) fn update() { println!("check and retrive updates") }
+
+pub(crate) fn init() {
+    fn create_error(name: &str, path: &str) {
+        std::fs::remove_file(path).unwrap();
+        error!("An error happened when asking for {}, try again later.", name);
+    }
 
-pub fn init() {
     let path = "maidfile";
-    
     let example_maidfile = "[tasks.example]\ninfo = \"this is a comment\"\nscript = \"echo 'hello world'\"";
 
     if !helpers::Exists::file(path.to_owned()).unwrap() {
@@ -60,15 +69,3 @@ pub fn init() {
         println!("{}", "maidfile already exists, aborting".yellow())
     }
 }
-
-pub fn clean() {
-    match std::fs::remove_dir_all(".maid/temp") {
-        Ok(_) => println!("{}", "removed temp archives".green()),
-        Err(_) => {}
-    };
-
-    match std::fs::remove_dir_all(".maid/cache") {
-        Ok(_) => println!("{}", "cleaned maid cache".green()),
-        Err(_) => println!("{}", "maid cache does not exist, cannot remove".yellow()),
-    };
-}
diff --git a/maid/client/cli/mod.rs b/maid/client/cli/mod.rs
index 84d8a82..71b1f2a 100644
--- a/maid/client/cli/mod.rs
+++ b/maid/client/cli/mod.rs
@@ -1,5 +1,4 @@
-pub(crate) mod verbose;
-pub(crate) mod butler;
+pub(crate) mod dispatch;
 pub(crate) mod run;
 pub(crate) mod tasks;
 
@@ -9,7 +8,8 @@ use crate::server;
 use crate::structs::{Cache, CacheConfig, Task};
 use crate::task;
 
-use colored::Colorize;
+use maid::log::prelude::*;
+
 use fs_extra::dir::get_size;
 use global_placeholders::global;
 use human_bytes::human_bytes;
@@ -23,7 +23,7 @@ pub fn get_version(short: bool) -> String {
     };
 }
 
-pub fn info(path: &String) {    
+pub fn info(path: &String) {
     let values = helpers::maidfile::merge(path);
     let project_root = parse::file::find_maidfile_root(path);
 
@@ -52,8 +52,8 @@ pub fn info(path: &String) {
     );
 }
 
-pub fn exec(task: &str, args: &Vec<String>, path: &String, silent: bool, is_dep: bool, is_remote: bool, log_level: Option<log::Level>, force: bool) {
-    log::info!("Starting maid {}", env!("CARGO_PKG_VERSION"));
+pub fn exec(task: &str, args: &Vec<String>, path: &String, silent: bool, is_dep: bool, is_remote: bool, log_level: Option<tracing::Level>, force: bool) {
+    debug!("Starting maid {}", env!("CARGO_PKG_VERSION"));
 
     if task.is_empty() {
         if is_remote {
@@ -101,7 +101,7 @@ pub fn exec(task: &str, args: &Vec<String>, path: &String, silent: bool, is_dep:
                         pb.suspend(|| {
                             println!(
                                 "{} {} in {} {}\n",
-                                helpers::string::check_icon(),
+                                maid::colors::OK,
                                 format!("finished {} {}", deps.len(), ternary!(deps.len() > 1, "dependencies", "dependency")).bright_green(),
                                 format!("{:.2?}", start.elapsed()).yellow(),
                                 format!("[{}]", deps.join(", ")).white()
@@ -127,7 +127,7 @@ pub fn exec(task: &str, args: &Vec<String>, path: &String, silent: bool, is_dep:
         if !cache.path.trim().is_empty() && !cache.target.is_empty() && !is_remote {
             if !helpers::Exists::folder(global!("maid.cache_dir", task)).unwrap() {
                 std::fs::create_dir_all(global!("maid.cache_dir", task)).unwrap();
-                log::debug!("created maid cache dir");
+                debug!("created maid cache dir");
             }
 
             let hash = task::cache::create_hash(&cache.path);
@@ -142,7 +142,7 @@ pub fn exec(task: &str, args: &Vec<String>, path: &String, silent: bool, is_dep:
                     })
                     .unwrap(),
                 ) {
-                    Ok(_) => log::debug!("created {task} cache config"),
+                    Ok(_) => debug!("created {task} cache config"),
                     Err(err) => crashln!("error {err} creating cache config"),
                 };
             }
@@ -170,11 +170,8 @@ pub fn exec(task: &str, args: &Vec<String>, path: &String, silent: bool, is_dep:
                     );
 
                     match std::fs::copy(Path::new(&cache_file), target.clone()) {
-                        Ok(_) => log::debug!("copied target file {}", target),
-                        Err(err) => {
-                            log::warn!("{err}");
-                            crashln!("Cannot copy target file.");
-                        }
+                        Ok(_) => debug!("copied target file {}", target),
+                        Err(err) => error!(%err, "Cannot copy target file."),
                     };
                 }
 
@@ -188,23 +185,23 @@ pub fn exec(task: &str, args: &Vec<String>, path: &String, silent: bool, is_dep:
                     })
                     .unwrap(),
                 ) {
-                    Ok(_) => log::debug!("added hash for {task} -> {hash}"),
-                    Err(err) => crashln!("error {err} creating cache config"),
+                    Ok(_) => debug!("added hash for {task} -> {hash}"),
+                    Err(err) => error!(%err, "error creating cache config"),
                 };
             }
         };
 
-        log::debug!("Is remote?: {is_remote}");
-        log::debug!("Project dir: {:?}", project_root);
-        log::debug!("Task path: {task_path}");
-        log::debug!("Working dir: {cwd}");
-        log::debug!("Started task: {task}");
+        debug!("Is remote?: {is_remote}");
+        debug!("Project dir: {:?}", project_root);
+        debug!("Task path: {task_path}");
+        debug!("Working dir: {cwd}");
+        debug!("Started task: {task}");
 
         if !silent && !is_remote {
             ternary!(
                 task_path == helpers::string::path_to_str(project_root.as_path()) || task_path == "%{dir.current}" || task_path == "." || task_path == *cwd,
-                println!("{} {}", helpers::string::arrow_icon(), &values.tasks[task].script),
-                println!("{} {} {}", format!("({task_path})").bright_cyan(), helpers::string::arrow_icon(), &values.tasks[task].script)
+                println!("{} {}", maid::colors::ARROW, &values.tasks[task].script),
+                println!("{} {} {}", format!("({task_path})").bright_cyan(), maid::colors::ARROW, &values.tasks[task].script)
             )
         }
 
diff --git a/maid/client/cli/run.rs b/maid/client/cli/run.rs
index e7b8433..59ded4e 100644
--- a/maid/client/cli/run.rs
+++ b/maid/client/cli/run.rs
@@ -1,14 +1,14 @@
+use maid::log::prelude::*;
+
 use crate::cli;
 use crate::helpers;
 use crate::shell::IntoArgs;
 use crate::structs::{Cache, Runner};
 use crate::table;
 
-use colored::Colorize;
 use fs_extra::dir::get_size;
 use human_bytes::human_bytes;
 use macros_rs::{crashln, string};
-use serde_json::json;
 use std::env;
 use std::io::Error;
 use std::path::Path;
@@ -32,42 +32,28 @@ fn run_script(runner: Runner) {
                 args.remove(0);
                 (result[0].clone(), args)
             }
-            Err(err) => {
-                log::warn!("{err}");
-                crashln!("Script could not be parsed into args");
-            }
+            Err(err) => error!(%err, "Script could not be parsed into args"),
         };
 
-        log::debug!("Original Script: {}", string);
-        log::debug!("Parsed Script: {}", script);
-        log::trace!("{}", json!({"name": name, "args": args}));
-        log::info!("Execute Command: '{name} {}'", args.join(" "));
+        debug!("Original Script: {}", string);
+        debug!("Parsed Script: {}", script);
+        debug!("Execute Command: '{name} {}'", args.join(" "));
 
         let working_dir = runner.project.join(&Path::new(runner.path));
         match env::set_current_dir(&working_dir) {
-            Ok(_) => {
-                log::info!("Working directory: {:?}", &working_dir);
-            }
-            Err(err) => {
-                crashln!("Failed to set working directory {:?}\nError: {:#?}", &working_dir, err);
-            }
+            Ok(_) => debug!("Working directory: {:?}", &working_dir),
+            Err(err) => error!(%err, "Failed to set working directory {:?}", &working_dir),
         };
 
         if runner.is_dep {
             cmd = match Command::new(&name).stdout(Stdio::null()).stderr(Stdio::null()).stdin(Stdio::null()).args(args.clone()).spawn() {
                 Ok(output) => output,
-                Err(err) => {
-                    log::warn!("{err}");
-                    crashln!("Cannot start command {name}.");
-                }
+                Err(err) => error!(%err, "Cannot start command {name}."),
             };
         } else {
             cmd = match Command::new(&name).args(args.clone()).stdout(Stdio::inherit()).stderr(Stdio::inherit()).stdin(Stdio::inherit()).spawn() {
                 Ok(output) => output,
-                Err(err) => {
-                    log::warn!("{err}");
-                    crashln!("Cannot start command {name}.");
-                }
+                Err(err) => error!(%err, "Cannot start command {name}."),
             };
         }
 
@@ -75,12 +61,12 @@ fn run_script(runner: Runner) {
         let exit_code = helpers::status::code(&status);
 
         status_array.push(status);
-        log::debug!("Finished cmd: '{name} {}' with exit code: {:?} in {:.2?}", args.join(" "), exit_code, start.elapsed());
+        debug!("Finished cmd: '{name} {}' with exit code: {:?} in {:.2?}", args.join(" "), exit_code, start.elapsed());
     }
 
     let status = match status_array.last() {
         Some(status) => status,
-        None => crashln!("Failed to fetch final status code."),
+        None => error!("Failed to fetch final status code."),
     };
 
     let cache = match &runner.maidfile.tasks[runner.name].cache {
@@ -93,7 +79,7 @@ fn run_script(runner: Runner) {
 
     if !runner.silent {
         if success {
-            println!("\n{} {}", helpers::string::check_icon(), "finished task successfully".bright_green());
+            println!("\n{} {}", maid::colors::OK, "finished task successfully".bright_green());
             if !cache.path.trim().is_empty() && !cache.target.is_empty() {
                 for target in cache.target {
                     let cache_file = format!(".maid/cache/{}/target/{}", runner.name, Path::new(&target).file_name().unwrap().to_str().unwrap());
@@ -104,19 +90,15 @@ fn run_script(runner: Runner) {
                                 format!("saved target '{}' to cache", target).bright_magenta(),
                                 format!("{}", human_bytes(get_size(cache_file.clone()).unwrap() as f64).white())
                             );
-                            log::debug!("saved target file {}", target)
-                        }
-                        Err(err) => {
-                            log::warn!("{err}");
-                            log::debug!("path: {}", target);
-                            crashln!("Cannot save target file.");
+                            debug!("saved target file {}", target)
                         }
+                        Err(err) => error!(%err, target, "Cannot save target file"),
                     };
                 }
             }
             println!("{} took {}", runner.name.white(), format!("{:.2?}", start.elapsed()).yellow());
         } else {
-            println!("\n{} {} {}", helpers::string::cross_icon(), "exited with status code".bright_red(), format!("{}", exit_code).red());
+            println!("\n{} {} {}", maid::colors::FAIL, "exited with status code".bright_red(), format!("{}", exit_code).red());
             println!("{} took {}", runner.name.white(), format!("{:.2?}", start.elapsed()).yellow());
         }
     } else {
@@ -127,15 +109,12 @@ fn run_script(runner: Runner) {
                     match std::fs::copy(Path::new(&target), cache_file.clone()) {
                         Ok(_) => println!(
                             "{} {}{}{}",
-                            helpers::string::add_icon(),
+                            maid::colors::ADD,
                             format!("{}", target).bright_green(),
-                            helpers::string::seperator(),
+                            maid::colors::SEP,
                             format!("{}", human_bytes(get_size(cache_file.clone()).unwrap() as f64).bright_cyan())
                         ),
-                        Err(err) => {
-                            log::error!("{err}");
-                            log::debug!("path: {}", target);
-                        }
+                        Err(err) => warn!(%err, %target, "Cache miss"),
                     };
                 }
             }
diff --git a/maid/client/cli/tasks.rs b/maid/client/cli/tasks.rs
index 798a875..572d8f5 100644
--- a/maid/client/cli/tasks.rs
+++ b/maid/client/cli/tasks.rs
@@ -4,9 +4,9 @@ use crate::parse;
 use crate::structs;
 use crate::table;
 
-use colored::Colorize;
 use inquire::Select;
 use macros_rs::{string, ternary};
+use maid::log::prelude::*;
 use text_placeholder::Template;
 
 pub fn json(path: &String, args: &Vec<String>, hydrate: bool) {
@@ -21,19 +21,22 @@ pub fn json(path: &String, args: &Vec<String>, hydrate: bool) {
 
 pub struct List;
 impl List {
-    pub fn all(path: &String, silent: bool, log_level: Option<log::Level>, force: bool) {
+    pub fn all(path: &String, silent: bool, log_level: Option<tracing::Level>, force: bool) {
         let values = helpers::maidfile::merge(path);
         let mut options: Vec<_> = values
             .tasks
             .iter()
             .map(|(key, task)| {
                 let info = match &task.info {
-                    Some(info) => ternary!(info.trim().len() < 1, string!("(no description)").bright_red(), format!("({info})").white()),
-                    None => string!("(no description)").bright_red(),
+                    Some(info) => match info.trim().len() < 1 {
+                        true => "(no description)".to_string().bright_red(),
+                        false => format!("({info})").white(),
+                    },
+                    None => "(no description)".to_string().bright_red(),
                 };
 
                 let verbose = match log_level.unwrap() {
-                    log::Level::ERROR => string!(),
+                    tracing::Level::ERROR => string!(),
                     _ => string!(task.script),
                 };
 
@@ -59,7 +62,7 @@ impl List {
         options.retain(|key| key.hidden == false);
         match Select::new("Select a task to run:", options).prompt() {
             Ok(task) => {
-                log::debug!("Starting {}", task.name);
+                debug!("Starting {}", task.name);
                 cli::exec(&String::from(task.name), &vec![String::from("")], &path, silent, false, false, log_level, force);
             }
 
@@ -67,19 +70,22 @@ impl List {
         }
     }
 
-    pub fn remote(path: &String, silent: bool, log_level: Option<log::Level>) {
+    pub fn remote(path: &String, silent: bool, log_level: Option<tracing::Level>) {
         let values = helpers::maidfile::merge(path);
         let mut options: Vec<_> = values
             .tasks
             .iter()
             .map(|(key, task)| {
                 let info = match &task.info {
-                    Some(info) => ternary!(info.trim().len() < 1, string!("(no description)").bright_red(), format!("({info})").white()),
-                    None => string!("(no description)").bright_red(),
+                    Some(info) => match info.trim().len() < 1 {
+                        true => "(no description)".to_string().bright_red(),
+                        false => format!("({info})").white(),
+                    },
+                    None => "(no description)".to_string().bright_red(),
                 };
 
                 let verbose = match log_level.unwrap() {
-                    log::Level::ERROR => string!(),
+                    tracing::Level::ERROR => string!(),
                     _ => string!(task.script),
                 };
 
@@ -99,7 +105,7 @@ impl List {
         options.retain(|key| key.hidden == false);
         match Select::new("Select a remote task to run:", options).prompt() {
             Ok(task) => {
-                log::debug!("Starting {}", task.name);
+                debug!("Starting {}", task.name);
                 cli::exec(&String::from(task.name), &vec![String::from("")], &path, silent, false, true, log_level, false);
             }
 
diff --git a/maid/client/helpers/logger.rs b/maid/client/helpers/logger.rs
index d91148f..08bc460 100644
--- a/maid/client/helpers/logger.rs
+++ b/maid/client/helpers/logger.rs
@@ -2,17 +2,17 @@
 macro_rules! log {
     ($level:expr, $($arg:tt)*) => {{
         lazy_static::lazy_static! {
-            static ref LEVEL_COLORS: std::collections::HashMap<Level, (&'static str, &'static str)> = {
+            static ref LEVEL_COLORS: std::collections::HashMap<Level, (&'static str, colored::Color)> = {
                 let mut map = std::collections::HashMap::new();
-                map.insert(Level::Fatal, ("FATAL", "bright red"));
-                map.insert(Level::Docker, ("DOCKER", "bright yellow"));
-                map.insert(Level::Info, ("INFO", "cyan"));
-                map.insert(Level::Build, ("BUILD", "bright green"));
-                map.insert(Level::Success, ("SUCCESS", "green"));
-                map.insert(Level::Debug, ("DEBUG", "magenta"));
-                map.insert(Level::Notice, ("NOTICE", "bright blue"));
-                map.insert(Level::Warning, ("WARN", "yellow"));
-                map.insert(Level::Error, ("ERROR", "red"));
+                map.insert(Level::Fatal, ("FATAL", colored::Color::BrightRed));
+                map.insert(Level::Docker, ("DOCKER", colored::Color::BrightYellow));
+                map.insert(Level::Info, ("INFO", colored::Color::Cyan));
+                map.insert(Level::Build, ("BUILD", colored::Color::BrightGreen));
+                map.insert(Level::Success, ("SUCCESS", colored::Color::Green));
+                map.insert(Level::Debug, ("DEBUG", colored::Color::Magenta));
+                map.insert(Level::Notice, ("NOTICE", colored::Color::BrightBlue));
+                map.insert(Level::Warning, ("WARN", colored::Color::Yellow));
+                map.insert(Level::Error, ("ERROR", colored::Color::Red));
                 return map;
             };
         }
@@ -21,8 +21,8 @@ macro_rules! log {
             print!("{}", format_args!($($arg)*).to_string());
         } else {
             match LEVEL_COLORS.get(&$level) {
-                Some((level_text, color_func)) => {
-                    let level_text = level_text.color(color_func.to_string());
+                Some((level_text, color)) => {
+                    let level_text = level_text.color(*color);
                     println!("{} {}", level_text, format_args!($($arg)*).to_string())
                 }
                 None => println!("Unknown log level: {:?}", $level),
diff --git a/maid/client/helpers/maidfile.rs b/maid/client/helpers/maidfile.rs
index 5354625..6c7aad8 100644
--- a/maid/client/helpers/maidfile.rs
+++ b/maid/client/helpers/maidfile.rs
@@ -1,7 +1,6 @@
 use crate::parse;
 use crate::structs::{DisplayTask, Maidfile};
-
-use macros_rs::crashln;
+use maid::log::prelude::*;
 
 pub fn merge(path: &String) -> Maidfile {
     let mut values = parse::file::read_maidfile(path);
@@ -10,10 +9,7 @@ pub fn merge(path: &String) -> Maidfile {
     for import in imported_values.iter() {
         values = match merge_struct::merge(&values, &import) {
             Ok(merge) => merge,
-            Err(err) => {
-                log::warn!("{err}");
-                crashln!("Unable to import tasks.");
-            }
+            Err(err) => error!(%err, "Unable to import tasks"),
         };
     }
 
@@ -24,10 +20,7 @@ impl Maidfile {
     pub fn to_json(&self) -> String {
         match serde_json::to_string(&self) {
             Ok(contents) => contents,
-            Err(err) => {
-                log::warn!("{err}");
-                crashln!("Cannot read maidfile.");
-            }
+            Err(err) => error!(%err, "Cannot read Maidfile"),
         }
     }
 }
diff --git a/maid/client/helpers/status.rs b/maid/client/helpers/status.rs
index bd4c29b..12bdb71 100644
--- a/maid/client/helpers/status.rs
+++ b/maid/client/helpers/status.rs
@@ -1,29 +1,23 @@
-use macros_rs::{crashln, errorln};
+use maid::log::prelude::*;
 use std::{io::Error, process::ExitStatus};
 
 pub fn error(debug_err: &str) {
-    errorln!("Unable to parse maidfile. Contains unexpected {debug_err} values.");
+    error!("Unable to parse maidfile. Contains unexpected {debug_err} values.");
 }
 
 pub fn code(status: &Result<ExitStatus, Error>) -> i32 {
     match status.as_ref() {
         Ok(status) => match status.code() {
             Some(iter) => iter,
-            None => crashln!("Missing status value"),
+            None => error!("Missing status value"),
         },
-        Err(err) => {
-            log::warn!("{err}");
-            crashln!("Unknown error, check verbose.");
-        }
+        Err(err) => error!(%err, "Unknown error"),
     }
 }
 
 pub fn success(status: &Result<ExitStatus, Error>) -> bool {
     match status.as_ref() {
         Ok(status) => status.success(),
-        Err(err) => {
-            log::warn!("{err}");
-            crashln!("Unknown error, check verbose.");
-        }
+        Err(err) => error!(%err, "Unknown error"),
     }
 }
diff --git a/maid/client/helpers/string.rs b/maid/client/helpers/string.rs
index e99f525..0a05c95 100644
--- a/maid/client/helpers/string.rs
+++ b/maid/client/helpers/string.rs
@@ -1,9 +1,8 @@
-use colored::{ColoredString, Colorize};
 use std::path::Path;
 
 // cache
 use std::collections::HashMap;
-use std::sync::{Mutex, LazyLock};
+use std::sync::{LazyLock, Mutex};
 
 static STRING_CACHE: LazyLock<Mutex<HashMap<String, &'static str>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
 
@@ -12,7 +11,7 @@ pub(crate) fn path_to_str(path: &Path) -> &'static str {
     if let Some(cached) = STRING_CACHE.lock().unwrap().get(&string) {
         return cached;
     }
-    
+
     let leaked = Box::leak(string.clone().into_boxed_str());
     STRING_CACHE.lock().unwrap().insert(string, leaked);
     leaked
@@ -24,9 +23,3 @@ pub(crate) fn trim_start_end(value: &str) -> &str {
     chars.next_back();
     chars.as_str()
 }
-
-pub(crate) fn seperator() -> ColoredString { ":".white() }
-pub(crate) fn arrow_icon() -> ColoredString { "ยป".white() }
-pub(crate) fn add_icon() -> ColoredString { "+".green() }
-pub(crate) fn cross_icon() -> ColoredString { "โœ–".red() }
-pub(crate) fn check_icon() -> ColoredString { "โœ”".green() }
\ No newline at end of file
diff --git a/maid/client/main.rs b/maid/client/main.rs
index d9dcf91..58eebc4 100644
--- a/maid/client/main.rs
+++ b/maid/client/main.rs
@@ -8,11 +8,12 @@ mod structs;
 mod table;
 mod task;
 
+use maid::log::{InfoLevel, Verbosity};
+
 use clap::{Parser, ValueEnum};
-use cli::verbose::{NoneLevel, Verbosity};
-use tracing_subscriber::{fmt, prelude::*};
 use macros_rs::str;
 use std::path::Path;
+use tracing_subscriber::{fmt, prelude::*};
 
 macro_rules! dispatch {
     ($cli:expr, { $($flag:ident => $func:expr),+ $(,)? }) => {$(
@@ -29,50 +30,50 @@ struct Cli {
     /// Run a task defined in maidfile
     #[arg(default_value = "", hide_default_value = true)]
     task: Vec<String>,
-    
+
     /// Base path for Maidfile
     #[arg(short, long, default_value = "maidfile")]
     path: String,
-    
+
     /// Ignore cache on build
     #[arg(short, long)]
     force: bool,
-    
+
     /// Switch Maid to server mode
     #[arg(short, long, visible_alias = "online")]
     remote: bool,
-    
+
     /// Clear build cache
     #[arg(short = 'C', long, visible_alias = "purge", group = "commands")]
     clean_cache: bool,
-    
+
     /// Create new Maid project
     #[arg(short, long, group = "commands")]
     init: bool,
-    
+
     /// List all runnable tasks
     #[arg(short, long, visible_alias = "tasks", visible_alias = "ls", group = "commands")]
     list: bool,
-    
-    /// Watch for changes in specified path 
+
+    /// Watch for changes in specified path
     #[arg(short = 'W', long)]
     watch: Option<String>,
-    
+
     /// View Maid health (server health if enabled)
     #[arg(short = 'H', long, group = "commands")]
     health: bool,
-    
+
     /// Per project commands
     #[arg(short = 'w', long, group = "commands")]
     project: Option<Project>,
-    
+
     /// Management Maid commands
     #[arg(short = 'g', long, group = "commands")]
     system: Option<System>,
-    
+
     #[clap(flatten)]
-    verbose: Verbosity<NoneLevel>,
-    
+    verbose: Verbosity<InfoLevel>,
+
     /// Shows this quick reference
     #[clap(short, long, action = clap::ArgAction::HelpLong)]
     help: Option<bool>,
@@ -99,49 +100,46 @@ enum Project {
 fn main() {
     let cli = Cli::parse();
     let fmt_layer = fmt::layer().without_time();
-        
-    tracing_subscriber::registry()
-        .with(cli.verbose.log_level_filter())
-        .with(fmt_layer)
-        .init();
-        
+
+    tracing_subscriber::registry().with(cli.verbose.log_level_filter()).with(fmt_layer).init();
+
     globals::init();
-        
+
     dispatch!(cli, {
-        init => cli::butler::init(),
-        health => server::cli::connect(&cli.path), 
+        init => cli::dispatch::init(),
+        health => server::cli::connect(&cli.path),
         health => match cli.remote {
             true => server::cli::connect(&cli.path),
             false => server::cli::connect(&cli.path), // improve health command for later
         },
         clean_cache => match cli.remote {
             true => server::cli::connect(&cli.path),
-            false => cli::butler::clean(),
+            false => cli::dispatch::clean(),
         },
         list => match cli.remote {
             true => cli::tasks::List::remote(&cli.path, cli.verbose.is_silent(), cli.verbose.log_level()),
             false => cli::tasks::List::all(&cli.path, cli.verbose.is_silent(), cli.verbose.log_level(), cli.force),
         }
     });
-    
+
     if let Some(project) = cli.project {
         return match project {
             Project::Info => cli::info(&cli.path), // add more info
-            Project::Env => {}, // print env from maidfile
+            Project::Env => {}                     // print env from maidfile
         };
     }
-    
+
     if let Some(system) = cli.system {
         return match system {
-            System::Update => cli::butler::update(), // add real update checker
+            System::Update => cli::dispatch::update(), // add real update checker
             System::Json => cli::tasks::json(&cli.path, &cli.task, false),
             System::JsonHydrated => cli::tasks::json(&cli.path, &cli.task, true),
         };
     }
-    
+
     if let Some(path) = cli.watch {
-        return cli::butler::watch(Path::new(&path)); // migrate watch path into executer below
+        return cli::dispatch::watch(Path::new(&path)); // migrate watch path into executer below
     }
-    
+
     cli::exec(cli.task[0].trim(), &cli.task, &cli.path, cli.verbose.is_silent(), false, cli.remote, cli.verbose.log_level(), cli.force)
 }
diff --git a/maid/client/parse/file.rs b/maid/client/parse/file.rs
index 18d6f83..8d9a9ea 100644
--- a/maid/client/parse/file.rs
+++ b/maid/client/parse/file.rs
@@ -1,5 +1,6 @@
 use crate::structs::Maidfile;
-use colored::Colorize;
+use maid::log::prelude::*;
+
 use macros_rs::{crashln, string, then};
 use std::{env, fs, io::Result, path::Path, path::PathBuf};
 
@@ -80,7 +81,7 @@ fn read_file(path: PathBuf, kind: &str) -> Maidfile {
     let contents = match fs::read_to_string(&path) {
         Ok(contents) => contents,
         Err(err) => {
-            log::warn!("{}", err);
+            warn!("{}", err);
             crashln!("Cannot find maidfile. Does it exist?");
         }
     };
@@ -90,17 +91,12 @@ fn read_file(path: PathBuf, kind: &str) -> Maidfile {
         "json" => serde_json::from_str(&contents).map_err(|err| string!(err)),
         "json5" => json5::from_str(&contents).map_err(|err| string!(err)),
         "yaml" | "yml" => serde_yaml::from_str(&contents).map_err(|err| string!(err)),
-        _ => {
-            log::warn!("Invalid format");
-            crashln!("Cannot read maidfile.");
-        }
+        _ => error!("Invalid format, cannot read Maidfile"),
     };
 
     match result {
         Ok(parsed) => parsed,
-        Err(err) => {
-            crashln!("Cannot read maidfile.\n{}", err.white());
-        }
+        Err(err) => error!("Cannot read Maidfile.\n{}", err.white()),
     }
 }
 
@@ -109,22 +105,16 @@ pub fn read_maidfile_with_error(filename: &String, error: &str) -> Maidfile {
         Ok(path) => match find_file(&path, &filename) {
             Some(path) => {
                 let extension = path.extension().and_then(|s| s.to_str());
-                log::debug!(path = path.display().to_string(), kind = extension, "Found tasks");
-                
+                debug!(path = path.display().to_string(), kind = extension, "Found tasks");
+
                 match extension {
                     Some("yaml") | Some("yml") | Some("json") | Some("json5") => read_file(path.clone(), extension.unwrap()),
                     _ => read_file(path, "toml"),
                 }
             }
-            None => {
-                log::warn!("{error}");
-                crashln!("{error}");
-            }
+            None => error!("{error}"),
         },
-        Err(err) => {
-            log::warn!("{err}");
-            crashln!("Home directory could not found.");
-        }
+        Err(err) => error!(%err, "Home directory could not found"),
     }
 }
 
@@ -133,18 +123,12 @@ pub fn find_maidfile_root(filename: &String) -> PathBuf {
         Ok(path) => match find_file(&path, &filename) {
             Some(mut path) => {
                 path.pop();
-                log::info!("Found project path: {}", path.display());
+                debug!("Found project path: {}", path.display());
                 return path;
             }
-            None => {
-                log::warn!("Cannot find project root.");
-                crashln!("Cannot find project root.");
-            }
+            None => error!("Cannot find project root."),
         },
-        Err(err) => {
-            log::warn!("{err}");
-            crashln!("Home directory could not found.");
-        }
+        Err(err) => error!(%err, "Home directory could not found"),
     }
 }
 
diff --git a/maid/client/server/cli.rs b/maid/client/server/cli.rs
index ef776df..4f4f6aa 100644
--- a/maid/client/server/cli.rs
+++ b/maid/client/server/cli.rs
@@ -2,8 +2,8 @@ use crate::helpers;
 use crate::server;
 use crate::structs::{ConnectionData, ConnectionInfo, Kind, Level, Maidfile, Task, Websocket};
 
-use colored::Colorize;
 use macros_rs::{crashln, fmtstr};
+use maid::log::prelude::*;
 use reqwest::blocking::Client;
 use tungstenite::protocol::frame::{coding::CloseCode::Normal, CloseFrame};
 use tungstenite::{client::connect_with_config, client::IntoClientRequest, protocol::WebSocketConfig, Message};
@@ -14,20 +14,13 @@ fn health(client: Client, values: Maidfile) -> server::api::health::Route {
 
     let response = match client.get(fmtstr!("{address}/api/health")).header("Authorization", fmtstr!("Bearer {token}")).send() {
         Ok(res) => res,
-        Err(err) => {
-            log::warn!("{err}");
-            crashln!("Unable to connect to the maid server. Is it up?");
-        }
+        Err(err) => error!(%err, "Unable to connect to the maid server. Is it up?"),
     };
 
-    let body =
-        match response.json::<server::api::health::Route>() {
-            Ok(body) => body,
-            Err(err) => {
-                log::warn!("{err}");
-                crashln!("Unable to connect to the maid server. Is the token correct?")
-            }
-        };
+    let body = match response.json::<server::api::health::Route>() {
+        Ok(body) => body,
+        Err(err) => error!(%err, "Unable to connect to the maid server. Is the token correct?"),
+    };
 
     return body;
 }
@@ -102,7 +95,7 @@ pub fn remote(task: Task) {
     request.headers_mut().insert("Authorization", fmtstr!("Bearer {token}").parse().unwrap());
 
     let (mut socket, response) = connect_with_config(request, Some(websocket_config), 3).expect("Can't connect");
-    log::debug!("response code: {}", response.status());
+    debug!("response code: {}", response.status());
 
     let connection_data = ConnectionData {
         info: ConnectionInfo {
@@ -121,7 +114,7 @@ pub fn remote(task: Task) {
         }
     };
 
-    log::debug!("sending information");
+    debug!("sending information");
     socket.send(Message::Text(serde_json::to_string(&connection_data).unwrap())).unwrap();
 
     loop {
@@ -159,7 +152,7 @@ pub fn remote(task: Task) {
 
     server::file::remove_tar(&file_name);
     // run.rs:96 implement that later
-    println!("\n{} {}", helpers::string::check_icon(), "finished task successfully".bright_green());
+    println!("\n{} {}", maid::colors::OK, "finished task successfully".bright_green());
     println!("{}", "removed temporary archive".bright_magenta());
 
     if let Err(err) = socket.close(Some(CloseFrame {
diff --git a/maid/client/server/file.rs b/maid/client/server/file.rs
index bc11ede..0e864a9 100644
--- a/maid/client/server/file.rs
+++ b/maid/client/server/file.rs
@@ -3,6 +3,7 @@ use crate::helpers;
 use flate2::{read::GzDecoder, write::GzEncoder, Compression};
 use global_placeholders::global;
 use macros_rs::crashln;
+use maid::log::prelude::*;
 use std::{fs::write, fs::File, path::PathBuf};
 use tar::{Archive, Builder};
 use uuid::Uuid;
@@ -27,7 +28,7 @@ pub fn remove_tar(file: &String) {
 pub fn read_tar(archive: &Vec<u8>) -> Result<String, std::io::Error> {
     if !helpers::Exists::folder(global!("maid.temp_dir")).unwrap() {
         std::fs::create_dir_all(global!("maid.temp_dir")).unwrap();
-        log::debug!("created maid temp dir");
+        debug!("created maid temp dir");
     }
 
     let file_name = format!("{}/{}.tgz", global!("maid.temp_dir"), Uuid::new_v4());
@@ -47,7 +48,7 @@ pub fn unpack_tar(path: &String) -> std::io::Result<()> {
 pub fn write_tar(files: &Vec<String>) -> Result<String, std::io::Error> {
     if !helpers::Exists::folder(global!("maid.temp_dir")).unwrap() {
         std::fs::create_dir_all(global!("maid.temp_dir")).unwrap();
-        log::debug!("created maid temp dir");
+        debug!("created maid temp dir");
     }
 
     let file_name = format!("{}/{}.tgz", global!("maid.temp_dir"), Uuid::new_v4());
@@ -55,10 +56,10 @@ pub fn write_tar(files: &Vec<String>) -> Result<String, std::io::Error> {
     let enc = GzEncoder::new(archive, Compression::default());
     let mut tar = Builder::new(enc);
 
-    log::debug!("compressing to {}", &file_name);
+    debug!("compressing to {}", &file_name);
     for path in files {
         append_to_tar(&mut tar, path)?;
-        log::debug!("{} {:?}", helpers::string::add_icon(), path);
+        debug!("{} {:?}", maid::colors::ADD, path);
     }
 
     Ok(file_name)
diff --git a/maid/client/table.rs b/maid/client/table.rs
index 70c05f8..ec03a8b 100644
--- a/maid/client/table.rs
+++ b/maid/client/table.rs
@@ -1,7 +1,8 @@
 use crate::helpers;
 use crate::structs::Maidfile;
+use maid::log::prelude::*;
 
-use macros_rs::{errorln, str, ternary};
+use macros_rs::{str, ternary};
 use std::path::PathBuf;
 use std::{collections::BTreeMap, collections::HashMap, env};
 use text_placeholder::Template;
@@ -14,35 +15,30 @@ pub fn create(values: Maidfile, args: &Vec<String>, project: PathBuf) -> HashMap
     table.insert("os.platform", env::consts::OS);
     table.insert("os.arch", env::consts::ARCH);
 
-    log::trace!(os_platform = env::consts::OS);
-    log::trace!(os_arch = env::consts::ARCH);
+    trace!(os_platform = env::consts::OS);
+    trace!(os_arch = env::consts::ARCH);
 
     match env::current_dir() {
         Ok(path) => {
             table.insert("dir.current", helpers::string::path_to_str(&path));
-            log::trace!(dir_current = path.display().to_string());
-        }
-        Err(err) => {
-            log::warn!("{err}");
-            errorln!("Current directory could not be added as script variable.");
+            trace!(dir_current = path.display().to_string());
         }
+        Err(err) => error!(%err, "Current directory could not be added as script variable."),
     }
 
     match home::home_dir() {
         Some(path) => {
             table.insert("dir.home", helpers::string::path_to_str(&path));
-            log::trace!(dir_home = path.display().to_string());
-        }
-        None => {
-            errorln!("Home directory could not be added as script variable.");
+            trace!(dir_home = path.display().to_string());
         }
+        None => error!("Home directory could not be added as script variable."),
     }
 
     table.insert("dir.project", helpers::string::path_to_str(&project));
-    log::trace!(dir_project = project.display().to_string());
+    trace!(dir_project = project.display().to_string());
 
     for (pos, arg) in args.iter().enumerate() {
-        log::trace!("arg_{pos} = \"{arg}\"");
+        trace!("arg_{pos} = \"{arg}\"");
         table.insert(str!(format!("arg.{pos}")), arg);
     }
 
@@ -59,9 +55,9 @@ pub fn create(values: Maidfile, args: &Vec<String>, project: PathBuf) -> HashMap
         );
 
         env::set_var(key, value_formatted.clone());
-        log::trace!("env_{key} = \"{value_formatted}\"");
+        trace!("env_{key} = \"{value_formatted}\"");
         table.insert(str!(format!("env.{}", key.clone())), str!(value_formatted));
     }
-    
+
     return table;
 }
diff --git a/maid/server/docker/run.rs b/maid/server/docker/run.rs
index ec689c6..91f858c 100644
--- a/maid/server/docker/run.rs
+++ b/maid/server/docker/run.rs
@@ -2,14 +2,16 @@ macro_rules! Handle {
     ($id:ident, $socket:ident, $expr:expr $(, || $callback:expr)?) => {
         $( $callback; )?
         if let Err(err) = $expr {
-            log::error!("{err}");
+            warn!("{err}");
             $socket.remove_container(&$id, Some(RemoveContainerOptions { force: true, ..Default::default() })).await?;
-            log::warn!("removed old container");
+            warn!("removed old container");
         }
     };
 }
 
 use crate::{structs::ConnectionData, table, Kind, Level, Response};
+use maid::log::prelude::*;
+
 use bytes::Bytes;
 use flate2::{write::GzEncoder, Compression};
 use futures_core::Stream;
@@ -48,7 +50,7 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
                 Ok(value) => {
                     parsed = Some(value);
                 }
-                Err(err) => log::error!("Failed to deserialize JSON: {:?}", err),
+                Err(err) => error!("Failed to deserialize JSON: {:?}", err),
             }
         }
     }
@@ -57,7 +59,7 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
     let name = &parsed.info.name;
     let image = parsed.info.remote.image.clone();
 
-    log::info!("creating container (task={name}, image={})", image);
+    info!("creating container (task={name}, image={})", image);
 
     let image_config = CreateImageOptions {
         from_image: str!(image.clone()),
@@ -65,7 +67,7 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
     };
 
     let mut container = socket.create_image(Some(image_config), None, None);
-    log::info!("image created");
+    info!("image created");
 
     while let Some(message) = container.next().await {
         let message = message.as_ref().expect("Failed to get CreateImageInfo");
@@ -75,12 +77,11 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
             message.progress.clone().unwrap_or_else(|| string!(""))
         );
 
-        let docker_message =
-            Response {
-                level: Level::Docker,
-                kind: Kind::Message,
-                message: Some(formatted),
-            };
+        let docker_message = Response {
+            level: Level::Docker,
+            kind: Kind::Message,
+            message: Some(formatted),
+        };
 
         stream.send(docker_message.into()).await?;
     }
@@ -92,9 +93,9 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
     };
 
     let id = socket.create_container::<&str, String>(None, config).await?.id;
-    log::info!("created container");
+    info!("created container");
 
-    Handle!(id, socket, socket.start_container::<String>(&id, None).await, || log::info!("started container"));
+    Handle!(id, socket, socket.start_container::<String>(&id, None).await, || info!("started container"));
 
     let binary_message = Response {
         level: Level::Success,
@@ -105,14 +106,14 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
     stream.send(binary_message.into()).await?;
 
     if let Some(result) = stream.next().await {
-        log::info!("received message: binary");
+        info!("received message: binary");
 
         let msg = result?;
         let bytes_to_body = |bytes: &[u8]| -> rocket::http::hyper::Body { rocket::http::hyper::Body::from(bytes.to_vec()) };
         let upload_options = UploadToContainerOptions { path: "/opt", ..Default::default() };
 
         Handle!(id, socket, socket.upload_to_container(&id, Some(upload_options), bytes_to_body(&msg.into_data())).await);
-        log::info!("wrote tarfile to container");
+        info!("wrote tarfile to container");
     }
 
     let dependencies = match &parsed.maidfile.tasks[&parsed.info.name].depends {
@@ -177,18 +178,17 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
 
                 Handle!(id, socket, stream.send(output_message.into()).await);
             } else if let Err(err) = msg {
-                log::error!("{err}");
+                warn!("{err}");
             }
         }
     }
 
-    let res =
-        socket.download_from_container(
-            &id,
-            Some(DownloadFromContainerOptions {
-                path: fmtstr!("/opt/{}", parsed.info.remote.pull.clone()),
-            }),
-        );
+    let res = socket.download_from_container(
+        &id,
+        Some(DownloadFromContainerOptions {
+            path: fmtstr!("/opt/{}", parsed.info.remote.pull.clone()),
+        }),
+    );
 
     let bytes = concat_byte_stream(res).await?;
     let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
@@ -197,7 +197,7 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
     let compressed_data = encoder.finish()?;
 
     Handle!(id, socket, stream.send(Message::binary(compressed_data)).await);
-    log::info!("sent message: binary, from [{}]", parsed.info.remote.pull);
+    info!("sent message: binary, from [{}]", parsed.info.remote.pull);
 
     let done_message = Response {
         level: Level::Success,
@@ -206,10 +206,10 @@ pub async fn exec(mut stream: DuplexStream, docker: &Result<Docker, anyhow::Erro
     };
 
     stream.send(done_message.into()).await?;
-    log::info!("sent message: [done]");
+    info!("sent message: [done]");
 
     socket.remove_container(&id, Some(RemoveContainerOptions { force: true, ..Default::default() })).await?;
-    log::info!("removed old container");
+    info!("removed old container");
 
     Ok(())
 }
diff --git a/maid/server/helpers/file.rs b/maid/server/helpers/file.rs
index 498eb20..4c1eb72 100644
--- a/maid/server/helpers/file.rs
+++ b/maid/server/helpers/file.rs
@@ -1,4 +1,5 @@
 use crate::helpers;
+use maid::log::prelude::*;
 
 use flate2::{read::GzDecoder, write::GzEncoder, Compression};
 use global_placeholders::global;
@@ -27,7 +28,7 @@ pub fn remove_tar(file: &String) {
 pub fn read_tar(archive: &Vec<u8>) -> Result<String, std::io::Error> {
     if !helpers::Exists::folder(global!("maid.temp_dir")).unwrap() {
         std::fs::create_dir_all(global!("maid.temp_dir")).unwrap();
-        log::info!("created maid temp dir");
+        info!("created maid temp dir");
     }
 
     let file_name = format!("{}/{}.tgz", global!("maid.temp_dir"), Uuid::new_v4());
@@ -47,7 +48,7 @@ pub fn unpack_tar(path: &String) -> std::io::Result<()> {
 pub fn write_tar(files: &Vec<String>) -> Result<String, std::io::Error> {
     if !helpers::Exists::folder(global!("maid.temp_dir")).unwrap() {
         std::fs::create_dir_all(global!("maid.temp_dir")).unwrap();
-        log::info!("created maid temp dir");
+        info!("created maid temp dir");
     }
 
     let file_name = format!("{}/{}.tgz", global!("maid.temp_dir"), Uuid::new_v4());
@@ -55,10 +56,10 @@ pub fn write_tar(files: &Vec<String>) -> Result<String, std::io::Error> {
     let enc = GzEncoder::new(archive, Compression::default());
     let mut tar = Builder::new(enc);
 
-    log::info!("compressing to {}", &file_name);
+    info!("compressing to {}", &file_name);
     for path in files {
         append_to_tar(&mut tar, path)?;
-        log::info!("{} {:?}", helpers::string::add_icon(), path);
+        info!("{} {:?}", helpers::string::add_icon(), path);
     }
 
     Ok(file_name)
diff --git a/maid/server/main.rs b/maid/server/main.rs
index 832d027..9562196 100644
--- a/maid/server/main.rs
+++ b/maid/server/main.rs
@@ -7,6 +7,7 @@ mod table;
 use bollard::{Docker, API_DEFAULT_VERSION};
 use docker::container;
 use macros_rs::{fmtstr, ternary};
+use maid::log::prelude::*;
 use rocket::futures::SinkExt;
 use rocket::{get, http::Status, launch, outcome::Outcome, routes, State};
 use rocket_ws::{Channel, Message, WebSocket};
@@ -136,8 +137,8 @@ fn stream(ws: WebSocket, docker_state: &State<DockerState>, _token: Token) -> Ch
             stream.send(connect_success.into()).await?;
 
             match docker::run::exec(stream, &docker_state.docker).await {
-                Ok(_) => log::info!("build finished"),
-                Err(_) => log::error!("failed to build"),
+                Ok(_) => info!("build finished"),
+                Err(_) => warn!("failed to build"),
             };
 
             Ok(())
diff --git a/maid/server/table.rs b/maid/server/table.rs
index 8001057..120a1f1 100644
--- a/maid/server/table.rs
+++ b/maid/server/table.rs
@@ -1,9 +1,9 @@
 use crate::helpers;
 use crate::structs::Maidfile;
+use maid::log::prelude::*;
 
-use colored::Colorize;
-use macros_rs::{errorln, str, ternary};
-use serde_json::{json, Value};
+use macros_rs::{str, ternary};
+use serde_json::Value;
 use std::path::PathBuf;
 use std::{collections::BTreeMap, collections::HashMap, env};
 use text_placeholder::Template;
@@ -15,36 +15,30 @@ pub fn create(values: Maidfile, args: &Vec<String>, project: PathBuf) -> HashMap
     table.insert("os.platform", env::consts::OS);
     table.insert("os.arch", env::consts::ARCH);
 
-    log::info!("{} os.platform: '{}'", helpers::string::add_icon(), env::consts::OS.yellow());
-    log::info!("{} os.arch: '{}'", helpers::string::add_icon(), env::consts::ARCH.yellow());
+    trace!(os_platform = env::consts::OS);
+    trace!(os_arch = env::consts::ARCH);
 
     match env::current_dir() {
         Ok(path) => {
             table.insert("dir.current", helpers::string::path_to_str(&path));
-            log::info!("{} dir.current: '{}'", helpers::string::add_icon(), helpers::string::path_to_str(&path).yellow());
-        }
-        Err(err) => {
-            log::warn!("{err}");
-            errorln!("Current directory could not be added as script variable.");
+            trace!(dir_current = path.display().to_string());
         }
+        Err(err) => error!(%err, "Current directory could not be added as script variable."),
     }
 
     match home::home_dir() {
         Some(path) => {
             table.insert("dir.home", helpers::string::path_to_str(&path));
-            log::info!("{} dir.home: '{}'", helpers::string::add_icon(), helpers::string::path_to_str(&path).yellow());
-        }
-        None => {
-            errorln!("Home directory could not be added as script variable.");
+            trace!(dir_home = path.display().to_string());
         }
+        None => error!("Home directory could not be added as script variable."),
     }
 
-    let project_root = helpers::string::path_to_str(&project);
-    table.insert("dir.project", project_root);
-    log::info!("{} dir.project: '{}'", helpers::string::add_icon(), project_root.yellow());
+    table.insert("dir.project", helpers::string::path_to_str(&project));
+    trace!(dir_project = project.display().to_string());
 
     for (pos, arg) in args.iter().enumerate() {
-        log::info!("{} arg.{pos}: '{}'", helpers::string::add_icon(), arg.yellow());
+        trace!("arg_{pos} = \"{arg}\"");
         table.insert(str!(format!("arg.{pos}")), arg);
     }
 
@@ -61,11 +55,9 @@ pub fn create(values: Maidfile, args: &Vec<String>, project: PathBuf) -> HashMap
         );
 
         env::set_var(key, value_formatted.clone());
-        log::info!("{} env.{key}: '{}'", helpers::string::add_icon(), value_formatted.yellow());
+        trace!("env_{key} = \"{value_formatted}\"");
         table.insert(str!(format!("env.{}", key.clone())), str!(value_formatted));
     }
 
-    log::trace!("{}", json!({ "env": table }));
-
     return table;
 }
diff --git a/maid/shared/colors.rs b/maid/shared/colors.rs
new file mode 100644
index 0000000..c1449a9
--- /dev/null
+++ b/maid/shared/colors.rs
@@ -0,0 +1,36 @@
+use colored::{ColoredString, Colorize};
+use std::{fmt, ops::Deref, sync::OnceLock};
+
+pub struct LazyColoredString {
+    inner: OnceLock<ColoredString>,
+    initializer: fn() -> ColoredString,
+}
+
+impl LazyColoredString {
+    const fn new(initializer: fn() -> ColoredString) -> Self { LazyColoredString { inner: OnceLock::new(), initializer } }
+    fn get(&self) -> &ColoredString { self.inner.get_or_init(self.initializer) }
+}
+
+impl Deref for LazyColoredString {
+    type Target = ColoredString;
+    fn deref(&self) -> &Self::Target { self.get() }
+}
+
+impl fmt::Display for LazyColoredString {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.get(), f) }
+}
+
+macro_rules! create_symbols {
+    ($($name:ident: $style:ident->$text:expr),* $(,)?) => {$(
+        pub static $name: LazyColoredString = LazyColoredString::new(|| $text.$style());
+    )*};
+}
+
+create_symbols! {
+    SEP: white->":",
+    ARROW: white->"ยป",
+    ADD: green->"+",
+    WARN: yellow->"!",
+    FAIL: red->"โœ–",
+    OK: green->"โœ”",
+}
diff --git a/maid/shared/lib.rs b/maid/shared/lib.rs
new file mode 100644
index 0000000..7746688
--- /dev/null
+++ b/maid/shared/lib.rs
@@ -0,0 +1,2 @@
+pub mod colors;
+pub mod log;
diff --git a/maid/client/cli/verbose.rs b/maid/shared/log.rs
similarity index 80%
rename from maid/client/cli/verbose.rs
rename to maid/shared/log.rs
index 3cbd6ff..0326213 100644
--- a/maid/client/cli/verbose.rs
+++ b/maid/shared/log.rs
@@ -1,5 +1,46 @@
-use log::{level_filters::LevelFilter, Level};
 use std::fmt;
+use tracing::{level_filters::LevelFilter, Level};
+
+pub mod prelude {
+    pub use crate::{debug, error, info, trace, warn};
+    pub use colored::{ColoredString, Colorize};
+}
+
+#[macro_export]
+macro_rules! info {
+    ($($arg:tt)*) => {
+        tracing::info!($($arg)*)
+    }
+}
+
+#[macro_export]
+macro_rules! warn {
+    ($($arg:tt)*) => {
+        tracing::warn!($($arg)*)
+    }
+}
+
+#[macro_export]
+macro_rules! error {
+    ($($arg:tt)*) => {{
+        tracing::error!($($arg)*);
+        std::process::exit(1);
+    }}
+}
+
+#[macro_export]
+macro_rules! debug {
+    ($($arg:tt)*) => {
+        tracing::debug!($($arg)*)
+    }
+}
+
+#[macro_export]
+macro_rules! trace {
+    ($($arg:tt)*) => {
+        tracing::trace!($($arg)*)
+    }
+}
 
 #[derive(clap::Args, Debug, Clone, Default)]
 pub struct Verbosity<L: LogLevel = ErrorLevel> {
@@ -107,10 +148,3 @@ pub struct InfoLevel;
 impl LogLevel for InfoLevel {
     fn default() -> Option<Level> { return Some(Level::INFO); }
 }
-
-#[derive(Copy, Clone, Debug, Default)]
-pub struct NoneLevel;
-
-impl LogLevel for NoneLevel {
-    fn default() -> Option<Level> { None }
-}
-- 
GitLab