# 日志系统 — 使用指南

> **module**: logging-system
> **doc_type**: UserGuide
> **status**: Active
> **owner**: FFOA Team
> **audience**: 后端工程师 / 运维 / SRE / 系统管理员
> **upstream_docs**: 01-prd.md, 03-architecture.md, 07-api.md
> **last_verified**: 2026-05-11

---

## 目录

1. [系统概览](#1-系统概览)
2. [核心概念](#2-核心概念)
3. [角色与权限](#3-角色与权限)
4. [Part 1 — 给开发：如何排查问题](#4-part-1--给开发如何排查问题)
5. [Part 2 — 给运维：监控与告警](#5-part-2--给运维监控与告警)
6. [Part 3 — 配置与维护](#6-part-3--配置与维护)
7. [常见问题（FAQ）](#7-常见问题-faq)

---

## 1. 系统概览

日志系统记录应用运行时的**技术日志**——HTTP 请求 / 应用错误 / 性能数据 / Temporal 工作流。用于**开发调试、运维监控和问题排查**，不是业务合规追溯（那是审计系统的职责）。

**核心能力**：

- **HTTP 自动埋点**：所有 API 调用由拦截器自动记录，含 method / path / status / 耗时 / 用户 / IP
- **错误堆栈**：异常自动捕获 + 完整堆栈；通过 `errorStack` 字段排查
- **链路追踪**：`traceId` / `spanId` 跨服务追踪一次请求经过的所有节点
- **敏感数据脱敏**：`password` / `token` / `Authorization` 等关键字自动脱敏
- **采样与丢弃**：大流量时按比例采样；buffer 满时优先丢 DEBUG / INFO，**ERROR / WARN 永不丢弃**（兜底落文件）
- **轮转与清理**：按日期自动轮转，过期日志按保留策略清理
- **告警**：慢请求 / 高错误率 / 异常模式自动告警

**与审计系统的边界**：

| 数据 | 归属 |
|------|------|
| HTTP 请求 / 响应、运行日志、错误堆栈、性能埋点 | **日志系统** |
| Temporal Workflow / Activity 执行日志 | **日志系统** |
| 业务操作（创建 / 更新 / 删除 / 审批 / 财务） | 审计系统 |
| 权限变更、密码修改 | 审计系统 |

判断标准：**纯技术、给工程师看的** → 日志；**有业务意义、需要事后向人解释**的 → 审计。

---

## 2. 核心概念

| 概念 | 说明 |
|------|------|
| **SystemLog** | 主日志表，存储所有结构化日志条目 |
| **LogConfig** | 日志配置表，包含日志级别、保留期、采样率等运行时可调参数 |
| **LogAlert** | 告警规则与历史；触发条件如慢请求阈值、错误率突增 |
| **LogCleanupRecord** | 日志清理执行记录，配合保留策略使用 |
| **requestId** | 单次 HTTP 请求的唯一 ID，由拦截器注入响应头 `X-Request-Id` |
| **traceId / spanId** | 跨服务分布式追踪 ID；traceId 全链路一致，spanId 标识链路上每个节点 |
| **日志级别** | `DEBUG` < `INFO` < `WARN` < `ERROR` < `FATAL`；生产默认 INFO 起 |
| **buffer 溢出策略** | 内存 buffer 满时按级别优先丢弃 DEBUG/INFO；ERROR/WARN 走文件兜底，不丢 |
| **trace API 屏蔽** | 生产环境 `/logs/trace/:traceId` 接口对客户端屏蔽 `errorStack`，防 stack 外泄 |
| **采样** | 大流量时只保留固定比例日志条目（如 10%），可对特定路径单独配置 |

---

## 3. 角色与权限

| 角色 | 可见 | 操作 |
|------|------|------|
| **Administrator** | 全部日志 | 查询 + 配置 + 告警管理 + 清理 |
| **运维 / SRE** | 全部日志 | 查询 + 告警查看 + 配置只读 |
| **后端工程师** | 全部日志（开发 / staging） | 查询 + trace 追踪 |
| **业务用户** | 不可见 | 日志系统不对业务用户开放 |

**关键权限点**：

| 权限码 | 控制 |
|------|------|
| `logs:read` | 读取日志（基础） |
| `logs:statistics` | 查看统计页 |
| `logs:config` | 修改日志配置（级别 / 采样率 / 保留期） |
| `logs:alerts` | 管理告警规则 |
| `logs:cleanup` | 执行手动清理 |

---

## 4. Part 1 — 给开发：如何排查问题

### 4.1 入口总览

| 页面 | 路由 | 用途 |
|------|------|------|
| 概览 | `/logs` | 近期错误率、QPS、慢请求 Top N |
| 查询 | `/logs/query` | 按时间 / 级别 / 模块 / 关键词全文检索 |
| 错误 | `/logs/errors` | 只看 ERROR / FATAL，按错误指纹分组 |
| 慢请求 | `/logs/slow-requests` | 超过阈值的请求列表 |
| 追踪 | `/logs/trace` | 输入 traceId 查全链路 |
| 统计 | `/logs/statistics` | 错误率 / 耗时分布 / 模块分布图表 |
| 配置 | `/logs/config` | 日志级别、保留期、采样率调整（admin） |
| 告警 | `/logs/alerts` | 告警规则与历史 |

### 4.2 典型排查流程

**场景 A — 「某个接口刚才报 500」**

1. 进入"错误"页
2. 按时间倒序看最近 ERROR
3. 找到目标错误，点开看完整堆栈 + 请求 payload + 用户 + IP
4. 复制 `traceId` 到"追踪"页 → 看同一请求触发的下游所有节点

**场景 B — 「某用户说他刚才操作失败」**

1. 让用户提供大致时间或刷新页面后的 `X-Request-Id`（响应头）
2. "查询"页按用户 ID + 时间窗筛选
3. 找到对应日志条目，看响应状态、耗时、是否触发下游错误

**场景 C — 「页面变慢了，但没报错」**

1. 进入"慢请求"页
2. 按耗时倒序、按路径分组
3. 复制慢请求的 `traceId` 到"追踪"页 → 看链路上是哪个 span 最慢（DB？外部 API？）

**场景 D — 「错误率突增告警」**

1. "告警"页找到本次告警条目，看触发时间和错误样本
2. 跳转"错误"页按时间窗筛选，确认错误指纹分布
3. 错误指纹高度集中 → 单点 bug；分布广泛 → 基础设施问题（DB / 缓存 / 网络）

### 4.3 在代码里打日志

注入 `LoggerService`：

```ts
import { LoggerService } from '@/core/observability/logging';

@Injectable()
export class OrderService {
  constructor(private logger: LoggerService) {}

  async create(dto: CreateOrderDto) {
    this.logger.info('订单创建', { orderId: dto.id, userId: dto.userId });
    // ...
    this.logger.error('订单创建失败', { error, dto });
  }
}
```

**约定**：

- `error` / `warn` 必须带结构化字段（不只是字符串），方便事后筛选
- 不要把整个请求 body 打进去（脱敏前可能含密码）
- 业务关键节点（订单提交、付款、审批）日志同时写审计——日志和审计是两套，不互相代替

### 4.4 traceId / requestId 怎么获取

- **后端代码内**：通过 `AsyncLocalStorage` 读 `requestContext.traceId`（拦截器已注入）
- **响应头**：浏览器 F12 看 `X-Request-Id` / `X-Trace-Id`
- **前端代码**：从响应头读取并展示给用户（白屏报错时让用户截图）

---

## 5. Part 2 — 给运维：监控与告警

### 5.1 内置告警类型

| 告警类型 | 触发条件（默认）| 处理建议 |
|------|------|------|
| 慢请求 | 单接口耗时 > 3000ms | 看 trace 找瓶颈 span |
| 错误率突增 | 5 分钟错误率 > 5% | 看错误指纹分布，定位回归 |
| 高错误模块 | 单模块 1 分钟内 ≥ 10 条 ERROR | 模块级回归或外部依赖故障 |
| Buffer 溢出 | 内存 buffer 持续溢出丢日志 | 检查日志写入 sink 健康；调采样率 |
| 磁盘容量 | 日志文件目录使用率 > 80% | 触发清理或扩容 |

阈值在 `/logs/config` 页可调（需 `logs:config` 权限）。

### 5.2 日常巡检

每日：
- 看"概览"页错误率与 QPS 趋势
- 看"告警"页未处理条目
- 看"慢请求"Top 10 有没有新冒出来的接口

每周：
- 看"统计"页错误率周对比
- 检查清理 Job 执行记录（`LogCleanupRecord`）

### 5.3 应急

**生产报错刷屏导致 buffer 溢出**：

1. 临时把日志级别从 INFO 改成 WARN（少打 INFO）
2. 排查根因，修复
3. 改回 INFO
4. 走 deploy-ops 流程，不要直接 SSH 改生产

**磁盘满**：

1. 触发手动清理：`/logs/config` 页 → 立即清理
2. 检查保留策略是否过宽（默认 14 天，生产可调 30 天）

---

## 6. Part 3 — 配置与维护

### 6.1 关键配置项（`/logs/config`）

| 配置 | 默认 | 说明 |
|------|------|------|
| 日志级别 | INFO | 全局最低级别；可按模块单独覆盖 |
| 保留天数 | 14 | 过期日志自动清理 |
| 采样率 | 100% | 高 QPS 路径可单独配低采样率 |
| Buffer 大小 | 1000 | 内存 buffer 条数；溢出按级别优先丢 |
| 慢请求阈值 | 3000ms | 触发告警 + 进 slow-requests 页 |
| 脱敏字段名单 | password / token / Authorization 等 | 可追加自定义字段 |

### 6.2 部署相关

- **后端代码**：`backend/src/core/observability/logging/`
- **数据库表**：`SystemLog` / `LogConfig` / `LogAlert` / `LogCleanupRecord`
- **本地文件兜底**：生产环境同时写 PostgreSQL + 本地文件（buffer 溢出兜底）
- **日志框架**：Winston

### 6.3 安全要求

- **生产环境 trace API 屏蔽 errorStack**：客户端调 `/logs/trace/:traceId` 时，response 不包含 `errorStack` 字段
- **敏感字段脱敏**：拦截器层强制脱敏，业务代码不要绕过
- **logs:* 权限收紧**：业务用户不应有任何 `logs:*` 权限

---

## 7. 常见问题（FAQ）

### Q1：日志系统和审计系统是不是重复了？

**不是。** 日志面向技术（排查 bug、性能、可用性）；审计面向业务合规（谁做了什么，可法律存证）。同一个业务操作可能两边都写一条。

### Q2：日志能改 / 能删吗？

**配置上能**——`logs:cleanup` 权限可执行清理。但日常运行中应用层不暴露修改单条日志的 API。如果需要审计意义的"不可改"，要走审计系统而不是日志。

### Q3：为什么有的请求查不到日志？

可能原因：
1. 该路径配置了低采样率（如 10%）
2. 时间已超过保留期被清理
3. Buffer 溢出时被丢（仅 DEBUG/INFO，ERROR/WARN 不会丢）
4. 拦截器异常（极少见，看告警页有无 "log sink failed"）

### Q4：traceId 是怎么传递的？

- 入口请求由拦截器生成 traceId
- HTTP 调用通过 header `X-Trace-Id` 透传
- 异步任务通过 `AsyncLocalStorage` 跨 await 传递
- Temporal 内通过 Workflow context 传递

### Q5：能把生产日志拉到本地分析吗？

可以。两种方式：
1. `/logs/query` 页导出 CSV / JSON
2. SSH 到生产机的 `~/logs` 目录下载文件兜底日志

⚠️ **不要在生产机本地 grep + awk** —— 占 CPU，影响业务。下载到本地分析。

### Q6：怎么知道某个日志是不是被脱敏过？

被脱敏的字段值会显示为 `***` 或 `[REDACTED]`。脱敏发生在拦截器层，进 DB 时已是脱敏后的值，**原始值不可恢复**。

### Q7：日志能跨多个 worktree / slot 看到吗？

各 worktree / slot 写各自的本地文件，DB 共享。`/logs/query` 走 DB，能看到所有 slot 的日志（按 `instance` 字段区分）。

### Q8：错误堆栈太长能折叠吗？

详情页默认折叠 `errorStack`，点击展开。前端有"复制完整堆栈"按钮。

---

## 关联文档

- [01-prd.md](01-prd.md) — 完整 PRD
- [03-architecture.md](03-architecture.md) — 架构设计、Winston 配置、拦截器实现
- [07-api.md](07-api.md) — 日志 REST API
- [10-roadmap.md](10-roadmap.md) — 后续规划
- [审计系统使用指南](../audit-system/11-user-guide.md) — 审计 vs 日志的边界
