上一篇,我把 PDF 导入时散开的异常日志收到了观测云里,一条报错终于能完整地读了。
接下来我想省掉另一个动作:出了问题,先翻日志,再复制一大段给 AI。既然 Codex 已经能帮我读代码,能不能让它自己查运行日志?
这次就拿知识库的 PDF 解析做个实测。我给 Codex 一个文档编号和故障描述,让它去观测云找依据,最后再回控制台逐条核对。
先只告诉它现象
我的项目是一个接了 Milvus 的 AI 知识库。资料先解析成文本,再做切分、向量化和入库,供后面的问答检索使用。一份 PDF 没有进入知识库,单看这个结果,很难知道停在哪一步。
Milvus 负责后面的向量存储与检索,前面的程序还得先把 PDF 读成文本。如果连文件都没读出来,排查就得从解析开始。只说一句“没导进去”,还分不清这些情况。
为了把过程看清楚,我准备了三份自制 PDF:两份故意写坏,一份正常。运行项目实际使用的 PDF 解析器,得到两次失败、一次成功。这次范围只到本地解析,没有执行 HTTP 上传和 Milvus 写入。
两份坏文件分别用 1042、10420 编号,正常文件是 1043。这组编号还能检查查询是否串记录:正常文件的消息里也提到了 1042,光搜一串数字,很容易把别的文档一起找出来。
先不接日志,我先把情况告诉 Codex:
我:
我这边有个 PDF 没进知识库,文档编号是 1042。帮我看看卡在哪一步了?
是今天上午 11 点 27 分左右那批,批次号是 codex-owl-20261106-112707,服务名是 clxzs-codex-owl-lab。同一批还有别的文档。
Codex:
目前只能确认:文档 1042 未进入知识库。仅凭现有信息,无法确定失败阶段或具体原因。
- 失败阶段:未确定。 无证据判断停在上传、解析、切分、向量化还是入库阶段,也不能排除仍在排队或处理中。
给它一个查询入口
我沿用前面的 DataKit 日志采集,在观测云里保留了 document_id、batch_id、stage、status 和 exception 等字段。
DataKit 负责从文件里采日志,送到观测云。文档编号用来找某一份资料,批次号把同一次处理的记录放在一起;阶段说明当时在接收文件还是解析文件,异常字段则保存报错。后面 Codex 查的就是这些数据。
我另外给这批日志起了一个单独的来源名,也就是后文的 source。把来源、批次和时间一起限定,能让这次查询集中在这一小批记录上。
Codex 这一侧,我接的是观测云官方提供的 OWL MCP。它把数据查询等操作提供成工具,Codex 可以读取工具说明和参数要求,再发起查询。官方接入文档里有各站点的地址。
这里的 MCP 是 Codex 调用外部工具的接口。在这次运行里,Codex 通过工具查询平台数据,不需要代替我在网页上点筛选按钮。DataKit 把日志送上去,MCP 再让 Codex 查到它,两边分工不同。
我用 Codex 命令行,也就是 CLI,发起这次任务。我用的是观测云的 CN3 北京免费站点,因此连接地址也选 CN3。下面是本次运行的核心配置,前两行指定工具审批方式,后面几行指定连接地址和凭据变量:
approval_policy = "on-request"
approvals_reviewer = "auto_review"
[mcp_servers.owl]
url = "https://cn3-owl-mcp.guance.com/mcp"
bearer_token_env_var = "OWL_MCP_API_KEY"
default_tools_approval_mode = "writes"
这次配置随单次 CLI 任务传入,没有改全局设置。按官方文档配置客户端时,连接项也可以放在用户目录的 .codex/config.toml,并让新启动的 Codex 读取凭据环境。文中的来源名和批次号都是我这次的数据,换个项目要换成它自己的值。
OWL_MCP_API_KEY 是存放观测云 API Key 的环境变量名。只在配置里写这个名字还不够,启动 Codex 的进程也得读到它的值。
它和 DataKit 上报日志使用的 Token 是两回事:采集器能把日志送上去,不等于 Codex 已经拿到了查日志的权限。
我用的是个人 API Key,权限随账号生效;本次实际操作只读,并不意味着这个 Key 天生只能查询。给 Codex 的任务范围也写得很具体:只查本次来源、批次和时间,不做数据修改。
这回让它自己查
接上 MCP 后,我把同一个问题交给 Codex,再让它查这批日志。我给了它文档编号、来源、批次和时间,没有预先把 Java 异常或最终答案贴进去,也没有让它读取本地文件。错误内容要从观测云的返回里拿。
我:
观测云已经接好了。你帮我查一下 1042 这份文档的日志,看看具体报了什么错。
看今天上午 11 点 20 到 11 点 40 这段就行,批次号是 codex-owl-20261106-112707,日志来源是 clxzs_codex_owl_20261106。
再看看同一批其他文档有没有成功的。1042 和 10420 是两个文档,别查混了。先查原因,别改数据。
Codex 调用 owl.data.simple_query,参数如下:
{
"namespace": "L",
"source": "clxzs_codex_owl_20261106",
"index": "default",
"start_time": 1793935200000,
"end_time": 1793936400000,
"where_clause": "`batch_id` = 'codex-owl-20261106-112707' AND `service` = 'clxzs-codex-owl-lab' AND `document_id` = '1042'",
"limit": 100
}
这就是第一次业务查询。原生记录里共有 8 次工具调用,前面做工具与数据发现,真正查业务日志的是两次。
这次查询用 source 选日志来源,用 index 选日志索引,两个时间戳对应我给的查询窗口。where_clause 再把批次、服务和文档编号一起限制住。
其中 document_id = '1042' 查的是字段值相等。直接搜正文里的“1042”,可能同时找到编号 10420,或者其他文档消息里顺带提到的 1042。这里先把编号查准,才好往下读异常。
工具返回了两条记录,下面只摘失败的这一条:
{
"event_id": "codex-owl-20261106-112707-business-04",
"document_id": "1042",
"stage": "parse",
"status": "error",
"elapsed_ms": 24,
"retryable": false,
"__message": "Document 1042 local PDF parsing failed"
}
同一条记录的 exception 字段里,有这两行原文:
Caused by: java.io.IOException: Missing root object specification in trailer.
at org.apache.pdfbox.pdfparser.PDFParser.initialParse(PDFParser.java:112)
同次返回还带着查询结果的两个标记,后面复核时要一起看:
{
"partial": true,
"coverage": "unknown"
}
parse 就是文件解析,error 表示这条记录报错。PDFBox 是读取 PDF 的库,Caused by 后面列出了底层抛出的异常;这次说的是 PDF 里缺少根对象的定义。
原始调用里还有一次同批查询:来源和时间不变,文档条件改为排除 1042。返回中的两条解析记录如下,其余接收记录和字段省略:
[
{
"document_id": "1043",
"stage": "parse",
"status": "info",
"__message": "Document 1043 local PDF parsing succeeded; comparison reference: document 1042"
},
{
"document_id": "10420",
"stage": "parse",
"status": "error",
"__message": "Document 10420 local PDF parsing failed"
}
]
1043 的消息明确写着解析成功,10420 则是另一个失败文档。这里要结合阶段和消息看,单独一个 info 还不能算导入成功:前面的文件接收记录也是 info,但那时文件还没开始解析。
正常样本说明这段解析代码至少读出了这一份文件;两个失败样本则各有自己的错误记录。它们不能用来判断整个服务都正常或都故障,更不能把 1043 的解析成功算成已经写进 Milvus。
拿到这些返回后,Codex 的最终回答是(节选):
确认失败在 PDF 解析阶段:1042 的
stage=parse、status=error,耗时24ms,retryable=false。事件为codex-owl-20261106-112707-business-04。最内层异常是java.io.IOException: Missing root object specification in trailer.,由 PDFBoxPDFParser.initialParse抛出,经PagePdfDocumentReader、PdfFileParser向上传递,即解析器未找到 PDF trailer 中的根对象定义。优先核验1042原始PDF结构及传输完整性,修复后重试。
回平台核对一遍
AI 说查到了,我还是要看看它用的究竟是哪条记录。
我在观测云日志查看器里选定当时的时间范围,按来源和批次筛选,得到 6 条记录:三条本地文件登记,三条解析结果。
六条记录对应三份文档:每份文件都有一条接收记录和一条解析结果。Codex 两次查询分别返回 1042 的 2 条和其他文档的 4 条,合起来也是这六条。
再加上 document_id='1042',列表剩下两条:这份本地文件的登记记录,以及它的解析失败记录。相似编号和正常文档正文里提到的 1042,都没有混进来。
我生成这批日志是在 11 点 27 分左右,平台列表显示的是 11:28:29,给出的查询窗口覆盖了这两个时刻。
打开失败记录,我先看文档编号是不是 1042,再看事件编号末尾是不是 business-04,最后展开异常字段。里面能找到刚才那句 Missing root object specification in trailer.,和 Codex 引用的是同一条记录。
同一个异常可能出现在不同文档或批次里。我用事件编号把 Codex 引用的内容和平台记录对上。
最后我把 MCP 返回的六条记录和本地原始日志逐条比较,事件编号、文档、阶段、状态、耗时和异常内容全部一致。
返回里同时带着 partial=true、coverage=unknown。因此,可以依据已经查到的异常下判断,但不能因为没看到后续阶段,就断言后续一定没执行。Codex 的回答也保留了这层限制。
这次省掉了什么
| 关注点 | 只给故障描述 | 接入本次日志后 |
|---|---|---|
| 失败阶段 | 无法确认 | 查到 parse 和 error |
| 具体原因 | 没有运行证据 | 查到 PDFBox 的根异常 |
| 怎么复核 | 只有现象和编号 | 有查询范围、事件编号和原始记录 |
这次从启动 Codex 到拿到最终回答,用时 93.6 秒,约 1 分 34 秒。8 次工具调用里,真正查业务日志的是 2 次,分别返回 2 条和 4 条记录。
我回平台手动核查,前后花了 10 分钟左右。具体走的是 6 条 → 2 条 → 1 条:按来源和批次找到 6 条,加文档编号剩 2 条,再展开那 1 条失败记录,对照事件编号和根异常。Codex 先给出记录和结论,我再沿着这条路径检查它有没有找错。
如果日志仍散在文件里,就得自己找、截取,再贴给 AI;这次观测云提供集中查询,Codex 通过 MCP 完成查找和整理,我保留最后的核对。
观测云在这里提供了集中保存的日志、可精确查询的字段,以及让我复核结果的查看器。MCP 则把查询入口交给了 Codex。至于坏 PDF 怎么处理,仍要根据具体文件决定;这次没有让它自动修改文件或重跑入库。
下一篇准备把文档数量加上去:一批资料里有成功也有失败时,先用观测云查出失败清单,再看能否有针对性地处理,减少整批重来。