saydbg 0.2.1

A tiny macro for conditional debug printing with optional colored output.
Documentation
//! # saydbg
//!
//! A lightweight crate for conditional debug printing.
//!
//! - Prints only in debug builds (`debug_assertions`)
//! - Optional color, timestamps, and file logging
//! - Zero overhead in release builds
//!
//! ## Features
//! - `color`: enable colorized output
//! - `timestamp`: include local timestamp in log prefix
//! - `file`: mirror output to `saydbg.log`
//!
//! ## Example
//! ```rust
//! use saydbg::{saydbg, saywarn, sayerr, saytrace, saylog};
//!
//! fn main() {
//!     saydbg!("Connected to database");
//!     saywarn!("Missing optional config file");
//!     saytrace!("Query took {:?} ms", 5);
//!     sayerr!("User not found: {}", "admin");
//!     saylog!("Always visible log in both debug and release");
//! }
//! ```

#[cfg(feature = "file")]
use once_cell::sync::Lazy;
#[cfg(feature = "file")]
use std::{fs::OpenOptions, io::Write};

#[cfg(feature = "file")]
static LOG_FILE: Lazy<std::sync::Mutex<std::fs::File>> = Lazy::new(|| {
    let file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("saydbg.log")
        .expect("Unable to open saydbg.log");
    std::sync::Mutex::new(file)
});

#[cfg(feature = "file")]
pub fn write_to_log_file(line: &str) {
    if let Ok(mut file) = LOG_FILE.lock() {
        let _ = writeln!(file, "{}", line);
    }
}

#[cfg(feature = "timestamp")]
use chrono::Local;
#[cfg(feature = "color")]
use colored::Colorize;

/// Writes a line to the log file if the `file` feature is enabled.
#[inline(always)]
pub fn maybe_write_file(_line: &str) {
    #[cfg(feature = "file")]
    {
        crate::write_to_log_file(_line);
    }
}

/// Internal helper: builds timestamp + colored prefix.
pub fn build_prefix(label: &str, _color: Option<&str>) -> String {
    #[cfg(feature = "timestamp")]
    let time = format!("[{}]", Local::now().format("%Y-%m-%d %H:%M:%S"));
    #[cfg(not(feature = "timestamp"))]
    let time = String::new();

    #[cfg(feature = "color")]
    let label_colored = match _color {
        Some("red") => label.bright_red(),
        Some("green") => label.green(),
        Some("blue") => label.bright_blue(),
        Some("yellow") => label.yellow(),
        Some("gray") => label.dimmed(),
        _ => label.normal(),
    }
    .to_string();

    #[cfg(not(feature = "color"))]
    let label_colored = label.to_string();

    if !time.is_empty() {
        format!("{} {}", time, label_colored)
    } else {
        label_colored
    }
}

/// Prints `[debug]` messages in debug builds.
#[macro_export]
macro_rules! saydbg {
    ($($arg:tt)*) => {
        #[cfg(debug_assertions)]
        {
            let prefix = $crate::build_prefix("[debug]", Some("blue"));
            let line = format!("{} {}", prefix, format!($($arg)*));
            println!("{}", line);
            $crate::maybe_write_file(&line);
        }
    };
}

/// Prints `[error]` messages to stderr in debug builds.
#[macro_export]
macro_rules! sayerr {
    ($($arg:tt)*) => {
        #[cfg(debug_assertions)]
        {
            let prefix = $crate::build_prefix("[error]", Some("red"));
            let line = format!("{} {}", prefix, format!($($arg)*));
            eprintln!("{}", line);
            $crate::maybe_write_file(&line);
        }
    };
}

/// Prints `[warn]` messages in debug builds.
#[macro_export]
macro_rules! saywarn {
    ($($arg:tt)*) => {
        #[cfg(debug_assertions)]
        {
            let prefix = $crate::build_prefix("[warn]", Some("yellow"));
            let line = format!("{} {}", prefix, format!($($arg)*));
            println!("{}", line);
            $crate::maybe_write_file(&line);
        }
    };
}

/// Prints `[trace]` messages in debug builds (very low-level info).
#[macro_export]
macro_rules! saytrace {
    ($($arg:tt)*) => {
        #[cfg(debug_assertions)]
        {
            let prefix = $crate::build_prefix("[trace]", Some("gray"));
            let line = format!("{} {}", prefix, format!($($arg)*));
            println!("{}", line);
            $crate::maybe_write_file(&line);
        }
    };
}

/// Always-on log (prints in all builds, including release).
#[macro_export]
macro_rules! saylog {
    ($($arg:tt)*) => {
        {
            let prefix = $crate::build_prefix("[log]", Some("green"));
            let line = format!("{} {}", prefix, format!($($arg)*));
            println!("{}", line);
            $crate::maybe_write_file(&line);
        }
    };
}