通用可观测性、分析与反馈
通用可观测性、分析与反馈 stage-20(横切基础设施)
这一阶段是贯穿全程的“记录和体检系统”,从启动、处理请求、跑工具到关机都在幕后工作。分析事件先把会话、报错、代码改动等记成统一格式,再归并、排队发出。OpenTelemetry 像统一仪表盘,收日志、指标和耗时。会话和专项埋点记录每次请求、工具、数据库等表现。Rollout trace 把过程写成可回放的流水账。反馈和日志持久化负责打包线索、脱敏、存本地,Windows 沙盒日志也记录命令成败,方便事后排查。
子阶段
本阶段涉及的状态9
reg-account-quota-limits账号的额度、限流、消费控制和剩余可用量信息,用来决定请求能不能继续发。reg-local-state-runtime本地 SQLite 和 StateRuntime 打开的那套持久化句柄,负责让线程、日志、目标、记忆等数据能长期保存。reg-server-daemon-stateapp-server、daemon、exec-server 等常驻服务当前是否启动、有哪些客户端连着、处理器是否还接单的运行状态。reg-background-job-queue后台刷新云配置、同步目录、整理记忆、清理连接、检查更新等异步任务的队列和运行状态。reg-observability-telemetry日志、指标、分析事件、请求耗时、工具结果和错误报告等观测数据的收集与发送状态。reg-rollout-trace-logrollout 记录下来的可回放流水账和会话状态,用来恢复、排错、重连和事后分析。reg-feedback-report-buffer用户反馈、附带日志线索、脱敏后的诊断包以及上传重试/完成状态的暂存缓冲。reg-installation-identity本机安装实例的持久编号和识别信息,用于账号、后端请求、遥测和安全身份关联。reg-session-usage-accounting每个线程或回合累计的模型 token、请求次数、工具用量和成本/用量统计状态,用于展示、持久化和遥测。
通用可观测性、分析与反馈
windows-sandbox-rs/src/logging.rs源码 ↗
沙盒里会启动命令、改权限、创建用户,很多步骤一旦失败,不留记录就很难查。这个文件把这些关键事件写进每天一个的日志文件,文件名类似 sandbox.2026-05-21.log,并且最多保留 90 个日志文件。它还会把很长的命令截短到安全长度,避免日志太乱,同时不会把中文或 emoji 这种多字节字符截坏。主要流程是:外部代码调用 log_start、log_success、log_failure 或 log_note;这些函数加上时间、程序名和简短说明;最后 append_line 打开当天日志并写入一行。debug_log 比较特别,只有环境变量 SBX_DEBUG=1 时才写调试日志并打印到屏幕,避免平时刷屏。
exe_label15–23 ↗
fn exe_label() -> &'static str
作用:取当前正在运行的程序文件名,用来放在每条日志里,让人知道这行日志是谁写的。取不到时就用 proc 这个兜底名字。
数据流:它不接收参数,会读取当前进程的可执行文件路径 → 从路径里取出文件名,并缓存起来,避免每次写日志都重新查 → 返回一个固定可复用的程序标签字符串。
调用关系:它是 log_note 生成日志行时的一个小助手。log_note 写每条普通日志前会需要这个标签;它内部用一次性缓存 OnceLock(一种只初始化一次的存放处)保存结果。
调用图:外部调用 1 个(new)。
preview25–32 ↗
fn preview(command: &[String]) -> String
作用:把一整条命令变成适合写进日志的短预览。这样日志能看出跑了什么命令,又不会被特别长的参数塞满。
数据流:输入是一组命令字符串 → 先用空格拼成一行;如果不超过限制就原样返回,如果太长就按字符边界安全截短 → 输出一段不会截坏 UTF-8 字符的命令预览。
调用关系:它服务于 log_start、log_success 和 log_failure。每当这些函数要记录某个命令的开始、成功或失败时,都会先把命令交给 preview 做成短版本。
调用图:被 3 处调用(log_failure, log_start, log_success);外部调用 1 个(take_bytes_at_char_boundary)。
log_file_path_for_utc_date34–40 ↗
fn log_file_path_for_utc_date(base_dir: &Path, date: chrono::NaiveDate) -> PathBuf
作用:根据一个目录和一个日期,算出那天对应的日志文件路径。它让代码和测试都能明确知道日志文件应该叫什么。
数据流:输入是日志目录和一个 UTC 日期 → 按 sandbox.年-月-日.log 的格式拼文件名,再接到目录后面 → 输出完整路径。
调用关系:它是 current_log_file_path 的底层拼路径工具。current_log_file_path 先取得今天日期,再把日期交给它生成当天日志文件名。
调用图:被 1 处调用(current_log_file_path);外部调用 2 个(join, format!)。
current_log_file_path42–44 ↗
fn current_log_file_path(base_dir: &Path) -> PathBuf
作用:算出某个日志目录里,今天应该写入的日志文件路径。外部想知道当前日志在哪里时会用它。
数据流:输入是日志目录 → 读取当前 UTC 日期 → 调用 log_file_path_for_utc_date 拼出今天的 sandbox.YYYY-MM-DD.log 路径 → 返回这个路径,不负责创建文件。
调用关系:它被 current_log_file_path_for_codex_home 用来从 Codex 主目录推导日志位置,也会被 run_setup_refresh_inner 这类设置流程用来告诉用户或代码当前日志文件在哪。
调用图:调用 1 个内部函数(log_file_path_for_utc_date);被 2 处调用(current_log_file_path_for_codex_home, run_setup_refresh_inner);外部调用 1 个(now)。
current_log_file_path_for_codex_home46–48 ↗
fn current_log_file_path_for_codex_home(codex_home: &Path) -> PathBuf
作用:从 Codex 的主目录直接算出沙盒日志文件路径。调用者不用自己知道沙盒日志目录藏在主目录下哪个位置。
数据流:输入是 codex_home 路径 → 先通过 sandbox_dir 得到里面的 .sandbox 目录 → 再调用 current_log_file_path 生成今天的日志文件路径 → 输出这个路径。
调用关系:它把“Codex 主目录”和“沙盒日志目录”这两层关系接起来。需要从用户配置目录定位日志时,会走这个更方便的入口。
调用图:调用 1 个内部函数(current_log_file_path);外部调用 1 个(sandbox_dir)。
log_writer50–62 ↗
fn log_writer(base_dir: &Path) -> Option<RollingFileAppender>
作用:创建一个可以写日志文件的写入器,并按天自动滚动日志。目录不存在时它会直接放弃,避免写到错误位置。
数据流:输入是日志目录 → 先检查它是不是目录;不是目录就返回 None → 如果是目录,就创建一个每天换文件、文件名前缀为 sandbox、后缀为 log、最多保留 90 个文件的写入器 → 返回可写对象或失败时返回 None。
调用关系:它只被 append_line 调用。append_line 想写一行日志时,先找它拿当天日志文件的写入器,再实际写入内容。
调用图:被 1 处调用(append_line);外部调用 2 个(is_dir, builder)。
append_line64–70 ↗
fn append_line(line: &str, base_dir: Option<&Path>)
作用:真正把一行文字追加到日志文件里。它是所有日志函数最后落到磁盘的共同出口。
数据流:输入是一行日志文字和一个可选目录 → 如果没有目录,就什么也不做;如果有目录,就通过 log_writer 打开当天日志 → 写入这一行,写失败也不让主流程崩溃。
调用关系:debug_log 和 log_note 都把最终文本交给它。它再把打开日志文件的事交给 log_writer,自己只负责写这一行。
调用图:调用 1 个内部函数(log_writer);被 2 处调用(debug_log, log_note);外部调用 1 个(writeln!)。
log_start72–75 ↗
fn log_start(command: &[String], base_dir: Option<&Path>)
作用:记录一条命令即将开始执行。之后排查问题时,可以看到沙盒到底试图跑过什么。
数据流:输入是命令参数列表和可选日志目录 → 用 preview 压成短命令 → 加上 START: 前缀 → 交给 log_note 写成带时间和程序名的日志行。
调用关系:它会在准备或启动沙盒命令时被调用,比如 run_windows_sandbox_capture_for_permission_profile、prepare_elevated_spawn_context_for_permissions 和 prepare_spawn_context_common。它自己不直接写文件,而是把记录动作交给 log_note。
调用图:调用 2 个内部函数(log_note, preview);被 3 处调用(run_windows_sandbox_capture_for_permission_profile, prepare_elevated_spawn_context_for_permissions, prepare_spawn_context_common);外部调用 1 个(format!)。
log_success77–80 ↗
fn log_success(command: &[String], base_dir: Option<&Path>)
作用:记录一条命令已经成功完成。它让日志里不只看到开始,还能看到哪些任务顺利结束了。
数据流:输入是命令参数列表和可选日志目录 → 生成短命令预览 → 加上 SUCCESS: 前缀 → 交给 log_note 写入当天日志。
调用关系:它通常在命令退出并确认成功后由 finalize_exit 或 run_windows_sandbox_capture_with_filesystem_overrides 调用。它依赖 preview 整理命令,依赖 log_note 统一写日志格式。
调用图:调用 2 个内部函数(log_note, preview);被 2 处调用(finalize_exit, run_windows_sandbox_capture_with_filesystem_overrides);外部调用 1 个(format!)。
log_failure82–85 ↗
fn log_failure(command: &[String], detail: &str, base_dir: Option<&Path>)
作用:记录一条命令失败了,并带上失败原因。排查沙盒问题时,这通常是最有价值的日志。
数据流:输入是命令参数列表、失败说明和可选日志目录 → 把命令缩成预览 → 拼成 FAILURE: 命令 (原因) 的文字 → 交给 log_note 加时间后写入日志。
调用关系:它会在 finalize_exit 或 run_windows_sandbox_capture_with_filesystem_overrides 发现失败时被调用。它和 log_success 是一对:一个记成功,一个记失败。
调用图:调用 2 个内部函数(log_note, preview);被 2 处调用(finalize_exit, run_windows_sandbox_capture_with_filesystem_overrides);外部调用 1 个(format!)。
debug_log88–93 ↗
fn debug_log(msg: &str, base_dir: Option<&Path>)
作用:写调试日志,但只有明确打开 SBX_DEBUG=1 时才生效。这样开发排错时能看到细节,普通运行时不会被大量信息打扰。
数据流:输入是一条调试消息和可选日志目录 → 先读取环境变量 SBX_DEBUG;只有值等于 1 时,才把 DEBUG: 消息写入日志,并同时打印到标准错误输出 → 没打开时不产生任何输出。
调用关系:它被 create、grant_desktop_access、load_marker、load_users、remove_sandbox_users_file、create_process_as_user 等底层步骤调用,用来在复杂系统操作中留下额外线索。真正写文件仍交给 append_line。
调用图:调用 1 个内部函数(append_line);被 6 处调用(create, grant_desktop_access, load_marker, load_users, remove_sandbox_users_file, create_process_as_user);外部调用 3 个(eprintln!, format!, var)。
log_note96–99 ↗
fn log_note(msg: &str, base_dir: Option<&Path>)
作用:写一条普通日志备注,是这个文件里最通用的记日志入口。其他开始、成功、失败日志最终也会走到这里。
数据流:输入是一条消息和可选日志目录 → 取当前本地时间,取当前程序名 → 拼成类似 [时间 程序名] 消息 的一行 → 交给 append_line 写入当天日志。
调用关系:它被很多沙盒流程调用,比如权限收紧、扫描可写目录、复制辅助程序、隐藏用户目录等。log_start、log_success、log_failure 也用它来统一日志格式。
调用图:调用 1 个内部函数(append_line);被 16 处调用(apply_capability_denies_for_world_writable_for_permissions, apply_world_writable_scan_and_denies_for_permissions, audit_everyone_writable, copy_helper_if_needed, resolve_current_exe_for_launch, resolve_helper_for_launch, hide_current_user_profile_dir, hide_newly_created_users, hide_users_in_winlogon, require_logon_sandbox_creds (+6 more));外部调用 2 个(now, format!)。
tests::preview_does_not_panic_on_utf8_boundary106–114 ↗
fn preview_does_not_panic_on_utf8_boundary()
作用:测试命令预览在截短包含 emoji 的字符串时不会崩溃。它防止以后有人改代码时又把多字节字符截坏。
数据流:它构造一个刚好可能被截在 emoji 中间的命令 → 用 catch_unwind 捕获是否发生崩溃 → 检查结果正常,并且预览长度没有超过限制。
调用关系:这是 preview 的安全性测试。它不参与正常运行,只在测试时验证 preview 调用 take_bytes_at_char_boundary 的做法确实可靠。
tests::log_note_writes_to_daily_rolling_log117–138 ↗
fn log_note_writes_to_daily_rolling_log()
作用:测试 log_note 真的会在目录里写出每天滚动的日志文件,并且内容能读回来。
数据流:它创建一个临时目录 → 调用 log_note 写入 hello daily log → 读取目录,确认只出现一个日志文件,文件名以 sandbox. 开头、以 .log 结尾 → 再读取文件内容确认消息存在。
调用关系:这是 log_note、append_line 和 log_writer 串起来的集成测试。它模拟真实写日志过程,确认从入口到磁盘文件这条链路能跑通。
调用图:调用 1 个内部函数(log_note);外部调用 5 个(assert!, assert_eq!, read_dir, read_to_string, tempdir)。
tests::log_file_path_for_utc_date_matches_rolling_appender_name141–148 ↗
fn log_file_path_for_utc_date_matches_rolling_appender_name()
作用:测试指定日期生成的日志文件名格式是否符合约定。这样手写路径和自动滚动日志使用的名字不会对不上。
数据流:它创建日期 2026-05-21 → 调用 log_file_path_for_utc_date → 检查结果等于 logs/sandbox.2026-05-21.log。
调用关系:这是 log_file_path_for_utc_date 的格式测试。它保证 current_log_file_path 等上层函数依赖的命名规则稳定。
调用图:外部调用 2 个(assert_eq!, from_ymd_opt)。
tests::current_log_file_path_for_codex_home_uses_sandbox_dir151–158 ↗
fn current_log_file_path_for_codex_home_uses_sandbox_dir()
作用:测试从 Codex 主目录推导日志路径时,确实会进入 .sandbox 子目录。它防止日志被错误地放到主目录根上。
数据流:它准备一个 codex-home 路径 → 调用 current_log_file_path_for_codex_home → 和直接对 codex-home/.sandbox 调用 current_log_file_path 的结果比较 → 确认两者一致。
调用关系:这是 current_log_file_path_for_codex_home 的路径关系测试。它确认该函数正确使用 sandbox_dir 这条约定。
调用图:外部调用 2 个(new, assert_eq!)。