commit 739234a896ae38ea4b0a79b12eeb4e5567a05ae7 Author: shay7sev Date: Wed Jan 21 14:03:53 2026 +0800 feat(project): init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..711174a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +.env +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e41da37 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "rust_logger" +version = "0.1.0" +edition = "2024" + +[dependencies] +# 异步运行时 +tokio = { version = "1", features = ["full"] } +# 数据库 ORM (PostgreSQL) +sqlx = { version = "0.8", features = [ + "runtime-tokio-native-tls", + "postgres", + "chrono", +] } +# 时间处理 +chrono = { version = "0.4", features = ["serde"] } +# 异步 Trait 支持 +async-trait = "0.1" +# 全局单例支持 +once_cell = "1.19" +# 错误处理 +anyhow = "1.0" +# 读取 .env 文件 +dotenv = "0.15" diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c8d4de --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +```mermaid +classDiagram + class LogLevel { + <> + DEBUG + INFO + WARNING + ERROR + FATAL + } + + class LogRecord { + +DateTime timestamp + +LogLevel level + +String message + +String target + } + + class LogOutput { + <> + +write(record: LogRecord) Future + } + + class ConsoleOutput { + +write(record: LogRecord) + } + + class PostgresOutput { + -PgPool pool + +new(pool: PgPool) + +write(record: LogRecord) + } + + class FileOutput { + -File file + +write(record: LogRecord) + } + + class LoggerConfig { + +LogLevel min_level + +Vec~Box~LogOutput~~ outputs + } + + class Logger { + -Sender~LogRecord~ tx + -LogLevel min_level + +init(config: LoggerConfig) Arc~Logger~ + +log(level, message, target) + -run_background_task(rx, outputs) + } + + %% Relationships + LogOutput <|.. ConsoleOutput : Implements + LogOutput <|.. PostgresOutput : Implements + LogOutput <|.. FileOutput : Implements + + Logger ..> LogRecord : Creates + Logger o-- LoggerConfig : Uses + LoggerConfig o-- LogOutput : Aggregates (0..*) + LogRecord -- LogLevel : Has +``` + +```mermaid +classDiagram + %% 接口定义:强调 Send + Sync 约束 + class LogOutput { + <> + <> + +write(record: LogRecord) Future + } + + %% 具体实现 + class PostgresOutput { + -PgPool pool + +write(record) + } + class ConsoleOutput { + +write(record) + } + + %% 业务线程持有的 Logger (Producer) + class Logger { + <> + <> + -mpsc::Sender~LogRecord~ tx + -LogLevel min_level + +log(level, msg) + } + + %% 后台异步任务 (Consumer) + class BackgroundWorker { + <> + <> + -mpsc::Receiver~LogRecord~ rx + -Vec~Box~LogOutput~~ outputs + +run() + } + + %% 数据包 + class LogRecord { + <> + +timestamp + +level + +message + } + + %% 关系描述 + LogOutput <|.. PostgresOutput + LogOutput <|.. ConsoleOutput + + %% 关键的线程安全机制:MPSC Channel + Logger "1" o-- "1" `mpsc::Sender` : Owns + BackgroundWorker "1" o-- "1" `mpsc::Receiver` : Owns + + %% 逻辑流 + ClientThread ..> Logger : 1. Calls log() (Non-blocking) + Logger ..> `mpsc::Sender` : 2. Sends Record + `mpsc::Sender` ..> `mpsc::Receiver` : 3. Channel Transfer (Thread-Safe) + `mpsc::Receiver` ..> BackgroundWorker : 4. Receives Record + BackgroundWorker --> LogOutput : 5. Serialized Writes +``` + +```text +my-logger/ +├── Cargo.toml +├── .env # 数据库配置 +├── src/ +│ ├── lib.rs # 库入口,定义宏 +│ ├── model.rs # 定义 LogLevel, LogRecord +│ ├── outputs/ # 输出模块 +│ │ ├── mod.rs # Trait 定义 +│ │ ├── console.rs # 控制台输出实现 +│ │ └── postgres.rs # 数据库输出实现 +│ ├── core.rs # Logger 核心逻辑 (Channel, Spawn) +│ └── main.rs # 模拟业务服务使用 +``` \ No newline at end of file diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..4a30e6a --- /dev/null +++ b/init.sql @@ -0,0 +1,26 @@ +-- 1. 创建 rust_logger 专用用户 +CREATE USER rust_logger_user WITH PASSWORD 'rust_logger_password'; + +-- 2. 创建 rust_logger 专用数据库 +CREATE DATABASE rust_logger_db OWNER rust_logger_user; + +-- 3. 赋予权限(确保它能在 rust_logger_db 库里创建 Schema) +GRANT ALL PRIVILEGES ON DATABASE rust_logger_db TO rust_logger_user; + +CREATE TABLE app_logs ( + id BIGSERIAL, -- 注意:后面会讲分区,分区表通常不直接用主键 + service_name VARCHAR(50) NOT NULL, -- 新增:用于区分是哪个服务 + log_level VARCHAR(10) NOT NULL, + message TEXT NOT NULL, + module VARCHAR(100), + created_at TIMESTAMPTZ NOT NULL +) PARTITION BY RANGE (created_at); -- 核心优化:按时间分区 + + +-- 建立索引(包含 service_name) +CREATE INDEX idx_logs_service_time ON app_logs(service_name, created_at); + +-- create_next_month.sql +-- 这里的日期逻辑需要通过外部脚本(Python/Shell)动态生成 SQL 语句 +CREATE TABLE IF NOT EXISTS app_logs_2026_01 PARTITION OF app_logs + FOR VALUES FROM ('2026-01-01') TO ('2026-02-01'); \ No newline at end of file diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..631b876 --- /dev/null +++ b/src/core.rs @@ -0,0 +1,73 @@ +// src/core.rs +use crate::model::{LogLevel, LogRecord}; +use crate::outputs::LogOutput; +use chrono::Utc; +use std::sync::Arc; +use tokio::sync::mpsc; + +// Logger 配置 +pub struct LoggerConfig { + pub min_level: LogLevel, + // Box 允许我们在 Vec 中存放不同类型的 Output + pub outputs: Vec>, +} + +// Logger 结构体 +pub struct Logger { + tx: mpsc::Sender, + min_level: LogLevel, + service_name: String, +} + +impl Logger { + // 初始化函数:设置 channel 并启动后台任务 + pub fn init(service_name: &str, config: LoggerConfig) -> Arc { + // 1. 创建异步通道 (缓冲区大小 1000) + let (tx, mut rx) = mpsc::channel::(1000); + + let min_level = config.min_level; + let outputs = Arc::new(config.outputs); + + // 2. 启动后台 Worker (Consumer) + tokio::spawn(async move { + // 不断从 channel 接收消息 + while let Some(record) = rx.recv().await { + // 遍历所有输出目标并写入 + // 这里可以用 join_all 并发写,也可以串行写,视 DB 压力而定 + for output in outputs.iter() { + output.write(&record).await; + } + } + // channel 关闭后,任务自动结束 + }); + + // 3. 返回 Logger 实例 (Producer) + Arc::new(Self { + tx, + min_level, + service_name: service_name.to_string(), + }) + } + + // 发送日志的方法 + pub fn log(&self, level: LogLevel, message: String, module: String) { + // 1. 级别过滤 + if level < self.min_level { + return; + } + + // 2. 构造记录 + let record = LogRecord { + service_name: self.service_name.clone(), + timestamp: Utc::now(), + level, + message, + module, + }; + + // 3. 发送 (非阻塞,如果队列满则丢弃,防止拖死业务) + if self.tx.try_send(record).is_err() { + eprintln!("Logger warning: Channel full, dropping log message."); + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..721fa14 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,45 @@ +// src/lib.rs +pub mod core; +pub mod model; +pub mod outputs; + +use crate::core::Logger; +use once_cell::sync::OnceCell; +use std::sync::Arc; + +// 全局静态 Logger 实例 +pub static GLOBAL_LOGGER: OnceCell> = OnceCell::new(); + +// 初始化入口 +pub fn set_global_logger(logger: Arc) { + if GLOBAL_LOGGER.set(logger).is_err() { + eprintln!("Logger already initialized"); + } +} + +// 定义宏 +#[macro_export] +macro_rules! log_info { + ($($arg:tt)*) => { + if let Some(logger) = $crate::GLOBAL_LOGGER.get() { + logger.log( + $crate::model::LogLevel::INFO, + format!($($arg)*), + module_path!().to_string() + ); + } + }; +} + +#[macro_export] +macro_rules! log_error { + ($($arg:tt)*) => { + if let Some(logger) = $crate::GLOBAL_LOGGER.get() { + logger.log( + $crate::model::LogLevel::ERROR, + format!($($arg)*), + module_path!().to_string() + ); + } + }; +} diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..dc5ef2a --- /dev/null +++ b/src/model.rs @@ -0,0 +1,37 @@ +// src/model.rs +use chrono::{DateTime, Utc}; +use std::fmt; + +// 1. 日志级别,支持比较大小 (PartialOrd) +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum LogLevel { + DEBUG = 0, + INFO = 1, + WARNING = 2, + ERROR = 3, + FATAL = 4, +} + +// 为了方便转换成字符串存库 +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let level_str = match self { + LogLevel::DEBUG => "DEBUG", + LogLevel::INFO => "INFO", + LogLevel::WARNING => "WARNING", + LogLevel::ERROR => "ERROR", + LogLevel::FATAL => "FATAL", + }; + write!(f, "{}", level_str) + } +} + +// 2. 日志记录实体 +#[derive(Debug, Clone)] +pub struct LogRecord { + pub service_name: String, // 新增 + pub timestamp: DateTime, + pub level: LogLevel, + pub message: String, + pub module: String, +} diff --git a/src/outputs/console.rs b/src/outputs/console.rs new file mode 100644 index 0000000..2af559a --- /dev/null +++ b/src/outputs/console.rs @@ -0,0 +1,20 @@ +// src/outputs/console.rs +use super::LogOutput; +use crate::model::LogRecord; +use async_trait::async_trait; + +pub struct ConsoleOutput; + +#[async_trait] +impl LogOutput for ConsoleOutput { + async fn write(&self, record: &LogRecord) { + // 简单的控制台打印,生产环境可以加上颜色代码 + println!( + "[{}] [{}] ({}) - {}", + record.timestamp.format("%Y-%m-%d %H:%M:%S"), + record.level, + record.module, + record.message + ); + } +} diff --git a/src/outputs/mod.rs b/src/outputs/mod.rs new file mode 100644 index 0000000..63e4827 --- /dev/null +++ b/src/outputs/mod.rs @@ -0,0 +1,12 @@ +// src/outputs/mod.rs +pub mod console; +pub mod postgres; + +use crate::model::LogRecord; +use async_trait::async_trait; + +// 定义 Trait,要求实现者必须是线程安全的 (Send + Sync) +#[async_trait] +pub trait LogOutput: Send + Sync { + async fn write(&self, record: &LogRecord); +} diff --git a/src/outputs/postgres.rs b/src/outputs/postgres.rs new file mode 100644 index 0000000..550e10a --- /dev/null +++ b/src/outputs/postgres.rs @@ -0,0 +1,39 @@ +// src/outputs/postgres.rs +use super::LogOutput; +use crate::model::LogRecord; +use async_trait::async_trait; +use sqlx::PgPool; + +pub struct PostgresOutput { + pool: PgPool, +} + +impl PostgresOutput { + pub fn new(pool: PgPool) -> Self { + Self { pool } + } +} + +#[async_trait] +impl LogOutput for PostgresOutput { + async fn write(&self, record: &LogRecord) { + let query = r#" + INSERT INTO app_logs (log_level, message, module, created_at) + VALUES ($1, $2, $3, $4) + "#; + + // 注意:这里的 write 是在后台任务中执行的,就算慢也不会阻塞主业务 + // 我们忽略错误,因为如果日志系统挂了,不能让它导致业务逻辑崩溃 (Panic) + // 生产环境可以考虑加一个 fallback 机制(比如降级写文件) + if let Err(e) = sqlx::query(query) + .bind(record.level.to_string()) + .bind(&record.message) + .bind(&record.module) + .bind(record.timestamp) + .execute(&self.pool) + .await + { + eprintln!("Failed to write log to PostgreSQL: {}", e); + } + } +}