Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f73167135a | ||
|
|
c244e9b2fb |
No files matched your search
@@ -42,6 +42,8 @@ Linux 制品由 GitHub Actions 在原生架构 runner 上构建并完成生命
|
||||
|
||||
架构详情见 [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) 和
|
||||
[`docs/contracts/runtime-unification.md`](docs/contracts/runtime-unification.md)。
|
||||
产品、测试和一线支持排查导入与记忆沉淀时,使用
|
||||
[`docs/PRODUCT_LOG_TROUBLESHOOTING.md`](docs/PRODUCT_LOG_TROUBLESHOOTING.md)。
|
||||
|
||||
## 开发环境
|
||||
|
||||
|
||||
@@ -316,6 +316,10 @@ class ImportSupervisor:
|
||||
"source_id": current.source_id,
|
||||
"mode": current.operation,
|
||||
"count": current.stored,
|
||||
"stored_count": current.stored,
|
||||
"revised_count": current.revised,
|
||||
"skipped_count": current.skipped,
|
||||
"error_count": current.errors,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -334,6 +334,19 @@ class MemoryTaskRunner:
|
||||
**diagnostic_exception(error),
|
||||
)
|
||||
raise
|
||||
outputs, counts = await asyncio.gather(
|
||||
in_the_background(self.tasks.outputs, task_id),
|
||||
in_the_background(self.tasks.counts),
|
||||
)
|
||||
outstanding = sum(
|
||||
counts.get(status, 0)
|
||||
for status in (
|
||||
MemoryTaskStatus.PENDING,
|
||||
MemoryTaskStatus.WAITING_FOR_PERMISSION,
|
||||
MemoryTaskStatus.WAITING_FOR_MODEL,
|
||||
MemoryTaskStatus.FAILED,
|
||||
)
|
||||
)
|
||||
diagnostic_event(
|
||||
level="error" if result.status is MemoryTaskStatus.FAILED else "info",
|
||||
component="memory.task",
|
||||
@@ -343,6 +356,11 @@ class MemoryTaskRunner:
|
||||
duration_ms=elapsed_ms(started),
|
||||
state_after=str(result.status),
|
||||
error_code=result.last_error_code,
|
||||
details={
|
||||
"result_count": len(outputs),
|
||||
"pending_count": outstanding,
|
||||
"attempt": result.attempts,
|
||||
},
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -165,6 +165,13 @@ def test_import_request_trace_reaches_the_background_job_without_original_text(t
|
||||
assert "import.queued" in names
|
||||
assert "import.started" in names
|
||||
assert "import.finished" in names
|
||||
finished = next(event for event in events if event["event"] == "import.finished")
|
||||
assert {
|
||||
"stored_count",
|
||||
"revised_count",
|
||||
"skipped_count",
|
||||
"error_count",
|
||||
} <= finished["details"].keys()
|
||||
assert secret not in written
|
||||
|
||||
|
||||
|
||||
@@ -315,6 +315,12 @@ def test_memory_task_logs_six_step_durations_without_content(tmp_path) -> None:
|
||||
events = [json.loads(line) for line in written.splitlines()]
|
||||
steps = [event for event in events if event["event"] == "memory_task.step_completed"]
|
||||
assert [event["details"]["phase"] for event in steps] == ["1", "2", "3", "4", "5", "6"]
|
||||
finished = next(event for event in events if event["event"] == "memory_task.finished")
|
||||
assert finished["details"] == {
|
||||
"attempt": 1,
|
||||
"pending_count": 0,
|
||||
"result_count": 1,
|
||||
}
|
||||
assert tasks.processing_metrics()["hy_calls"] == 1
|
||||
assert all(event["duration_ms"] >= 0 for event in steps)
|
||||
assert marker not in written
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
# MemHub 产品日志排查手册
|
||||
|
||||
本文给产品、测试和一线支持同学使用。目标不是解释底层实现,而是回答三个问题:
|
||||
|
||||
1. 数据有没有成功导入;
|
||||
2. 记忆沉淀有没有持续运行、产出了多少条;
|
||||
3. 如果停住,停在导入、模型调用还是最终写入。
|
||||
|
||||
默认 `info` 下的结构化诊断日志不包含会话正文、模型输入输出或 API Key。`debug`
|
||||
会额外启用 HY pipeline trace,其中可能包含完整 prompt/response 和获授权的对话正文。
|
||||
排查时优先通过 App 的“导出日志”提供文件,不要直接发送整个 `~/.one-memory` 数据目录;
|
||||
debug 期间产生的 pipeline 日志在分享前必须由开发人员确认并脱敏。
|
||||
|
||||
## 1. Log level 设什么
|
||||
|
||||
### 日常观察:`info`
|
||||
|
||||
`info` 是默认级别,已经包含导入数量、任务排队、模型沉淀结果、待处理数量和失败终态。
|
||||
持续观察产品是否正常工作时,不需要打开 debug。
|
||||
|
||||
### 复现具体问题:`debug`
|
||||
|
||||
只有开发人员明确要求跟踪一次点击、一次召回或一个具体模型任务时,才打开:
|
||||
|
||||
> 设置 → 运行诊断 → 详细日志(排查用)
|
||||
|
||||
开关打开后等同于 `debug`。复现完成后:
|
||||
|
||||
1. 点击“记录问题时间点”;
|
||||
2. 点击“导出日志(发给开发排查)”;
|
||||
3. 关闭“详细日志(排查用)”。
|
||||
|
||||
debug 可能把完整模型 prompt/response 写入本机调试日志,不得长期开启。切回 `info`
|
||||
只会停止后续正文记录,不会自动删除此前已经产生的 debug 文件。
|
||||
|
||||
不要把日志级别设置成仅 `warning` 或 `error` 来判断系统是否健康,因为正常进度事件是
|
||||
`info`。
|
||||
|
||||
## 2. 日志在哪里
|
||||
|
||||
App 内推荐直接使用“运行诊断 → 导出日志”。开发人员远程指导时,优先使用
|
||||
`memhub logs`,它会再次过滤不安全字段。需要现场查看原始结构化日志时,Core 日志默认位于:
|
||||
|
||||
```text
|
||||
~/.one-memory/logs/diagnostic-core.jsonl
|
||||
```
|
||||
|
||||
每行都是一条 JSON。macOS 终端中可先设置:
|
||||
|
||||
```bash
|
||||
LOG="$HOME/.one-memory/logs/diagnostic-core.jsonl"
|
||||
```
|
||||
|
||||
## 3. 用什么 key 过滤
|
||||
|
||||
产品排查只需要认识以下字段:
|
||||
|
||||
- `timestamp`:发生时间;
|
||||
- `level`:`info`、`warn`、`error`;
|
||||
- `event`:事件名称,是最重要的过滤 key;
|
||||
- `outcome`:`started`、`succeeded`、`completed`、`failed` 等结果;
|
||||
- `error_code`:稳定错误码,可直接发给开发;
|
||||
- `job_id`:一次导入任务;
|
||||
- `task_id`:一次记忆沉淀任务;
|
||||
- `request_id` / `trace_id`:串联一次模型或界面请求;
|
||||
- `details.result_count`:本次沉淀最终关联了多少条记忆;
|
||||
- `details.pending_count`:完成本次任务后还有多少条待处理;
|
||||
- `details.stored_count`:本次导入新增了多少条原始消息;
|
||||
- `duration_ms`:耗时。
|
||||
|
||||
不要按用户原话、会话内容或 API Key 搜索;这些内容按设计不会进入诊断日志。
|
||||
|
||||
## 4. 系统正常工作时应该看到什么
|
||||
|
||||
### A. Core 已启动
|
||||
|
||||
```text
|
||||
event = core.ready
|
||||
outcome = succeeded
|
||||
```
|
||||
|
||||
没有 `core.ready` 时,后面的导入和沉淀都不会运行。
|
||||
|
||||
### B. 原始消息正在导入
|
||||
|
||||
```text
|
||||
event = import.finished
|
||||
outcome = completed
|
||||
details.stored_count >= 0
|
||||
details.error_count = 0
|
||||
```
|
||||
|
||||
`stored_count=0` 可能只是没有新消息;只要状态不是 failed,就不能单凭 0 判故障。
|
||||
|
||||
### C. 记忆任务进入队列
|
||||
|
||||
```text
|
||||
event = memory_scheduler.tasks_queued
|
||||
outcome = succeeded
|
||||
details.prepared_count > 0
|
||||
```
|
||||
|
||||
这表示导入的数据已经进入记忆沉淀队列。
|
||||
|
||||
### D. SDK 正在沉淀
|
||||
|
||||
```text
|
||||
event = hy.ingest_started
|
||||
outcome = started
|
||||
```
|
||||
|
||||
随后应出现:
|
||||
|
||||
```text
|
||||
event = hy.ingest_completed
|
||||
outcome = succeeded
|
||||
details.result_count = 本次 SDK 返回的记忆数
|
||||
```
|
||||
|
||||
如果有 reconcile,还会出现:
|
||||
|
||||
```text
|
||||
event = hy.reconcile.completed
|
||||
details.created_count
|
||||
details.updated_count
|
||||
details.superseded_count
|
||||
details.deleted_count
|
||||
```
|
||||
|
||||
### E. MemHub 已完成本次沉淀
|
||||
|
||||
最适合产品观察的是:
|
||||
|
||||
```text
|
||||
event = memory_task.finished
|
||||
outcome = completed
|
||||
details.result_count = 本任务最终写入或关联的记忆数
|
||||
details.pending_count = 剩余待处理任务数
|
||||
details.attempt = 第几次尝试
|
||||
```
|
||||
|
||||
健康表现是:
|
||||
|
||||
- `memory_task.finished` 持续出现;
|
||||
- `pending_count` 总体下降,或在持续导入时保持动态变化;
|
||||
- `result_count` 间歇性大于 0;
|
||||
- 没有持续重复的同一 `error_code`。
|
||||
|
||||
`result_count=0` 并不一定是故障。某段对话没有值得长期保存的信息时,0 是正常结果。只有大量
|
||||
任务连续为 0,同时存在 `hy.ingest_failed`、`memory_task.failed` 或积压只增不减,才需要升级
|
||||
排查。
|
||||
|
||||
## 5. 可复制的过滤命令
|
||||
|
||||
优先使用以下安全读取命令:
|
||||
|
||||
```bash
|
||||
memhub logs --since 2h --event memory_task.finished --json
|
||||
memhub logs --since 2h --event hy.reconcile.completed --json
|
||||
memhub logs --since 24h --errors-only --tail 500 --json
|
||||
memhub status --json
|
||||
```
|
||||
|
||||
以下 `jq` 命令仅用于 `diagnostic-core.jsonl`,不要对 `core.log`、`service.log` 或 debug
|
||||
pipeline 日志直接运行后把完整输出发给他人。
|
||||
|
||||
### 查看最近的失败
|
||||
|
||||
```bash
|
||||
jq -r '
|
||||
select(.level == "error" or .outcome == "failed") |
|
||||
[.timestamp, .service, .event, (.error_code // "-"), (.task_id // .job_id // "-")] |
|
||||
@tsv
|
||||
' "$LOG"
|
||||
```
|
||||
|
||||
### 查看每次沉淀数量和剩余积压
|
||||
|
||||
```bash
|
||||
jq -r '
|
||||
select(.event == "memory_task.finished") |
|
||||
[.timestamp, .outcome, (.details.result_count // 0),
|
||||
(.details.pending_count // 0), (.details.attempt // 0), (.error_code // "-")] |
|
||||
@tsv
|
||||
' "$LOG"
|
||||
```
|
||||
|
||||
输出列依次是:
|
||||
|
||||
```text
|
||||
时间 结果 本次记忆数 剩余待处理数 尝试次数 错误码
|
||||
```
|
||||
|
||||
### 统计当前日志里累计完成和累计记忆数
|
||||
|
||||
```bash
|
||||
jq -s '
|
||||
[.[] | select(.event == "memory_task.finished" and .outcome == "completed")] |
|
||||
{
|
||||
completed_tasks: length,
|
||||
memory_results: (map(.details.result_count // 0) | add // 0),
|
||||
latest_pending: (last.details.pending_count // 0)
|
||||
}
|
||||
' "$LOG"
|
||||
```
|
||||
|
||||
这里的 `memory_results` 是任务结果数量之和,用于判断流水线是否持续产出;它不是当前记忆库
|
||||
去重后的总行数。
|
||||
|
||||
### 查看导入数量
|
||||
|
||||
```bash
|
||||
jq -r '
|
||||
select(.event == "import.finished") |
|
||||
[.timestamp, .details.source_id, .outcome,
|
||||
(.details.stored_count // 0), (.details.revised_count // 0),
|
||||
(.details.skipped_count // 0), (.details.error_count // 0)] |
|
||||
@tsv
|
||||
' "$LOG"
|
||||
```
|
||||
|
||||
### 跟踪一个具体任务
|
||||
|
||||
先从失败或完成日志复制 `task_id`:
|
||||
|
||||
```bash
|
||||
TASK_ID="粘贴-task_id"
|
||||
jq --arg id "$TASK_ID" 'select(.task_id == $id)' "$LOG"
|
||||
```
|
||||
|
||||
## 6. 常见停点怎么判断
|
||||
|
||||
- 有 `core.ready`,没有 `import.finished`:来源未授权、未开启同步,或来源读取失败。
|
||||
- `import.finished` 正常,没有 `memory_scheduler.tasks_queued`:导入内容尚未形成完整会话,或未进入处理窗口。
|
||||
- 有 `memory_task.started`,没有 `hy.ingest_completed`:重点查模型配置、余额、网络、超时和 `hy.ingest_failed`。
|
||||
- 有 `hy.ingest_completed`,但 `memory_task.finished` 失败:重点查最终治理写入和对应 `error_code`。
|
||||
- `pending_count` 长时间完全不下降:查看是否反复出现同一个错误码,或任务都在
|
||||
`waiting_for_model`。
|
||||
- 导入数量持续增加、沉淀任务完全没有出现:这是异常,不应解释为“对话没有记忆价值”。
|
||||
|
||||
## 7. 产品反馈给开发时提供什么
|
||||
|
||||
请提供:
|
||||
|
||||
1. 问题发生的大致时间;
|
||||
2. App 版本和平台;
|
||||
3. 是否开启了详细日志;
|
||||
4. 点击“记录问题时间点”后显示的结果;
|
||||
5. 通过“导出日志”得到的诊断文件;
|
||||
6. 最近重复出现的 `error_code`;
|
||||
7. `memory_task.finished` 最近几行中的 `result_count` 和 `pending_count`。
|
||||
|
||||
不要提供 API Key,也不要打包发送整个记忆数据库。
|
||||
@@ -44,6 +44,16 @@ host-bridge、desktop 和 cli。单文件 4 MiB,保留 3 份轮转;额外清
|
||||
权限与安装操作、关键 IPC、失败分类、重试、耗时和终态。不记录无变化的高频轮询或逐条原文,
|
||||
导入使用阶段和计数汇总,避免日志反过来造成性能问题。
|
||||
|
||||
产品进度判断使用以下稳定事件:
|
||||
|
||||
- `import.finished`:`stored_count`、`revised_count`、`skipped_count`、`error_count`;
|
||||
- `hy.ingest.completed`:SDK 本次返回的 `result_count`;
|
||||
- `hy.reconcile.completed`:新增、更新、取代和删除数量;
|
||||
- `memory_task.finished`:最终 `result_count`、剩余 `pending_count` 和 `attempt`。
|
||||
|
||||
其中 `memory_task.finished` 是产品侧判断沉淀是否持续推进的最终事件;不能只用
|
||||
`hy.ingest.completed` 代替,因为后者早于 Governance 写入。
|
||||
|
||||
## 验收
|
||||
|
||||
隐私测试必须先植入一条可检测的秘密作为正向对照,再证明真实链路日志和导出均不含该秘密。
|
||||
|
||||
Reference in new issue
Block a user