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

16 KiB
Raw Blame History

生产级日志系统设计方案

版本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)

{
  "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 表)

-- 已有表结构(保持不变)
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 日志中间件设计

# 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 裁饰器自动记录,避免手动调用:

# 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 敏感数据脱敏规则

# 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_idduration_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)

# 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 格式):

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

-- 查询某用户最近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. 参考链接