29 KiB
局域网服务日志与可观测性实施计划
- 状态:待实施
- 编写日期:2026-07-22
- 代码基线:
v0.2.0/04e80b1 - 目标部署机:
192.168.193.237(实施时必须重新确认) - 当前 API:
http://192.168.193.237:39180
新会话启动方式
新开 Codex 会话后,先发送下面的提示词:
请完整阅读 docs/observability-implementation-plan.md、DEPLOY_5700U.md、
compose.deploy.yml、server/src/main.rs、server/src/transcription_worker.rs、
asr-service/app.py 和 utils/api.ts。
把 observability-implementation-plan.md 作为本次任务的执行约束。
先检查 git、目标部署机、Docker 日志驱动、磁盘和现有容器状态,再从文档中
第一个未完成的阶段开始实施。每完成一个阶段就运行该阶段验收,不要跳过日志
脱敏、请求 ID、持久化、回滚验证或文档更新。除非我明确要求,不要自行提交、
推送或修改目标机防火墙。
新会话开始后应先更新本文档顶部的代码基线、目标机 IP 和实际组件版本。本文中的 镜像版本、端口和资源阈值是实施起点,不得替代实施时的现场检查。
1. 目标
本计划要建立一条可从小程序故障追溯到具体代码版本的日志链路:
小程序请求
-> Rust API 结构化请求日志
-> 转录队列事件
-> FunASR 结构化推理日志
-> Docker 本地日志与轮转
-> Grafana Alloy 采集
-> Loki 持久化与检索
-> Grafana 查询、面板和告警
完成后应支持:
- 任意可信局域网电脑通过浏览器访问 Grafana。
- 按环境、Compose 项目、服务、容器、日志级别、时间和代码版本筛选日志。
- 用
request_id、feedback_session_id、lesson_id或segment_id关联 API 与 FunASR。 - 判断故障发生在哪个镜像版本和 Git 提交,避免按错误版本修改代码。
- 保存至少 14 天日志,并限制 Docker 原始容器日志占用。
- 在 Loki/Grafana 不可用时,API 与 FunASR 仍可正常提供业务服务。
- 后续可在同一 Grafana 中逐步加入日志告警和 Prometheus 指标。
2. 非目标
第一轮不做以下工作:
- 不部署 Kubernetes,也不把现有单机 Compose 改成集群。
- 不部署 Promtail;它已从当前 Loki 版本线移除,新部署使用 Alloy。
- 不在应用代码中直接调用 Loki API,应用只写标准输出和标准错误。
- 不在第一轮部署 Tempo、全链路追踪、Mimir 或 Elasticsearch。
- 不采集请求体、反馈正文、转录文本、音频内容、Bearer Token 或数据库连接串。
- 不把 Grafana、Loki、Alloy 或 Docker API 直接暴露到公网。
- 不用日志替代业务数据库、审计表或音频持久化卷。
3. 当前状态与缺口
3.1 已确认状态
compose.deploy.yml只包含funasr、migrate和api。- API 暴露宿主机
39180,FunASR 只在 Compose 内部网络提供10095。 - 规划时
GET /health返回健康,数据库、语音和微信鉴权均已配置。 - Rust API 已使用
tracing和tracing-subscriber,转录 worker 已记录部分worker_id、segment_id和错误事件。 - Rust 使用
TraceLayer::new_for_http(),但默认请求/响应事件为DEBUG;镜像当前RUST_LOG=info,不能依赖它提供完整访问日志。 - Rust 日志当前是文本格式,没有稳定 JSON 字段、请求 ID 或代码版本字段。
- FunASR 使用 Python
logging,但 Uvicorn 通过--no-access-log关闭访问日志;当前主要 记录模型启动和推理异常,没有成功请求耗时。 - Compose 没有服务级
logging配置。若目标机未修改 Docker daemon,通常会使用默认json-file且没有自动轮转,存在磁盘耗尽风险。 compose.deploy.yml中 API 镜像仍标记为teaching-feedback-api:0.1.0,与当前v0.2.0不一致。- 仓库没有 Loki、Alloy、Grafana、日志持久化卷、数据源自动配置或日志运行手册。
3.2 必须解决的问题
| 问题 | 影响 | 解决阶段 |
|---|---|---|
| Docker 日志无显式轮转 | 宿主机磁盘可能被占满 | 阶段 1 |
| API 请求日志不完整 | 无法定位具体失败请求 | 阶段 2 |
| API 与 ASR 缺少统一关联 ID | 跨服务排障依赖时间猜测 | 阶段 2 |
| 日志没有版本元数据 | 无法映射到正确代码 | 阶段 1、2 |
| 日志仅保存在单机 Docker 中 | 其他电脑无法方便检索历史 | 阶段 3 |
| 没有保留期和持久化验证 | 重启或磁盘增长不可控 | 阶段 3 |
| 没有基于实际基线的告警 | 故障只能人工发现 | 阶段 4 |
| 没有时序指标 | 无法可靠发现“没有日志”的故障 | 阶段 5 |
4. 目标架构
应用栈和可观测性栈使用两份 Compose 文件独立管理:
compose.deploy.yml compose.observability.yml
api -------- stdout/stderr ----+ alloy ----> loki ----> grafana:3000
funasr ----- stdout/stderr -----+ | | |
migrate ---- stdout/stderr -----+--------+ loki-data grafana-data
|
Docker Engine API
/var/run/docker.sock
约束:
api、funasr、migrate不依赖 Loki、Alloy 或 Grafana 启动。- Alloy 通过 Docker Engine API 发现带指定标签的容器并读取日志。
- Loki 与 Alloy 不发布宿主机端口。
- 只有 Grafana 发布到可信局域网,建议默认使用
${GRAFANA_PORT:-39300}:3000。 - 局域网外访问使用 WireGuard、Tailscale 或 SSH 隧道,不做公网裸端口。
- Grafana 必须启用登录;禁止匿名访问。
- Loki 初始使用单体模式、TSDB 索引和本地文件系统卷,接受单机无高可用的现实。
- Loki 默认保留 14 天;稳定运行并确认磁盘余量后再考虑 30 天。
5. 日志字段规范
5.1 公共字段
Rust API 和 FunASR 每条应用日志尽量使用一致字段:
| 字段 | 示例 | 说明 |
|---|---|---|
timestamp |
2026-07-22T02:00:00.123Z |
RFC 3339 UTC 时间 |
level |
INFO |
TRACE/DEBUG/INFO/WARN/ERROR |
service |
api |
api、funasr 或 migrate |
environment |
lan |
固定低基数字段 |
app_version |
0.2.0 |
发行版本 |
git_sha |
04e80b1 |
构建或部署提交 |
event |
http_request_finished |
稳定、可查询的事件名 |
request_id |
UUID | 单次 HTTP 请求关联 ID,不作为 Loki 标签 |
message |
简短英文或中文 | 人类可读说明 |
容器、Compose 项目、Compose 服务、镜像等字段由 Alloy 从 Docker 元数据补充。
5.2 API 请求字段
methodroute:优先路由模板,例如/api/v1/profiles/{profile_id}path:必要时保留原始路径,但不得包含敏感 query 参数statuslatency_msrequest_iderror_class:只在失败时记录稳定分类
不要记录完整请求头。尤其禁止记录 Authorization、Cookie、微信 code、AppSecret 和
上传内容。
5.3 语音链路字段
feedback_session_idlesson_idsegment_idasr_request_idworker_idaudio_formataudio_size_bytesaudio_duration_msqueue_wait_msinference_latency_msproviderattemptstatus
禁止记录本地音频绝对路径、热词原文、转录文字或反馈正文。
5.4 Loki 标签规范
仅将低基数字段设为 Loki 标签:
environmentcompose_projectcompose_servicecontainerservicelevelapp_version
以下字段只能保留在 JSON 日志正文中,通过 | json 查询,不能成为标签:
request_id- 用户 ID、学生 ID、会话 ID、课节 ID、片段 ID
- URL 原始路径
- 错误消息
6. 计划新增或修改的文件
| 文件 | 动作 | 目的 |
|---|---|---|
compose.deploy.yml |
修改 | 日志轮转、容器筛选标签、版本环境变量、镜像版本 |
server/Cargo.toml |
修改 | 开启 tracing-subscriber/json 和 tower-http/request-id |
server/src/main.rs |
修改 | JSON 日志、请求 ID、INFO 请求完成日志、启动版本日志 |
server/src/config.rs |
修改 | 读取 LOG_FORMAT、APP_VERSION、GIT_SHA、APP_ENV |
server/src/transcription_worker.rs |
修改 | 增加队列等待、尝试次数、ASR 耗时和关联字段 |
server/src/speech.rs |
修改 | 将 segment_id/请求 ID 传给 FunASR |
server/.env.example |
修改 | 记录非敏感日志配置项 |
utils/api.ts |
修改 | 保存响应 X-Request-ID 到 ApiRequestError |
| 相关小程序错误状态 | 修改 | 在可复制位置显示请求编号,不塞入短 Toast |
asr-service/app.py |
修改 | 请求 ID 中间件、成功/失败耗时和结构化字段 |
asr-service/logging_config.py |
新增 | 标准库 JSON formatter 与日志初始化 |
asr-service/Dockerfile |
修改 | 传入版本环境,继续避免重复 Uvicorn access log |
compose.observability.yml |
新增 | Alloy、Loki、Grafana 三个服务 |
observability/alloy/config.alloy |
新增 | Docker 发现、筛选、标签、转发配置 |
observability/loki/loki.yml |
新增 | 单体 TSDB、文件系统、保留期配置 |
observability/grafana/provisioning/datasources/loki.yml |
新增 | 自动创建 Loki 数据源 |
observability/grafana/provisioning/dashboards/ |
后续新增 | 阶段 4 面板和告警 |
observability/.env.example |
新增 | 端口、版本、管理员账号变量说明,不含密码 |
.gitignore |
修改 | 忽略 observability/.env、本地数据和导出文件 |
docs/observability-runbook.md |
新增 | 启停、查询、备份、升级、故障处理 |
DEPLOY_5700U.md |
修改 | 把日志栈加入部署和验收流程 |
7. 分阶段实施
阶段 0:现场基线与风险检查
目标:在修改配置前记录目标机真实状态。
在目标部署机执行:
cd /path/to/teaching-feedback-assistant
git status --short --branch
git rev-parse HEAD
git tag --points-at HEAD
docker version
docker compose version
docker info --format '{{.LoggingDriver}}'
docker compose -f compose.deploy.yml ps
docker compose -f compose.deploy.yml images
docker compose -f compose.deploy.yml logs --since 1h api funasr
docker inspect teaching-feedback-api-1 --format '{{json .HostConfig.LogConfig}}' || true
df -h
docker system df
timedatectl status
ss -lntp | grep -E ':(39180|39300|3100|12345)\b' || true
实施要求:
- 不输出
server/.env内容。 - 记录 Docker 当前日志驱动、容器名、镜像 ID、重启次数和日志占用。
- 确认
39300是否可用;冲突时选择其他端口并更新本文档。 - 确认至少有 10 GB 可用空间给日志卷;不足时先清理或降低保留期。
- 确认目标机时钟同步。日志统一为 UTC,Grafana 按浏览器时区显示。
- 确认 API、FunASR 和真实转录链路在改造前正常。
验收:把基线命令和非敏感结果写入实施记录,任何失败先解释,不进入阶段 1。
阶段 1:Docker 日志轮转与版本元数据
目标:即使集中日志尚未部署,容器日志也不能无限增长。
在 compose.deploy.yml 增加可复用配置:
x-default-logging: &default-logging
driver: local
options:
max-size: "20m"
max-file: "5"
对 funasr、migrate 和 api 使用该配置。优先使用服务级配置,不修改 Docker daemon
全局默认,避免影响目标机的其他项目。
同时增加:
labels:
observability.logs: "true"
observability.environment: "lan"
environment:
APP_ENV: lan
APP_VERSION: ${APP_VERSION:-0.2.0}
GIT_SHA: ${GIT_SHA:-unknown}
LOG_FORMAT: ${LOG_FORMAT:-json}
要求:
- API 镜像标签不得继续硬编码为
0.1.0。 - 实施时从 Git tag/commit 注入
APP_VERSION和GIT_SHA。 - 镜像版本必须固定,不使用
latest。 local日志驱动的配置值必须是字符串。- 修改日志驱动后必须重建容器;仅 restart 不会更新已有容器的日志驱动。
验证:
docker compose -f compose.deploy.yml config
docker compose -f compose.deploy.yml up -d --build --force-recreate
docker compose -f compose.deploy.yml ps
docker compose -f compose.deploy.yml logs --since 10m api funasr
docker inspect <api-container> --format '{{json .HostConfig.LogConfig}}'
docker inspect <funasr-container> --format '{{json .Config.Labels}}'
curl -fsS http://127.0.0.1:39180/health
阶段 1 完成标准:三个服务使用受控日志驱动,API 健康且真实转录仍成功。
阶段 2:结构化请求日志与跨服务关联
2.1 Rust API
依赖调整:
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
tower-http = { version = "0.6", features = ["cors", "trace", "request-id"] }
实现要求:
LOG_FORMAT=json时使用tracing_subscriber::fmt().json(),关闭 ANSI,并保留当前 span。- 本地开发允许
LOG_FORMAT=pretty,但生产 Compose 固定json。 - 使用
tower_http::request_id:- 接受合法的客户端
X-Request-ID。 - 缺失时用
MakeRequestUuid生成。 - 在响应头中传播同一个
X-Request-ID。
- 接受合法的客户端
- 中间件顺序按 tower-http 要求设置:先 Set Request ID,再 Trace,最后 Propagate。
TraceLayer的请求开始、请求结束和失败级别显式设置为INFO/ERROR,不依赖默认DEBUG。make_span_with不记录 headers;记录method、route/path和request_id。on_response记录status和latency_ms。- 5xx/超时记录
ERROR,可预期 4xx 不得全部当成系统错误。 - 启动时记录
service、environment、app_version、git_sha、监听地址和语音提供方。 - 不在日志中输出
DATABASE_URL、微信密钥、Summary API Key 或任何 Token。
补充单元/集成测试:
- 无请求 ID 时响应包含合法 UUID。
- 带合法请求 ID 时响应原样传播。
- 日志中不包含 Authorization 值。
- 200、400、401、500 都产生一次完成事件。
- JSON 日志每行可被
jq解析。
2.2 转录 worker 与 Rust -> FunASR
- 在 claim job 时取得
processing_started_at、transcription_attempts等必要字段。 - 以
segment_id作为跨 API/ASR 的稳定业务关联字段。 - Rust 请求 FunASR 时设置
X-Request-ID;可直接使用segment_id,或生成新的asr_request_id并同时记录两者。 - 记录排队等待、音频读取、HTTP 调用、推理完成和数据库更新的分段耗时。
- 成功日志不得包含 transcript;失败日志只记录经过清理的错误分类和消息。
2.3 FunASR
- 新增标准 JSON formatter,不因日志系统引入另一套应用框架。
- 中间件读取/生成
X-Request-ID,并写回响应头。 - 记录
asr_request_started、asr_request_finished和asr_request_failed。 - 成功和失败均记录
status、latency_ms、audio_size_bytes、audio_duration_ms、audio_format和request_id。 - 保留
--no-access-log,避免 Uvicorn 默认访问日志和自定义日志重复。 - 不记录临时路径、文件名、hotword、模型输出文本或上传内容。
2.4 小程序错误编号
ApiRequestError增加可选requestId。wx.request与wx.uploadFile都从响应头读取X-Request-ID,兼容响应头大小写。- 短 Toast 不塞入长 UUID;需要持久显示错误的状态区增加“请求编号”与复制入口。
- 开发者控制台可以记录请求编号,但不得打印 Token、上传路径或请求正文。
阶段 2 验收:
cd server
cargo fmt --check
cargo test
cargo clippy --all-targets --all-features -- -D warnings
cd ..
npm run typecheck
docker compose -f compose.deploy.yml config
再用一个固定 X-Request-ID 调用 /health 和一个授权业务接口,确认 API 日志可按该 ID
检索。使用真实音频验证 segment_id 能关联 Rust 与 FunASR,且日志中没有转录正文。
阶段 3:部署 Alloy、Loki、Grafana
3.1 版本选择
实施时查阅官方 release notes,选择互相兼容的稳定版本并固定完整版本号:
grafana/alloy:<version>grafana/loki:<version>grafana/grafana:<version>
禁止使用 latest。将选定版本写入 observability/.env.example 和实施记录。
3.2 Compose 约束
新增 compose.observability.yml,项目名建议为 teaching-feedback-observability。
loki:
- 单体模式。
- 配置文件只读挂载。
/var/loki使用loki-data持久化卷。- 不发布
3100到宿主机。 - 提供 readiness 健康检查。
restart: unless-stopped。
alloy:
- 配置文件只读挂载。
/var/lib/alloy/data使用alloy-data,保存 Docker 读取位置。- 初始部署读取
/var/run/docker.sock。 - Docker Socket 挂载即使标记只读,也不是完整的 API 权限隔离;容器必须使用固定镜像、 最小权限且不对外开放 Alloy UI。安全要求提高时改为受限 Docker Socket Proxy。
- 不发布 Alloy
12345到宿主机。 restart: unless-stopped。
grafana:
/var/lib/grafana使用grafana-data。- 默认发布
${GRAFANA_PORT:-39300}:3000。 GF_AUTH_ANONYMOUS_ENABLED=false。- 管理员密码仅放在未提交的
observability/.env或 Docker Secret。 - 自动挂载 Loki 数据源 provisioning。
restart: unless-stopped。
3.3 Loki 配置
observability/loki/loki.yml 至少满足:
auth_enabled: false,但 Loki 只存在于内部 Docker 网络。- 单体
target=all。 - TSDB schema,不使用已弃用 BoltDB index。
- chunks、rules、WAL 和 compactor 目录全部落在
/var/loki。 - compactor retention 开启。
- 初始
retention_period: 336h(14 天)。 - 禁用匿名 usage analytics(若当前版本支持对应配置)。
- 设定合理 ingestion/query 限制,防止一次错误查询拖垮单机。
文件系统存储没有副本,也不会按磁盘剩余空间自动删除;保留期之外仍要监控磁盘。
3.4 Alloy 配置
observability/alloy/config.alloy 应包含:
discovery.docker连接unix:///var/run/docker.sock。discovery.relabel只保留observability.logs=true的容器。- 从 Docker 元数据映射
compose_project、compose_service、container、image。 loki.source.docker读取容器日志并保存 positions。loki.process添加environment=lan,解析 JSON 日志级别;解析失败时保留原始行。- 不把
request_id、用户或业务 ID 提升为标签。 loki.write发送到http://loki:3100/loki/api/v1/push。
3.5 Grafana 自动配置
Loki 数据源通过 provisioning 创建:
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: true
editable: false
首次只创建数据源和一个最小“日志浏览”面板,不提前加入未经验证的告警阈值。
3.6 启动与验证
docker compose -f compose.observability.yml config
docker compose -f compose.observability.yml up -d
docker compose -f compose.observability.yml ps
docker compose -f compose.observability.yml logs --since 10m alloy loki grafana
curl -fsS http://127.0.0.1:39300/api/health
验收:
- 浏览器可从另一台局域网电脑打开
http://192.168.193.237:39300。 - 未登录不能查看日志。
- Grafana Explore 能看到
api和funasr。 - 以下查询可用,实际标签名以 Alloy 配置为准:
{compose_service="api"} | json
{compose_service="api"} | json | request_id="<request-id>"
{compose_service="funasr"} | json | segment_id="<segment-id>"
{compose_service="api"} | json | status >= 500
- 重启 Alloy 后不大量重复采集旧日志。
- 重启整套可观测性 Compose 后,Grafana 数据源、账号配置和 Loki 历史仍存在。
- 停止 Loki/Alloy/Grafana 后,API 和 FunASR 继续正常工作。
- Loki、Alloy 的宿主机端口不可访问。
阶段 4:基于实际日志建立面板与告警
先运行至少 3–7 天,观察正常流量、错误数量和转录耗时,再设置阈值。
首批面板:
- API 请求总量、4xx、5xx。
- API
latency_ms的 p50/p95/p99。 - 各路由错误日志列表。
- 转录成功、失败和重试数量。
- ASR 推理耗时和音频时长比值。
ASR service is unavailable与恢复事件。- 按
app_version对比错误率。
首批日志告警候选:
- 5 分钟内 API 5xx 达到 3 次。
- 10 分钟内出现转录失败。
- 5 分钟内持续出现数据库查询失败。
- ASR 不可用持续超过 5 分钟。
- Loki ingestion 或 Alloy target unhealthy。
告警要求:
- 每条告警必须提供可直接打开的 Grafana 查询。
- 配置
for持续时间,避免瞬时启动日志触发。 - 通知内容包含服务、环境、版本和时间范围,不包含用户数据。
- 先发送到低风险通知渠道进行一周观察,再升级为正式告警。
- 告警规则和 dashboard JSON 纳入 Git,禁止只在网页里手工保存。
Grafana 可直接基于 Loki 日志创建告警,但“服务完全无日志”“CPU/内存异常”“磁盘即将 耗尽”更适合指标,留到阶段 5。
阶段 5:按实际故障增加指标采集
日志稳定后再引入 Prometheus 指标。Alloy 可以抓取指标,但它不是长期指标存储;自托管 方案需要增加 Prometheus,或把指标 remote write 到已有兼容后端。
建议指标:
Rust API:
http_requests_total{route,method,status_class}http_request_duration_seconds{route,method}transcription_queue_jobs{status}transcription_job_duration_secondstranscription_job_retries_totaldatabase_pool_connections{state}asr_available
FunASR:
asr_requests_total{status}asr_inference_duration_secondsasr_audio_duration_secondsasr_inflight_requestsasr_failures_total{reason}
宿主机与容器:
- node-exporter:CPU、内存、磁盘、负载。
- cAdvisor 或 Alloy Docker integration:容器 CPU、内存、重启和网络。
up:API、FunASR、Loki、Alloy、Grafana 抓取状态。
指标标签禁止包含请求 ID、学生 ID、会话 ID 或原始 URL。路由必须使用模板路径,避免 高基数。
阶段 5 告警优先级:
- 磁盘剩余小于 15%。
- API/FunASR
up == 0持续 2 分钟。 - 转录队列持续增长或最老任务等待超阈值。
- API p95 延迟持续异常。
- 容器反复重启或内存逼近限制。
只有在日志和指标仍无法解释跨服务延迟时,才评估 OpenTelemetry + Tempo tracing。
8. 安全与隐私要求
禁止进入日志
Authorization、Cookie、微信登录 code、session key。WECHAT_APP_SECRET、数据库密码、完整DATABASE_URL。- Summary/LLM API Key。
- 学生姓名、家长称呼、反馈正文、转录全文、hotword。
- 音频内容、完整本地路径、上传临时文件名。
- 完整请求/响应 body。
允许但不作为标签
- 内部 UUID:profile/session/lesson/segment。
- request ID。
- 清理后的错误消息。
网络与权限
- Grafana 只允许可信局域网 CIDR 或 VPN 访问。
- Loki、Alloy 和 Docker API 不发布到宿主机。
- Alloy 的 Docker Socket 权限视为高权限,固定镜像版本并限制可访问人员。
- Grafana 管理员密码不得提交;首次登录后更换默认密码。
- Scalar 和 Grafana 都属于运维入口,未来若局域网信任边界扩大,应统一加反向代理、 HTTPS 和认证。
9. 容量、保留与备份
初始值:
- Docker 原始日志:每容器
20 MB x 5。 - Loki:14 天保留。
- 每日检查:
df -h、docker system df、Loki 卷大小。 - 预警:磁盘剩余 20% 开始观察,15% 告警,10% 紧急处理。
备份:
grafana-data:保存数据源、用户和本地状态;虽然 provisioning 在 Git 中,仍需备份。loki-data:若历史日志不是关键资产,可不做强一致备份,但必须明确接受丢失风险。- 配置文件和 dashboard/alert provisioning 必须进入 Git。
- 不备份 Alloy positions 时会导致重采或漏采风险,因此
alloy-data应持久化。
备份和恢复命令在实施时写入 docs/observability-runbook.md,并实际演练一次 Grafana 配置
恢复和 Loki 重启持久性。
10. 回滚方案
可观测性栈必须可独立回滚:
docker compose -f compose.observability.yml down
该操作不能停止 compose.deploy.yml 的业务服务。默认不加 -v,避免误删日志和 Grafana
数据。
应用日志回滚:
- 将
LOG_FORMAT=pretty可暂时恢复文本输出,不需要改业务代码。 - 若请求中间件引发问题,回退对应提交并重建 API 镜像。
- 若
local日志驱动与采集不兼容,回退 Compose 日志块后--force-recreate容器; restart 不足以恢复日志驱动。 - 每阶段使用独立提交,禁止把业务功能改动混进日志基础设施提交。
删除数据前必须先确认:
docker volume ls | grep teaching-feedback
docker compose -f compose.observability.yml down
# 只有用户明确确认后才允许删除 loki-data/grafana-data/alloy-data
11. 建议提交拆分
ops(logging): configure container log rotation and version labelsfeat(logging): add structured request and transcription logsops(observability): add alloy loki and grafana stackdocs(observability): add operations and troubleshooting runbookfeat(observability): add validated dashboards and alertsfeat(metrics): expose service and transcription metrics(阶段 5,单独实施)
每个提交前运行与其影响范围相匹配的测试。部署修改未经目标机验收不得直接打 release tag。
12. 最终验收清单
- 目标机 Docker 日志驱动和轮转参数已记录。
- API、FunASR、migrate 均使用受控日志轮转。
- Rust 与 FunASR 每行输出有效 JSON。
- API 响应返回
X-Request-ID。 - 小程序能保留并展示失败请求编号。
- 真实录音可通过
segment_id/request_id关联 Rust 与 FunASR。 - 日志中未发现 Token、密钥、学生信息、反馈正文或转录全文。
- Alloy 只采集明确标记的容器。
- Loki 与 Alloy 没有宿主机端口。
- Grafana 需要登录且只在可信网络可达。
- Loki 数据源由 provisioning 自动创建。
- 14 天 retention 生效,磁盘增长已观察。
- 重启可观测性栈后历史和配置仍存在。
- 停止可观测性栈不影响 API 和 FunASR。
cargo fmt --check、cargo test、严格 Clippy 和npm run typecheck通过。docs/observability-runbook.md包含启停、查询、备份、升级和回滚命令。- 至少 3–7 天基线后再启用正式告警。
- 指标阶段明确使用 Prometheus 或兼容存储,不误把 Alloy 当成指标数据库。
13. 官方参考资料
实施时重新核对最新稳定版本和配置语法:
- Docker logging drivers:https://docs.docker.com/engine/logging/configure/
- Grafana Alloy 工作方式:https://grafana.com/docs/alloy/latest/introduction/how-alloy-works/
- Alloy Docker 日志采集:https://grafana.com/docs/alloy/latest/reference/components/loki/loki.source.docker/
- Alloy Docker 容器安装:https://grafana.com/docs/alloy/latest/set-up/install/docker/
- Loki Docker/Compose 安装:https://grafana.com/docs/loki/latest/setup/install/docker/
- Loki 存储和 retention:https://grafana.com/docs/loki/latest/configure/storage/
- Loki 配置参考:https://grafana.com/docs/loki/latest/reference/loki-config-ref/
- Grafana Docker 安装:https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
- Grafana Alerting:https://grafana.com/docs/grafana/latest/alerting/
- Alloy Prometheus scrape:https://grafana.com/docs/alloy/latest/reference/components/prometheus/prometheus.scrape/
- tower-http request ID:https://docs.rs/tower-http/0.6.11/tower_http/request_id/
- tower-http trace:https://docs.rs/tower-http/0.6.11/tower_http/trace/
14. 实施完成后的交付报告
新会话完成每个阶段后应报告:
- 实际提交和部署版本。
- 新增/修改文件与
git diff --stat。 - 目标机日志驱动、容器、端口、网络和卷状态。
- API、FunASR、Alloy、Loki、Grafana 健康检查结果。
- 一条真实请求和一条真实转录的关联查询结果。
- 日志脱敏检查结果。
- 数据保留、磁盘使用和备份/恢复验证。
- 已配置告警及其基线依据。
- 未完成项、已知风险和明确回滚命令。
报告不得输出密码、Token、完整数据库地址、学生信息、反馈正文或转录内容。