Files
YG_FT/docs/生产级日志系统方案.md

413 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 生产级日志系统设计方案
> 版本v1.0
> 日期2026-08-17
> 状态:待评审
---
## 一、现状分析
### 1.1 当前日志架构
```
┌─────────────┐
│ FastAPI │ ← 请求入口
└──────┬──────┘
┌─────────────┐
│ Logging │ ← Python logging 模块
│ Middleware │
└──────┬──────┘
├──────────────────┬──────────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Console │ │ File │ ← 输出目标
│ (开发环境) │ │ (JSON格式) │
└─────────────┘ └─────────────┘
┌─────────────┐
│ audit_logs │ ← 审计日志表
│ (PostgreSQL) │
└─────────────┘
```
### 1.2 现有组件
| 组件 | 文件路径 | 功能 |
|------|----------|------|
| `logging.py` | `backend/app/core/` | 日志配置、JSON 格式化、按日期/大小轮转 |
| `platform_store.py` | `backend/app/db/` | `record_audit()` 审计日志写入 |
| `002_governance.sql` | `backend/app/db/sql/` | `audit_logs` 表结构 |
### 1.3 存在的问题
| 问题 | 影响 | 严重程度 |
|------|------|----------|
| **无结构化日志分级** | DEBUG/INFO/WARNING/ERROR 全部混在一起,无法按级别过滤查看 | 🔴 高 |
| **无请求链路追踪** | 一个请求从进入到返回经过哪些服务/函数,无法串联 | 🔴 高 |
| **审计日志与业务耦合** | 各模块手动调用 `record_audit()`,容易遗漏 | 🟡 中 |
| **无敏感数据脱敏** | 用户 token、密码等可能明文记录 | 🔴 高 |
| **无日志聚合查询** | 无法按用户/时间范围/操作类型快速检索 | 🟡 中 |
| **无告警通知** | 系统异常无法主动推送通知 | 🟡 中 |
| **日志文件无归档策略** | 只有简单的过期删除,无压缩归档 | 🟢 低 |
---
## 二、设计目标
### 2.1 核心原则
1. **结构化** - 日志有固定 schema便于机器解析和查询
2. **可追溯** - 每个请求有唯一 ID可串联完整调用链路
3. **分级输出** - 不同环境输出不同级别,生产环境不输出 DEBUG
4. **安全合规** - 敏感数据自动脱敏token、密码、手机号等
5. **高性能** - 日志写入不影响业务接口性能(异步写入)
6. **可观测** - 支持快速检索、统计、告警
### 2.2 日志分级标准
| 级别 | 使用场景 | 示例 | 生产环境 |
|------|----------|------|:--------:|
| **DEBUG** | 开发调试 | 变量值、SQL 语句、完整堆栈 | ❌ 不输出 |
| **INFO** | 正常流程记录 | 任务创建成功、用户登录 | ✅ 记录 |
| **WARNING** | 可恢复异常 | 重试操作、参数校验失败、资源不足 | ✅ 记录 |
| **ERROR** | 需要人工介入 | 数据库连接失败、第三方 API 超时 | ✅ 记录 + 告警 |
| **CRITICAL** | 系统不可用 | 磁盘满、主节点宕机 | ✅ 记录 + 立即告警 |
---
## 三、技术方案
### 3.1 整体架构
```
┌─────────────────────────────────────────────────────────────────────┐
│ 应用层 (Application Layer) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 数据集管理 │ │ 微调训练 │ │ 模型推理 │ │ 用户认证 │ ... │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
│ │ │ │ │ │
│ └────────────┴───────────┴──────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Structured │ ← 结构化日志中间件 │
│ │ Logger │ │
│ └──────┬───────┘ │
│ │ │
│ ┌────────────┬────────────┬─────────────┐ │
│ ▼ ▼ ▼ │ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Console │ │ File │ │ 审计DB │ │ 告警 │ │
│ │ (开发) │ │ (JSON) │ │ (PG) │ │(可选) │ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 可观测层 (Observability) │
├─────────────────────────────────────────────────────────────┤
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Grafana │ │ Kibana │ │ PagerDuty │ ... │
│ │ (查询) │ │ (分析) │ │ (告警) │ │
│ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 日志 Schema 设计
#### 3.2.1 应用日志 (app.log)
```json
{
"timestamp": "2026-08-17T10:30:00.000Z",
"level": "INFO",
"trace_id": "req-abc123",
"parent_span_id": "span-xyz789", // OpenTelemetry Span
"request": {
"method": "POST",
"path": "/dataset-manage",
"client_ip": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"user_id": "u_admin"
},
"module": "dataset.router",
"function": "create_dataset",
"message": "数据集创建成功",
"extra": {
"dataset_id": "ds_abc123",
"dataset_name": "训练数据"
},
"duration_ms": 125,
"status_code": 200,
"error": null
}
```
#### 3.2.2 审计日志 (audit_logs 表)
```sql
-- 已有表结构(保持不变)
CREATE TABLE IF NOT EXISTS audit_logs (
id TEXT PRIMARY KEY,
tenant_id TEXT,
project_id TEXT,
actor_id TEXT, -- 操作人
action TEXT, -- 操作类型: create/delete/update/acl.set/login...
target_type TEXT, -- 资源类型: dataset/model/fine-tune/user...
target_id TEXT, -- 资源 ID
detail TEXT, -- 详细信息 JSON
client_ip TEXT, -- 客户端 IP
time TEXT, -- 操作时间
-- 新增字段
trace_id TEXT, -- 关联应用日志的请求追踪 ID
request_method TEXT, -- HTTP 方法
request_path TEXT, -- 请求路径
status_code INTEGER, -- 响应状态码
duration_ms REAL, -- 耗时(ms)
extra JSONB -- 扩展信息
);
-- 新增索引
CREATE INDEX IF NOT EXISTS idx_audit_trace ON audit_logs(trace_id);
CREATE INDEX IF NOT EXISTS idx_audit_actor_time ON audit_logs(actor_id, time);
```
### 3.3 日志中间件设计
```python
# backend/app/core/logging.py 新增
class StructuredLogger:
"""结构化日志记录器"""
def __init__(self, name: str):
self.logger = logging.getLogger(name)
self.trace_id = context_var.get("trace_id")
def info(self, msg: str, **kwargs):
self._log("INFO", msg, **kwargs)
def warning(self, msg: str, **kwargs):
self._log("WARNING", msg, **kwargs)
def error(self, msg: str, **kwargs):
self._log("ERROR", msg, **kwargs)
def _log(self, level: str, msg: str,
user_id: str = None,
target_type: str = None,
target_id: str = None,
duration_ms: float = None,
status_code: int = None,
error: Exception = None,
**extra):
"""统一日志记录方法"""
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": level,
"trace_id": self.trace_id.get(),
"request": {
"user_id": user_id or current_user_id(),
"client_ip": client_ip(),
# ...
},
"module": calling_module,
"message": msg,
"target": {
"type": target_type,
"id": target_id,
},
"extra": extra,
"duration_ms": duration_ms,
"error": format_exception(error) if error else None,
}
# 1. 写入控制台/文件
self.logger.log(level, json.dumps(log_entry))
# 2. 异步写入审计表(如果需要)
if level in ("WARNING", "ERROR", "CRITICAL"):
async_write_audit(log_entry)
```
### 3.4 装饰器模式(推荐)
使用 Python 裁饰器自动记录,避免手动调用:
```python
# backend/app/core/log_decorator.py
def audit_log(action: str, target_type: str = ""):
"""审计日志装饰器"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
result = await func(*args, **kwargs)
# 自动记录审计日志
record_audit(
action=action,
target_type=target_type,
target_id=kwargs.get('id') or result.get('id'),
detail=f"params={kwargs}"
)
return result
return wrapper
return decorator
# 使用示例
@audit_log("dataset.create", "dataset")
async def create_dataset(...):
# 业务逻辑
pass
```
### 3.5 敏感数据脱敏规则
```python
# backend/app/core/masking.py
SENSITIVE_FIELDS = {
"token": "***",
"password": "***",
"phone": lambda x: f"{x[:3]}****{x[-4:]}",
"email": lambda x: x[0] + "***" + x.split("@")[1] if "@" in x else "***",
"id_card": lambda x: f"{x[:6]}********{x[-4:]}",
}
def mask_sensitive(data: dict) -> dict:
"""递归脱敏字典中的敏感字段"""
for key, value in data.items():
if key in SENSITIVE_FIELDS:
data[key] = SENSITIVE_FIELDS[key](value) if callable(SENSITIVE_FIELDS[key]) else "***"
elif isinstance(value, dict):
mask_sensitive(value)
return data
```
---
## 四、实施计划
### 4.1 Phase 1基础增强1-2 天)
- [ ] **P1-1** 升级 `JsonLogFormatter`,增加 `trace_id` 字段
- [ ] **P1-2** 新增 `StructuredLogger` 封装类
- [ ] **P1-3** 统一所有模块的日志格式为 JSON
- [ ] **P1-4** 实现 `mask_sensitive()` 脱敏函数
- [ ] **P1-5** 审计日志表新增 `trace_id``duration_ms` 字段
### 4.2 Phase 2自动化2-3 天)
- [ ] **P2-1** 编写 `@audit_log` 装饰器
- [ ] **P2-2** 为关键业务接口添加装饰器:
- 数据集 CRUD
- 模型 CRUD
- 微调任务创建/删除
- 用户登录/登出
- ACL 授权变更
- [ ] **P2-3** 实现日志异步写入队列(避免影响性能)
### 4.3 Phase 3可观测性3-5 天)
- [ ] **P3-1** 集成 ELK Stack 或 Loki可选
- [ ] **P3-2** 编写 Grafana 仪表板:
- 请求量趋势图
- 错误率统计
- 慢接口 TOP10
- 用户操作审计面板
- [ ] [ ] **P3-3** 实现告警规则(错误率超阈值触发)
---
## 五、配置示例
### 5.1 日志配置 (settings)
```yaml
# config.yaml 或 .env
LOGGING:
level: INFO # 生产环境用 INFO开发用 DEBUG
dir: ./logs
file_prefix: app
max_bytes: 50MB # 单文件最大 50MB
retention_days: 30 # 保留 30 天
error_prefix: error # 错误日志单独文件
json: true # JSON 格式输出
AUDIT:
enabled: true
auto_record: true # 是否自动记录(通过装饰器)
sensitive_mask: true # 启用敏感数据脱敏
```
### 5.2 日志输出示例
**控制台输出(开发环境):**
```
2026-08-17 18:30:00.123 | INFO | pid=12345 | MainThread | req=req-abc | dataset.router:create_dataset | dataset/router.py:45 | 数据集创建成功 {"dataset_id":"ds_abc"}
```
**文件输出JSON 格式):**
```json
{"@timestamp":"2026-08-17T18:30:00.123Z","level":"INFO","logger":"dataset.router","message":"数据集创建成功","module":"dataset.router","function":"create_dataset","file":"dataset/router.py","line":45,"process":12345,"thread":"MainThread","request_id":"req-abc","extra":{"dataset_id":"ds_abc"}}
```
**审计日志查询 SQL**
```sql
-- 查询某用户最近7天的所有操作
SELECT time, action, target_type, target_id, detail, client_ip
FROM audit_logs
WHERE actor_id = 'u_admin'
AND time >= now() - interval '7 days'
ORDER BY time DESC;
-- 查询某资源的授权变更历史
SELECT * FROM audit_logs
WHERE action LIKE '%acl%'
AND target_id = 'ds_abc123'
ORDER BY time DESC;
```
---
## 六、附录
### A. 日志关键字段说明
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `trace_id` | string | 请求唯一标识,用于串联一次请求的所有日志 | `req-uuid-1234` |
| `parent_span_id` | string | 父 Span ID用于分布式追踪 | `span-parent-5678` |
| `actor_id` | string | 操作人用户 ID | `u_admin` |
| `action` | string | 操作动作 | `dataset.create`, `model.delete`, `login.success` |
| `target_type` | string | 操作的资源类型 | `dataset`, `trained_model`, `user` |
| `target_id` | string | 资源 ID | `ds_abc123` |
| `detail` | string/json | 操作详情 | `{"name": "训练数据", "type": "train"}` |
| `client_ip` | string | 客户端 IP | `192.168.1.100` |
| `duration_ms` | real | 接口耗时(ms) | `125.5` |
| `status_code` | int | HTTP 状态码 | `200`, `404`, `500` |
### B. 推荐的 Python 日志库对比
| 库 | 特点 | 适用场景 |
|-----|------|---------|
| `structlog` | 结构化日志,高性能 | 推荐 ✅ |
| `loguru` | 简单易用,自动配置 | 小型项目 |
| `logging` | Python 标准库 | 当前已使用 |
### C. 参考链接
- [Python logging cookbook](https://docs.python.org/3/howto/logging.html)
- [ELK Stack 官方文档](https://www.elastic.co/guide/index.html)
- [OpenTelemetry 规范](https://opentelemetry.io/docs/)