feat(observability): add structured application logging

Emit correlated JSON logs from the API and FunASR services, propagate request IDs to the client, and configure bounded opt-in Docker logging.

Document the host-wide Grafana/Loki query workflow and keep observability deployment ownership outside the application repository.
This commit is contained in:
2026-07-22 13:52:40 +08:00
parent a63e9a1705
commit a875fe9f63
30 changed files with 1261 additions and 817 deletions

View File

@@ -0,0 +1,174 @@
# 中央日志接入与查询手册
教学反馈服务通过容器标签接入宿主机中央观测栈。本项目不包含也不启停 Loki、Alloy 或
Grafana中央栈位于 `/home/shay/data/docker-service/observability`Grafana 地址为
`http://192.168.193.237:39300`
## 登录 Grafana
1. 在可信局域网或已接入该主机网络的浏览器中打开 `http://192.168.193.237:39300`
2. 用户名使用 `admin`
3. 初始密码读取中央栈的
`/home/shay/data/docker-service/observability/.env` 中的 `GRAFANA_ADMIN_PASSWORD`
需要在目标机终端确认初始密码时使用:
```bash
sed -n 's/^GRAFANA_ADMIN_PASSWORD=//p' /home/shay/data/docker-service/observability/.env
```
不要把密码粘贴到 issue、提交信息、聊天记录或截图中。如果已经在 Grafana 页面修改过密码,
应使用修改后的密码;`.env` 只保证记录首次创建管理员时使用的值。
## 发布与接入检查
业务镜像构建时注入版本信息:
```bash
export APP_VERSION="$(git describe --tags --always)"
export GIT_SHA="$(git rev-parse --short HEAD)"
docker compose -f compose.deploy.yml config -q
docker compose -f compose.deploy.yml up -d --build --force-recreate
docker compose -f compose.deploy.yml ps
```
仅修改采集标签或日志轮转时不需要构建镜像:
```bash
docker compose -f compose.deploy.yml up -d --force-recreate --no-build api funasr
```
确认运行容器配置:
```bash
docker inspect teaching-feedback-api-1 --format '{{json .HostConfig.LogConfig}}'
docker inspect teaching-feedback-api-1 --format '{{json .Config.Labels}}'
docker inspect teaching-feedback-funasr-1 --format '{{json .HostConfig.LogConfig}}'
docker inspect teaching-feedback-funasr-1 --format '{{json .Config.Labels}}'
curl -fsS http://127.0.0.1:39180/health
```
预期日志驱动为 `local``max-size=20m``max-file=5`;两个服务都应包含
`observability.logs=true``observability.application=teaching-feedback`
`observability.environment=lan`
## 使用预置仪表盘
登录后打开 `Dashboards`,进入 `Host Observability / Host Service Logs`,然后设置:
- `Application``teaching-feedback`
- `Environment``lan`
- `Service`:按需选择 `api``funasr``All`
- 右上角时间范围:复现问题时优先选择最近 5 至 30 分钟
仪表盘适合浏览和筛选;需要精确关联请求或比较版本时,打开 `Explore`,数据源选择 `Loki`
## 常用 LogQL
查看教学反馈的全部日志:
```logql
{application="teaching-feedback"}
```
按服务、级别和文本筛选:
```logql
{application="teaching-feedback", service="api"} | json
{application="teaching-feedback", service="funasr"} | json
{application="teaching-feedback", level=~"WARN|ERROR"}
{application="teaching-feedback"} |= "database"
```
按 HTTP 状态和耗时寻找优化目标:
```logql
{application="teaching-feedback", service="api"} | json | status >= 500
{application="teaching-feedback", service="api"} | json | latency_ms >= 1000
{application="teaching-feedback", service="api"} | json | event="http_request_finished"
```
按关联 ID 追踪一次请求或转录任务:
```logql
{application="teaching-feedback", service="api"} | json | request_id="<request-id>"
{application="teaching-feedback", service="api"} | json | feedback_session_id="<session-id>"
{application="teaching-feedback", service="api"} | json | lesson_id="<lesson-id>"
{application="teaching-feedback", service="api"} | json | segment_id="<segment-id>"
{application="teaching-feedback", service="funasr"} | json | segment_id="<segment-id>"
```
按部署版本确认问题发生在哪份代码:
```logql
{application="teaching-feedback"} | json | app_version="0.2.0"
{application="teaching-feedback"} | json | git_sha="<git-sha>"
```
`request_id``segment_id``git_sha` 等字段位于 JSON 正文,不能直接放进流选择器的 `{...}`
也不能新增为 Loki 标签。查询无结果时先扩大时间范围,再从只包含 `application` 的查询逐步增加条件。
## 用日志定位代码
优化或修复代码时采用以下流程:
1. 复现问题,记录发生时间和小程序错误界面显示的 `request_id`
2.`request_id` 查询 API 的完整事件序列,确认状态码、`event``latency_ms`
`error_class`
3. 涉及录音时,从 API 日志取得 `segment_id`,分别查询 API worker 和 FunASR判断耗时发生在
排队、上传、推理还是结果写回阶段。
4.`app_version``git_sha` 确认运行代码版本,避免根据旧镜像日志修改新代码。
5. 修改后使用相同路径复现并比较事件数量、失败分类和耗时,不以“没有 ERROR”代替性能验收。
日志事件主要对应以下代码:
- HTTP 请求开始、结束、请求 ID`server/src/main.rs`
- API 错误分类和响应:`server/src/error.rs``server/src/recording_routes.rs`
- 转录队列、租约、重试和状态写回:`server/src/transcription_worker.rs`
- API 到本地 ASR 的请求关联:`server/src/speech.rs`
- FunASR 请求和推理耗时:`asr-service/app.py`
- 小程序请求 ID 展示与复制:`utils/api.ts` 及各页面的错误处理代码
## 历史范围
中央栈首次接入时回采了当时仍存在的 Docker 容器日志。2026-07-22 验收时可查询到的最早记录为:
- API2026-07-22 11:48:42北京时间
- FunASR2026-07-22 11:53:42北京时间
已删除容器中更早的日志无法恢复。Loki 当前保留期为 14 天Docker 本地日志还受到
`20m x 5` 轮转限制,因此长期比较应记录 `git_sha`、问题时间和关键查询结果,而不能假设日志永久存在。
## 故障定位
应用健康但 Grafana 无日志:
1. 检查业务容器是否具有采集标签。
2. 检查业务容器是否持续向 stdout/stderr 输出日志。
3. 检查中央栈运行状态;命令见中央目录 `README.md`
4. 使用 `{application="teaching-feedback"}` 查询,避免一开始叠加过多过滤条件。
Grafana/Loki 不可用时,不要重启或回滚业务容器来尝试修复中央栈。先确认:
```bash
curl -fsS http://127.0.0.1:39180/health
docker compose -f compose.deploy.yml ps
docker compose -f compose.deploy.yml logs --since 10m api funasr
```
如果本地 Docker 日志仍正常,则问题属于中央观测栈。中央栈的启停、数据卷、备份、恢复和升级
统一由 `/home/shay/data/docker-service/observability/README.md` 管理。
Grafana 不可用时,可以先用 Docker 本地日志继续定位:
```bash
docker compose -f compose.deploy.yml logs --since 30m api funasr
```
不要为了临时查询而把 Loki `3100` 或 Alloy `12345` 发布到宿主机。
## 应用侧回滚
结构化日志代码出现回归时,回滚对应代码并重建业务镜像。仅日志驱动不兼容时,修改
`compose.deploy.yml` 后必须 `--force-recreate`;普通 restart 不会更新容器日志驱动。任何应用侧
回滚都不得删除中央 Loki 或 Grafana 数据卷。