From 175c50654db4ed65674e3eabe1c94e39c2cbfc2a Mon Sep 17 00:00:00 2001 From: kingecg Date: Tue, 30 Sep 2025 00:06:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(log4r):=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=B9=B6=E5=AE=9E=E7=8E=B0=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增了符合 log4j 风格的日志框架,支持五种日志级别(Trace、Debug、Info、Warn、Error), 并通过宏调用方式输出日志。默认提供控制台输出 Appender 和简单布局格式。 同时添加了基本使用示例和相关依赖配置。 --- .gitignore | 1 + Cargo.lock | 298 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 7 + examples/advanced_usage.rs | 18 +++ examples/basic_usage.rs | 18 +++ src/lib.rs | 205 +++++++++++++++++++++++++ 6 files changed, 547 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 examples/advanced_usage.rs create mode 100644 examples/basic_usage.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e19517b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,298 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "log4r" +version = "0.1.0" +dependencies = [ + "chrono", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a70dad2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "log4r" +version = "0.1.0" +edition = "2024" + +[dependencies] +chrono = "0.4.42" diff --git a/examples/advanced_usage.rs b/examples/advanced_usage.rs new file mode 100644 index 0000000..33eae10 --- /dev/null +++ b/examples/advanced_usage.rs @@ -0,0 +1,18 @@ +use log4r::{LogLevel, Logger}; + +fn main() { + // Initialize the logger with Debug level + Logger::init(LogLevel::Debug); + + // Test all log levels + log4r::trace!("This is a trace message - won't be printed"); + log4r::debug!("This is a debug message - will be printed"); + log4r::info!("This is an info message - will be printed"); + log4r::warn!("This is a warning message - will be printed"); + log4r::error!("This is an error message - will be printed"); + + println!("\n--- Setting log level to Trace ---"); + // Note: In a real implementation, we would have a way to reconfigure the logger + // For now, we're just showing what trace level would look like + log4r::trace!("This is a trace message - would be printed with Trace level"); +} \ No newline at end of file diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs new file mode 100644 index 0000000..8af9577 --- /dev/null +++ b/examples/basic_usage.rs @@ -0,0 +1,18 @@ +use log4r::{LogLevel, Logger}; + +fn main() { + // Initialize the logger with Debug level + Logger::init(LogLevel::Debug); + + // Test all log levels + log4r::trace!("This is a trace message - won't be printed"); + log4r::debug!("This is a debug message - will be printed"); + log4r::info!("This is an info message - will be printed"); + log4r::warn!("This is a warning message - will be printed"); + log4r::error!("This is an error message - will be printed"); + + // Change log level to trace to see all messages + println!("\nChanging log level to Trace:"); + Logger::init(LogLevel::Trace); + log4r::trace!("This is a trace message - now will be printed"); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9523618 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,205 @@ +//! # log4r - A log4j-like logging framework for Rust +//! +//! This crate provides a simple, extensible logging framework similar to log4j. +//! +//! ## Example +//! +//! ``` +//! use log4r::{LogLevel, Logger}; +//! +//! Logger::init(LogLevel::Debug); +//! +//! log4r::trace!("This is a trace message"); +//! log4r::debug!("This is a debug message"); +//! log4r::info!("This is an info message"); +//! log4r::warn!("This is a warning message"); +//! log4r::error!("This is an error message"); +//! ``` + +use std::{sync::{Mutex, OnceLock}, time}; + +/// Log levels compatible with log4j +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum LogLevel { + /// The "trace" level. + Trace, + /// The "debug" level. + Debug, + /// The "info" level. + Info, + /// The "warn" level. + Warn, + /// The "error" level. + Error, +} + +impl LogLevel { + /// Returns the string representation of the log level + pub fn as_str(&self) -> &'static str { + match self { + LogLevel::Trace => "TRACE", + LogLevel::Debug => "DEBUG", + LogLevel::Info => "INFO", + LogLevel::Warn => "WARN", + LogLevel::Error => "ERROR", + } + } +} + +/// Log event containing all information about a log message +pub struct LogEvent<'a> { + /// The level of the log event + pub level: LogLevel, + /// The message to log + pub message: &'a str, + /// The timestamp when the event occurred + pub timestamp: std::time::SystemTime, +} + +/// Trait for formatting log events into strings +pub trait Layout: Send + Sync { + /// Format a log event into a string + fn format(&self, event: &LogEvent) -> String; +} + +/// Simple layout that formats log events as "[LEVEL] message" +pub struct SimpleLayout; + +impl Layout for SimpleLayout { + fn format(&self, event: &LogEvent) -> String { + format!("[{:<5}] {} {}", event.level.as_str(), get_now_str(event.timestamp), event.message) + } +} + +/// Trait for output targets (console, file, etc.) +pub trait Appender: Send + Sync { + /// Append a log event to the output target + fn append(&self, event: &LogEvent); +} + +/// Console appender that writes to stdout +pub struct ConsoleAppender { + layout: Box, +} + +impl ConsoleAppender { + /// Create a new console appender with a specific layout + pub fn new(layout: Box) -> Self { + Self { layout } + } +} + +impl Appender for ConsoleAppender { + fn append(&self, event: &LogEvent) { + println!("{}", self.layout.format(event)); + } +} + +static LOGGER: OnceLock>> = OnceLock::new(); + +/// The logger struct that holds the current log level +pub struct Logger { + level: LogLevel, + appenders: Vec>, +} +fn get_now_str(sys_time: time::SystemTime) -> String { + use chrono::{Local, DateTime}; + let dt = DateTime::::from(sys_time); + return dt.format("%Y-%m-%d %H:%M:%S").to_string(); +} +impl Logger { + /// Initialize the logger with a specific log level + pub fn init(level: LogLevel) { + let console_appender = Box::new(ConsoleAppender::new(Box::new(SimpleLayout))); + let logger = Logger { + level, + appenders: vec![console_appender], + }; + LOGGER.set(Mutex::new(Some(logger))).ok(); + } + + /// Get the current logger instance + pub fn get() -> Option>> { + LOGGER.get().map(|logger| logger.lock().unwrap()) + } + + /// Check if a message at the specified level should be logged + fn should_log(&self, level: LogLevel) -> bool { + level >= self.level + } + + /// Log a message at the specified level + pub fn log(&self, level: LogLevel, message: &str) { + if self.should_log(level) { + let event = LogEvent { + level, + message, + timestamp: std::time::SystemTime::now(), + }; + + for appender in &self.appenders { + appender.append(&event); + } + } + } +} + +/// Macro for logging trace messages +#[macro_export] +macro_rules! trace { + ($($arg:tt)*) => { + if let Some(guard) = $crate::Logger::get() { + if let Some(logger) = guard.as_ref() { + logger.log($crate::LogLevel::Trace, &format!($($arg)*)); + } + } + }; +} + +/// Macro for logging debug messages +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => { + if let Some(guard) = $crate::Logger::get() { + if let Some(logger) = guard.as_ref() { + logger.log($crate::LogLevel::Debug, &format!($($arg)*)); + } + } + }; +} + +/// Macro for logging info messages +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => { + if let Some(guard) = $crate::Logger::get() { + if let Some(logger) = guard.as_ref() { + logger.log($crate::LogLevel::Info, &format!($($arg)*)); + } + } + }; +} + +/// Macro for logging warning messages +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => { + if let Some(guard) = $crate::Logger::get() { + if let Some(logger) = guard.as_ref() { + logger.log($crate::LogLevel::Warn, &format!($($arg)*)); + } + } + }; +} + +/// Macro for logging error messages +#[macro_export] +macro_rules! error { + ($($arg:tt)*) => { + if let Some(guard) = $crate::Logger::get() { + if let Some(logger) = guard.as_ref() { + logger.log($crate::LogLevel::Error, &format!($($arg)*)); + } + } + }; +} \ No newline at end of file