Skip to content

os migrate recorded-by --json 的 stdout 里混着内核 INFO 日志,payload 无法直接 JSON.parse #6217

Description

@qq9340100

发现自 #4873 的实现过程(修那条 issue 时必须绕开本问题才能断言 payload)。未认领。#4873 是不同的 bug —— #4873 修的是退出码,修好之后本条照旧复现。

现象

--json 是给程序消费的,但 os migrate recorded-by --jsonstdout 上除了 JSON payload,还夹着内核启动的 INFO 日志,所以整个 stdout 不是一份合法 JSON:

os migrate recorded-by --json 2>/dev/null | jq .
# parse error: Invalid numeric literal at line 1, column 13

实测(丢弃 stderr 后把 stdout 整份喂给 JSON.parse):

JSON.parse FAILED: Unexpected token 'S', "[Standalone"... is not valid JSON

stdout 的实际形状是这样(payload 上下都有日志):

[StandaloneStack] no compiled artifact at '…' — booting without one (run 'os compile' to build it)
2026-08-07T10:09:22.887Z INFO Registered metadata loader: filesystem (file:)
…约 60 行 INFO…
2026-08-07T10:09:23.387Z INFO ✅ Bootstrap complete
{
  "planId": "metadata.recorded-by-sentinel-to-null",
  "sentinel": "system",
  "pending": 0,
  "applied": false,
  "duration": 504
}
2026-08-07T10:09:23.388Z INFO Graceful shutdown started
2026-08-07T10:09:23.394Z INFO ✅ Graceful shutdown complete

注意 stderr 是空的 —— 日志没走 stderr,是实打实占了 stdout。

归因(已定位到行,未修)

packages/core/src/logger.ts:343:

const stream = proc ? (isErrorLevel ? proc.stderr : proc.stdout) : undefined;

error/fatal 走 stderr,info/debug/warnstdout。凡是 --json 且要 boot 内核的子命令都会被这一条命中,不止 migrate recorded-by:bootSchemaStack 系(migrate plan / apply / resume / summary-nulls / value-shapes / files-to-references / meta resync)都在同一条路径上。不 boot 内核的命令(如 os migrate meta)不受影响 —— packages/cli/test/migrate-meta.e2e.test.ts 至今可以直接 JSON.parse(stdout),这也是为什么这个问题一直没被 e2e 测出来。

为什么值得修

--json 这个 flag 的唯一受众是程序。一个必须先靠启发式把日志行剔掉才能解析的 --json,等于没有 --json:消费者要么写一段"找最后一个独占一行的 { 到配对 }"的提取器(#4873 的 e2e pin 里就被迫写了一份,packages/cli/test/migrate-exit-code.e2e.test.tsjsonPayload()),要么就解析失败。而这类启发式在 payload 恰好紧邻某行日志、或日志里出现形似 JSON 的内容时会静默取错。

#4873 是同一受众的同一类伤害(成功的命令,程序侧却拿不到可用结果),但落点完全不同,所以单开一条。

可能的修法(未决,留给分诊)

  1. --json 时把内核 logger 的输出重定向到 stderr(payload 独占 stdout);
  2. --json 时把日志级别压到 error(信息量损失更大,且 warn 仍会丢);
  3. logger 默认全部走 stderr,stdout 只留给命令的正式输出(POSIX 惯例,影响面最大)。

三者对 os serve / os dev 的日志观感影响不同,建议由维护者定方向后再动手 —— 本条只负责把事实和落点记清楚。

复现

cd examples/app-crm
rm -f /tmp/t.db*
OS_DATABASE_URL="file:/tmp/t.db" node ../../packages/cli/bin/run.js migrate recorded-by --json 2>/dev/null | jq .

Refs #4873

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions