diff --git a/README.md b/README.md index 0aeb6e9..744d02a 100644 --- a/README.md +++ b/README.md @@ -1,164 +1,171 @@ -```mermaid -classDiagram - class LogLevel { - <> - DEBUG - INFO - WARNING - ERROR - FATAL - } +# 🚀 Rust Logger - class LogRecord { - +DateTime timestamp - +LogLevel level - +String message - +String target - } +**Rust Logger** 是一个专为微服务架构设计的高性能、异步、可扩展的 Rust 日志库。 - class LogOutput { - <> - +write(record: LogRecord) Future - } +它基于 `Tokio` 运行时,支持多输出源(控制台、文件、PostgreSQL),内置分布式追踪(TraceID)支持,并提供自动化的日志轮转与过期清理机制。 - class ConsoleOutput { - +write(record: LogRecord) - } +## ✨ 核心特性 - class PostgresOutput { - -PgPool pool - +new(pool: PgPool) - +write(record: LogRecord) - } +* **⚡ 全异步非阻塞**:使用 `tokio` 和 `channel` 异步写入,确保业务主线程不受 I/O 阻塞。 +* **🗄️ PostgreSQL 集成**: + * 支持 **自动按月分表**(Auto Partitioning),无需人工干预。 + * 支持 `service_name` 区分多服务。 + * 结构化存储,便于 SQL 审计与分析。 +* **📄 文件日志管理**: + * 支持 **按天 + 按大小 (100MB)** 自动轮转切割。 + * 支持基于文件名的智能过期删除。 +* **🔍 分布式追踪 (TraceID)**: + * 基于 `Task Local` 的无侵入式上下文传递。 + * 全链路日志关联,轻松定位并发请求问题。 +* **🧹 自动清理 (Retention)**: + * 后台协程自动清理过期数据库分区和旧日志文件。 - 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 rust_logger/ -├── Cargo.toml +├── Cargo.toml # 项目依赖配置 +├── tests/ # 集成测试 ├── src/ -│ ├── lib.rs # 库入口,定义宏 -│ ├── model.rs # 定义 LogLevel, LogRecord -│ ├── outputs/ # 输出模块 -│ │ ├── mod.rs # Trait 定义 -│ │ ├── console.rs # 控制台输出实现 -│ │ ├── file.rs # 文件输出实现 -│ │ └── postgres.rs # 数据库输出实现 -│ ├── core.rs # Logger 核心逻辑 (Channel, Spawn) -│ └── cleaner.rs # 清理输出协程 +│ ├── lib.rs # 库入口 (导出宏与全局实例) +│ ├── core.rs # 核心引擎 (Channel 管理与后台协程) +│ ├── model.rs # 数据模型 (LogRecord, LogLevel) +│ ├── context.rs # 链路追踪上下文 (Task Local) +│ ├── cleaner.rs # 自动清理任务 (Retention Policy) +│ └── outputs/ # 输出策略模块 +│ ├── mod.rs # LogOutput Trait 定义 +│ ├── console.rs # 控制台输出 +│ ├── file.rs # 文件输出 (含轮转逻辑) +│ └── postgres.rs # 数据库输出 (含自动分表逻辑) ``` +## 📦 安装说明 -为了展示 **TraceID** 的真正威力,我们将模拟一个 **高并发的 Web 服务场景**。 +由于本项目托管于内部 Kellnr 仓库,请按照以下步骤配置。 -在这个场景中,如果不使用 TraceID,多个用户的日志会像“洗牌”一样混在一起,根本无法区分谁是谁。使用了 TraceID 后,我们就能像“抓线头”一样把一次请求的完整路径提出来。 +### 1. 配置 Registry 源 +在项目根目录或全局的 `.cargo/config.toml` 中添加: ---- +```toml +[registries.kellnr] +index = "sparse+https://kellnr.shay7sev.site/api/v1/crates/" +``` -### 第一步:准备工作 (不可跳过) +### 2. 添加依赖 +在你的 `Cargo.toml` 中添加: -因为我们在 `LogRecord` 中增加了 `trace_id`,你必须更新数据库表结构,否则程序运行会报错。 +```toml +[dependencies] +# 指定 registry 为私有源 +rust_logger = { version = "0.1.0", registry = "kellnr" } +tokio = { version = "1", features = ["full"] } +sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls"] } +``` -**请在 PostgreSQL 中执行:** +## 🛠️ 快速开始 + +### 1. 数据库准备 +在使用 PostgreSQL 输出前,请先创建基础表结构: ```sql --- 1. 增加字段 -ALTER TABLE app_logs ADD COLUMN trace_id VARCHAR(64); +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), + trace_id VARCHAR(64), -- 支持 TraceID + created_at TIMESTAMPTZ NOT NULL +) PARTITION BY RANGE (created_at); --- 2. 建立索引 (查询全靠它) +-- 建立索引 +CREATE INDEX idx_logs_service_time ON app_logs(service_name, created_at); CREATE INDEX idx_logs_trace_id ON app_logs(trace_id); ``` ---- +### 2. 初始化 Logger -### 第二步:编写业务代码 (`order-service/src/main.rs`) +```rust +use rust_logger::{ + Logger, LoggerConfig, LogLevel, LogCleaner, + outputs::{ConsoleOutput, PostgresOutput, FileOutput}, + set_global_logger, log_info +}; +use sqlx::postgres::PgPoolOptions; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // 1. 连接数据库 + let pool = PgPoolOptions::new().connect("postgres://...").await?; + + // 2. 初始化文件输出 + let file_output = FileOutput::new("logs", "my-service").await?; + + // 3. 配置 Logger + let config = LoggerConfig { + min_level: LogLevel::INFO, + outputs: vec![ + Box::new(ConsoleOutput), + Box::new(PostgresOutput::new(pool.clone())), + Box::new(file_output), + ], + }; + + // 4. 启动全局 Logger (传入当前服务名) + let logger = Logger::init("my-service", config); + set_global_logger(logger); + + // 5. (可选) 启动自动清理任务:保留 7 天日志 + LogCleaner::new(7) + .with_db_cleanup(pool) + .with_file_cleanup("logs", "my-service") + .start(); + + // 6. 打印日志 + log_info!("System started successfully!"); + + Ok(()) +} +``` + +## 🔍 使用 TraceID 进行链路追踪 + +在处理 HTTP 请求或复杂业务时,建议使用 `with_trace_id` 包裹逻辑。 + +```rust +use rust_logger::context::with_trace_id; +use rust_logger::{log_info, log_error}; +use uuid::Uuid; + +async fn handle_request() { + let trace_id = Uuid::new_v4().to_string(); + + // 在此闭包内的所有日志都会自动带上 trace_id + with_trace_id(trace_id, async { + log_info!("收到请求"); + + process_payment().await; + + log_info!("请求处理完成"); + }).await; +} + +async fn process_payment() { + // 无需手动传递 ID,自动获取上下文 + log_info!("正在扣款..."); +} +``` + +**日志输出示例:** +```text +2026-01-23 10:00:01 [INFO] [a1b2-c3d4...] - 收到请求 +2026-01-23 10:00:02 [INFO] [a1b2-c3d4...] - 正在扣款... +2026-01-23 10:00:03 [INFO] [a1b2-c3d4...] - 请求处理完成 +``` + +
+ +### 实例:编写业务代码 (`order-service/src/main.rs`) 我们需要引入 `uuid` 来生成唯一的 ID。 @@ -266,9 +273,7 @@ async fn payment_service(user: &str, amount: i32) { } ``` ---- - -### 第三步:运行结果与证明 +### 测试:运行与证明 运行代码后,我们来看看 **为什么 TraceID 是分布式的救命稻草**。 @@ -304,7 +309,7 @@ async fn payment_service(user: &str, amount: i32) { --- -### 第四步:如何追查问题 (Audit) +### 调试:如何追查问题 (Audit) 当生产环境出现问题时,正确的排查姿势如下: @@ -344,8 +349,107 @@ ORDER BY created_at ASC; --- -### 总结 TraceID 的三大好处 +### 总结:TraceID 的三大好处 1. **解耦并发**:在高并发系统中(每秒几千个请求),TraceID 是唯一能把属于同一个请求的日志“聚合”在一起的键。 2. **跨越边界**:虽然本例只演示了一个服务,但 TraceID 通常会通过 HTTP Header 传给下游服务(Payment Service, Stock Service)。这样你在 Kibana/Grafana 里搜一个 TraceID,能看到跨越 5 个微服务的完整调用链。 -3. **无侵入开发**:你的开发人员不需要在写 `payment_service` 时手动传递 `trace_id` 参数,他们只需要写 `log_info!`,库会自动完成剩下的工作。 \ No newline at end of file +3. **无侵入开发**:你的开发人员不需要在写 `payment_service` 时手动传递 `trace_id` 参数,他们只需要写 `log_info!`,库会自动完成剩下的工作。 + +
+ +## ⚙️ 高级配置 + +### 数据库分区策略 +* 库会自动检测当前月份,若 `app_logs_YYYY_MM` 表不存在,则自动创建。 +* 无需 DBA 手动维护未来的分区表。 + +### 文件轮转策略 +* **按大小**:单文件超过 100MB 自动重命名归档。 +* **按日期**:跨天后自动创建新日期的文件。 +* **文件命名**:`{service_name}-{yyyy-mm-dd}.log` + +## 🤝 贡献指南 + +1. Clone 本仓库。 +2. 在 `tests/` 目录下编写集成测试。 +3. 提交 Pull Request 前请运行 `cargo test` 确保所有测试通过。 + +
+👉 点击查看系统架构 UML 图 + +## 🏗️ 架构设计 + +本库采用 **异步事件驱动 (Async Event-driven)** 架构,基于 **Active Object** 模式设计。 +业务线程仅负责将日志通过 `MPSC Channel` 发送,由独立的后台协程负责实际的 I/O 写入,从而实现 **Zero-Blocking**(零阻塞)的业务性能。 + +```mermaid +classDiagram + class LogLevel { + <> + DEBUG + INFO + WARNING + ERROR + FATAL + } + + class LogRecord { + +String service_name + +DateTime timestamp + +LogLevel level + +String message + +String module + +Option~String~ trace_id %% 使用 ~ 代替 < > + } + %% 接口定义:强调 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() + } + + + %% 关系描述 + 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 +``` + +
\ No newline at end of file