feat(project): init
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
.env
|
||||
Cargo.lock
|
||||
24
Cargo.toml
Normal file
24
Cargo.toml
Normal 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
136
README.md
Normal 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
26
init.sql
Normal 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
73
src/core.rs
Normal 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
45
src/lib.rs
Normal 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
37
src/model.rs
Normal 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
20
src/outputs/console.rs
Normal 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
12
src/outputs/mod.rs
Normal 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
39
src/outputs/postgres.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user