From 520fd4f1e6696da026e6f76079f3f8ad4a85ae50 Mon Sep 17 00:00:00 2001 From: danielvici123 <94993276+danielvici@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:13:39 +0200 Subject: [PATCH] feat: Add colorized request logging --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 1 + README.md | 5 ++++- src/main.rs | 25 +++++++++++++------------ src/middleware.rs | 31 ++++++++++++++++++++++++------- 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4ff05e..1be7138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,6 +200,16 @@ dependencies = [ "cc", ] +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.52.0", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -213,6 +223,7 @@ dependencies = [ "axum", "axum-server", "chrono", + "colored", "serde", "serde_json", "tokio", @@ -508,6 +519,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.186" diff --git a/Cargo.toml b/Cargo.toml index 354d600..a940d81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ serde_json = "1.0" axum-server = { version = "0.7", features = ["tls-rustls"] } tower-http = { version = "0.5", features = ["trace"] } chrono = "0.4" +colored = "2.1" diff --git a/README.md b/README.md index 9bb754d..9b47ba3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,10 @@ DHL is a lightweight, configurable HTTP/HTTPS mock server built with Rust and Ax - **Live Configuration Reloading**: Changes to `config.json` (routes, status codes, etc.) and response files are applied immediately without restarting the server. - **Multi-Route Support**: Define as many paths as you need. - **JSON Response Files**: Responses are loaded from external JSON files. -- **Request Logging**: Detailed logging of incoming requests, including methods, paths, and headers. +- **Colorized Request Logging**: Detailed logging of incoming requests with terminal colors. Status codes are color-coded for quick identification: + - **2xx Success**: Green + - **4xx Client Error**: Yellow + - **5xx Server Error**: Red - **Header Masking**: Sensitive headers like `Authorization` can be masked in logs. - **HTTPS/TLS Support**: Optional TLS support for secure connections. - **Automatic Default Generation**: Creates default configuration and response files if they are missing. diff --git a/src/main.rs b/src/main.rs index 2537935..b6681a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use crate::config::Config; use crate::state::AppState; use crate::handlers::{handler, fallback_handler}; use crate::middleware::logging_middleware; +use colored::*; #[tokio::main] async fn main() -> Result<(), Box> { @@ -71,17 +72,17 @@ async fn main() -> Result<(), Box> { } fn print_banner(config: &Config) { - println!("------------------------------------------"); - println!(" ____ ___ _ ______________ "); - println!(" / __ |/ | / | / / _/ ____/ / "); - println!(" / / / / /| | / |/ // // __/ / / "); - println!(" / /_/ / ___ |/ /| // // /___/ /___ "); - println!(" /_____/_/ |_/_/ |_/___/_____/_____/ "); - println!("------------------------------------------"); + println!("{}", "------------------------------------------".bright_black()); + println!("{}", " ____ ___ _ ______________ ".blue().bold()); + println!("{}", " / __ |/ | / | / / _/ ____/ / ".blue().bold()); + println!("{}", " / / / / /| | / |/ // // __/ / / ".blue().bold()); + println!("{}", " / /_/ / ___ |/ /| // // /___/ /___ ".blue().bold()); + println!("{}", " /_____/_/ |_/_/ |_/___/_____/_____/ ".blue().bold()); println!(" "); - println!("------ HTTP LISTENER ------"); - println!("host : {}", config.host); - println!("port : {}", config.port); - println!("Live Config Reloading: ENABLED"); - println!("------------------------------------------"); + println!("{}", "Config: ".bold().underline()); + println!(" "); + println!("{:<14}: {}", "host".bright_black(), config.host.yellow()); + println!("{:<14}: {}", "port".bright_black(), config.port.to_string().yellow()); + println!("{:<14}: {}", "Live Reload".bright_black(), "ENABLED".green().bold()); + println!("{}", "------------------------------------------".bright_black()); } diff --git a/src/middleware.rs b/src/middleware.rs index 884cea7..e110a10 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -7,6 +7,7 @@ use axum::{ use chrono::Local; use std::time::Instant; use crate::state::SharedState; +use colored::*; // Note: Axum body might need to be imported differently depending on version // but since the original used axum::body::Body, I'll stick to that or similar. @@ -23,10 +24,13 @@ pub async fn logging_middleware( // Load config live for masking settings let config = crate::config::Config::load(); - println!("---- REQUEST START {} ----", start_time.format("%Y-%m-%d %H:%M:%S")); - println!("Method: {}", req.method()); - println!("Path: {}", req.uri().path()); - println!("Headers:"); + let method = req.method().clone(); + let path = req.uri().path().to_string(); + + println!("{}", format!("---- REQUEST START {} ----", start_time.format("%Y-%m-%d %H:%M:%S")).blue().bold()); + println!("{}: {}", "Method".bright_black(), method); + println!("{}: {}", "Path".bright_black(), path); + println!("{}:", "Headers".bright_black()); for (name, value) in req.headers() { let name_str = name.as_str(); @@ -35,9 +39,9 @@ pub async fn logging_middleware( config.masking.headers.iter().any(|h| h.eq_ignore_ascii_case(name_str)); if should_mask { - println!(" {}: ***", name_str); + println!(" {}: {}", name_str.bright_black(), "***".yellow()); } else { - println!(" {}: {:?}", name_str, value); + println!(" {}: {:?}", name_str.bright_black(), value); } } @@ -45,8 +49,21 @@ pub async fn logging_middleware( let end_time = Local::now(); let duration = start_instant.elapsed(); + let status = response.status(); - println!("--- REQUEST END {} - {:?} ---------", end_time.format("%H:%M:%S"), duration); + let status_colored = if status.is_success() { + status.to_string().green() + } else if status.is_server_error() { + status.to_string().red() + } else if status.is_client_error() { + status.to_string().yellow() + } else { + status.to_string().cyan() + }; + + println!("{}: {}", "Status".bright_black(), status_colored.bold()); + println!("{}", format!("--- REQUEST END {} - {:?} ---------", end_time.format("%H:%M:%S"), duration).blue().bold()); + println!(); response }