feat(project): init

This commit is contained in:
2026-01-21 14:03:53 +08:00
commit 739234a896
10 changed files with 415 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
.env
Cargo.lock

24
Cargo.toml Normal file
View File

@@ -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"

136
README.md Normal file
View File

@@ -0,0 +1,136 @@
```mermaid
classDiagram
class LogLevel {
<<enumeration>>
DEBUG
INFO
WARNING
ERROR
FATAL
}
class LogRecord {
+DateTime timestamp
+LogLevel level
+String message
+String target
}
class LogOutput {
<<interface>>
+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 {
<<interface>>
<<Send + Sync>>
+write(record: LogRecord) Future
}
%% 具体实现
class PostgresOutput {
-PgPool pool
+write(record)
}
class ConsoleOutput {
+write(record)
}
%% 业务线程持有的 Logger (Producer)
class Logger {
<<Thread-Safe>>
<<Shared via Arc>>
-mpsc::Sender~LogRecord~ tx
-LogLevel min_level
+log(level, msg)
}
%% 后台异步任务 (Consumer)
class BackgroundWorker {
<<Active Object>>
<<Running in tokio::spawn>>
-mpsc::Receiver~LogRecord~ rx
-Vec~Box~LogOutput~~ outputs
+run()
}
%% 数据包
class LogRecord {
<<Immutable>>
+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 # 模拟业务服务使用
```

26
init.sql Normal file
View File

@@ -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');

73
src/core.rs Normal file
View File

@@ -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<dyn LogOutput> 允许我们在 Vec 中存放不同类型的 Output
pub outputs: Vec<Box<dyn LogOutput>>,
}
// Logger 结构体
pub struct Logger {
tx: mpsc::Sender<LogRecord>,
min_level: LogLevel,
service_name: String,
}
impl Logger {
// 初始化函数:设置 channel 并启动后台任务
pub fn init(service_name: &str, config: LoggerConfig) -> Arc<Self> {
// 1. 创建异步通道 (缓冲区大小 1000)
let (tx, mut rx) = mpsc::channel::<LogRecord>(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.");
}
}
}

45
src/lib.rs Normal file
View File

@@ -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<Arc<Logger>> = OnceCell::new();
// 初始化入口
pub fn set_global_logger(logger: Arc<Logger>) {
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()
);
}
};
}

37
src/model.rs Normal file
View File

@@ -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<Utc>,
pub level: LogLevel,
pub message: String,
pub module: String,
}

20
src/outputs/console.rs Normal file
View File

@@ -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
);
}
}

12
src/outputs/mod.rs Normal file
View File

@@ -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);
}

39
src/outputs/postgres.rs Normal file
View File

@@ -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);
}
}
}